diff --git a/Cargo.toml b/Cargo.toml index 535224e5c..2d288a0ff 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,5 @@ [workspace] members = [ - "JS/wasm/crates/apis", "JS/wasm/crates/arakoo-core", "JS/wasm/crates/cli", "JS/wasm/crates/serve", @@ -13,15 +12,20 @@ edition = "2021" version = "0.0.1" [workspace.dependencies] -wizer = "4.0.0" -wasmtime = "16" -wasmtime-wasi = "16" -wasi-common = "16" +wizer = "6.0.0" +wasmtime = { features = ["async"], version = "19" } +wasmtime-wasi = "19" javy = { version = "2.1.0" } anyhow = "1.0.79" once_cell = "1.19.0" serde = { version = "1", features = ["derive"] } serde_json = "1" +serde_bytes = "0.11" +http = "1.1.0" +reqwest = { version = "0.12.4", features = [ + "blocking","json" +] } + [profile.release] lto = true opt-level = 's' diff --git a/JS/jsonnet/src/jsonnet.js b/JS/jsonnet/src/jsonnet.js index 988c4a737..2cd69d04c 100644 --- a/JS/jsonnet/src/jsonnet.js +++ b/JS/jsonnet/src/jsonnet.js @@ -8,7 +8,7 @@ if (!isArakoo) { jsonnet_evaluate_snippet, jsonnet_destroy, jsonnet_make, - ext_string, + jsonnet_ext_string, jsonnet_evaluate_file, get_func, set_func, @@ -24,7 +24,7 @@ if (!isArakoo) { } extString(key, value) { - ext_string(this.vm, key, value); + jsonnet_ext_string(this.vm, key, value); return this; } @@ -55,24 +55,35 @@ if (!isArakoo) { }; } else { Jsonnet = class Jsonnet { - constructor() { - this.vars = {}; + constructor() {} + + #getVm() { + if (!this.vm) { + this.vm = __jsonnet_make(); + } + return this.vm; + } + + evaluateSnippet(snippet) { + let vm = this.#getVm(); + return __jsonnet_evaluate_snippet(vm, snippet); } extString(key, value) { - this.vars[key] = value; + let vm = this.#getVm(); + __jsonnet_ext_string(vm, key, value); return this; } - evaluateSnippet(snippet) { - let vars = JSON.stringify(this.vars); - return __jsonnet_evaluate_snippet(vars, snippet); - } + evaluateFile(filename) { - let vars = JSON.stringify(this.vars); - return __jsonnet_evaluate_file(vars, filename); + let vm = this.#getVm(); + return __jsonnet_evaluate_file(vm, filename); } - destroy() {} + destroy() { + let vm = this.#getVm(); + __jsonnet_destroy(vm); + } }; } diff --git a/JS/jsonnet/src/jsonnet_wasm.d.ts b/JS/jsonnet/src/jsonnet_wasm.d.ts index b0876700d..160156e6c 100644 --- a/JS/jsonnet/src/jsonnet_wasm.d.ts +++ b/JS/jsonnet/src/jsonnet_wasm.d.ts @@ -1,45 +1,45 @@ /* tslint:disable */ /* eslint-disable */ /** - * @returns {number} - */ +* @returns {number} +*/ export function jsonnet_make(): number; /** - * @param {number} vm - */ +* @param {number} vm +*/ export function jsonnet_destroy(vm: number): void; /** - * @param {number} vm - * @param {string} filename - * @param {string} snippet - * @returns {string} - */ +* @param {number} vm +* @param {string} filename +* @param {string} snippet +* @returns {string} +*/ export function jsonnet_evaluate_snippet(vm: number, filename: string, snippet: string): string; /** - * @param {number} vm - * @param {string} filename - * @returns {string} - */ +* @param {number} vm +* @param {string} filename +* @returns {string} +*/ export function jsonnet_evaluate_file(vm: number, filename: string): string; /** - * @param {number} vm - * @param {string} key - * @param {string} value - */ -export function ext_string(vm: number, key: string, value: string): void; +* @param {number} vm +* @param {string} key +* @param {string} value +*/ +export function jsonnet_ext_string(vm: number, key: string, value: string): void; /** - * @param {string} name - * @returns {Function | undefined} - */ +* @param {string} name +* @returns {Function | undefined} +*/ export function get_func(name: string): Function | undefined; /** - * @param {string} name - * @param {Function} func - */ +* @param {string} name +* @param {Function} func +*/ export function set_func(name: string, func: Function): void; /** - * @param {number} vm - * @param {string} name - * @param {number} args_num - */ +* @param {number} vm +* @param {string} name +* @param {number} args_num +*/ export function register_native_callback(vm: number, name: string, args_num: number): void; diff --git a/JS/jsonnet/src/jsonnet_wasm.js b/JS/jsonnet/src/jsonnet_wasm.js index 32b2f627e..7f1d7c2cf 100644 --- a/JS/jsonnet/src/jsonnet_wasm.js +++ b/JS/jsonnet/src/jsonnet_wasm.js @@ -1,5 +1,5 @@ let imports = {}; -imports["__wbindgen_placeholder__"] = module.exports; +imports['__wbindgen_placeholder__'] = module.exports; let wasm; const { read_file } = require(String.raw`./snippets/arakoo-jsonnet-17c737407ebd2d3c/read-file.js`); const { TextEncoder, TextDecoder } = require(`util`); @@ -8,9 +8,7 @@ const heap = new Array(128).fill(undefined); heap.push(undefined, null, true, false); -function getObject(idx) { - return heap[idx]; -} +function getObject(idx) { return heap[idx]; } let WASM_VECTOR_LEN = 0; @@ -23,29 +21,27 @@ function getUint8Memory0() { return cachedUint8Memory0; } -let cachedTextEncoder = new TextEncoder("utf-8"); - -const encodeString = - typeof cachedTextEncoder.encodeInto === "function" - ? function (arg, view) { - return cachedTextEncoder.encodeInto(arg, view); - } - : function (arg, view) { - const buf = cachedTextEncoder.encode(arg); - view.set(buf); - return { - read: arg.length, - written: buf.length, - }; - }; +let cachedTextEncoder = new TextEncoder('utf-8'); + +const encodeString = (typeof cachedTextEncoder.encodeInto === 'function' + ? function (arg, view) { + return cachedTextEncoder.encodeInto(arg, view); +} + : function (arg, view) { + const buf = cachedTextEncoder.encode(arg); + view.set(buf); + return { + read: arg.length, + written: buf.length + }; +}); function passStringToWasm0(arg, malloc, realloc) { + if (realloc === undefined) { const buf = cachedTextEncoder.encode(arg); const ptr = malloc(buf.length, 1) >>> 0; - getUint8Memory0() - .subarray(ptr, ptr + buf.length) - .set(buf); + getUint8Memory0().subarray(ptr, ptr + buf.length).set(buf); WASM_VECTOR_LEN = buf.length; return ptr; } @@ -59,7 +55,7 @@ function passStringToWasm0(arg, malloc, realloc) { for (; offset < len; offset++) { const code = arg.charCodeAt(offset); - if (code > 0x7f) break; + if (code > 0x7F) break; mem[ptr + offset] = code; } @@ -67,7 +63,7 @@ function passStringToWasm0(arg, malloc, realloc) { if (offset !== 0) { arg = arg.slice(offset); } - ptr = realloc(ptr, len, (len = offset + arg.length * 3), 1) >>> 0; + ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0; const view = getUint8Memory0().subarray(ptr + offset, ptr + len); const ret = encodeString(arg, view); @@ -106,7 +102,7 @@ function takeObject(idx) { return ret; } -let cachedTextDecoder = new TextDecoder("utf-8", { ignoreBOM: true, fatal: true }); +let cachedTextDecoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }); cachedTextDecoder.decode(); @@ -127,39 +123,39 @@ function addHeapObject(obj) { function debugString(val) { // primitive types const type = typeof val; - if (type == "number" || type == "boolean" || val == null) { - return `${val}`; + if (type == 'number' || type == 'boolean' || val == null) { + return `${val}`; } - if (type == "string") { + if (type == 'string') { return `"${val}"`; } - if (type == "symbol") { + if (type == 'symbol') { const description = val.description; if (description == null) { - return "Symbol"; + return 'Symbol'; } else { return `Symbol(${description})`; } } - if (type == "function") { + if (type == 'function') { const name = val.name; - if (typeof name == "string" && name.length > 0) { + if (typeof name == 'string' && name.length > 0) { return `Function(${name})`; } else { - return "Function"; + return 'Function'; } } // objects if (Array.isArray(val)) { const length = val.length; - let debug = "["; + let debug = '['; if (length > 0) { debug += debugString(val[0]); } - for (let i = 1; i < length; i++) { - debug += ", " + debugString(val[i]); + for(let i = 1; i < length; i++) { + debug += ', ' + debugString(val[i]); } - debug += "]"; + debug += ']'; return debug; } // Test for built-in @@ -171,14 +167,14 @@ function debugString(val) { // Failed to match the standard '[object ClassName]' return toString.call(val); } - if (className == "Object") { + if (className == 'Object') { // we're a user defined class or Object // JSON.stringify avoids problems with cycles, and is generally much // easier than looping through ownProperties of `val`. try { - return "Object(" + JSON.stringify(val) + ")"; + return 'Object(' + JSON.stringify(val) + ')'; } catch (_) { - return "Object"; + return 'Object'; } } // errors @@ -197,27 +193,27 @@ function handleError(f, args) { } } /** - * @returns {number} - */ -module.exports.jsonnet_make = function () { +* @returns {number} +*/ +module.exports.jsonnet_make = function() { const ret = wasm.jsonnet_make(); return ret >>> 0; }; /** - * @param {number} vm - */ -module.exports.jsonnet_destroy = function (vm) { +* @param {number} vm +*/ +module.exports.jsonnet_destroy = function(vm) { wasm.jsonnet_destroy(vm); }; /** - * @param {number} vm - * @param {string} filename - * @param {string} snippet - * @returns {string} - */ -module.exports.jsonnet_evaluate_snippet = function (vm, filename, snippet) { +* @param {number} vm +* @param {string} filename +* @param {string} snippet +* @returns {string} +*/ +module.exports.jsonnet_evaluate_snippet = function(vm, filename, snippet) { let deferred3_0; let deferred3_1; try { @@ -239,11 +235,11 @@ module.exports.jsonnet_evaluate_snippet = function (vm, filename, snippet) { }; /** - * @param {number} vm - * @param {string} filename - * @returns {string} - */ -module.exports.jsonnet_evaluate_file = function (vm, filename) { +* @param {number} vm +* @param {string} filename +* @returns {string} +*/ +module.exports.jsonnet_evaluate_file = function(vm, filename) { let deferred2_0; let deferred2_1; try { @@ -263,23 +259,23 @@ module.exports.jsonnet_evaluate_file = function (vm, filename) { }; /** - * @param {number} vm - * @param {string} key - * @param {string} value - */ -module.exports.ext_string = function (vm, key, value) { +* @param {number} vm +* @param {string} key +* @param {string} value +*/ +module.exports.jsonnet_ext_string = function(vm, key, value) { const ptr0 = passStringToWasm0(key, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); const len0 = WASM_VECTOR_LEN; const ptr1 = passStringToWasm0(value, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); const len1 = WASM_VECTOR_LEN; - wasm.ext_string(vm, ptr0, len0, ptr1, len1); + wasm.jsonnet_ext_string(vm, ptr0, len0, ptr1, len1); }; /** - * @param {string} name - * @returns {Function | undefined} - */ -module.exports.get_func = function (name) { +* @param {string} name +* @returns {Function | undefined} +*/ +module.exports.get_func = function(name) { const ptr0 = passStringToWasm0(name, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); const len0 = WASM_VECTOR_LEN; const ret = wasm.get_func(ptr0, len0); @@ -287,81 +283,73 @@ module.exports.get_func = function (name) { }; /** - * @param {string} name - * @param {Function} func - */ -module.exports.set_func = function (name, func) { +* @param {string} name +* @param {Function} func +*/ +module.exports.set_func = function(name, func) { const ptr0 = passStringToWasm0(name, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); const len0 = WASM_VECTOR_LEN; wasm.set_func(ptr0, len0, addHeapObject(func)); }; /** - * @param {number} vm - * @param {string} name - * @param {number} args_num - */ -module.exports.register_native_callback = function (vm, name, args_num) { +* @param {number} vm +* @param {string} name +* @param {number} args_num +*/ +module.exports.register_native_callback = function(vm, name, args_num) { const ptr0 = passStringToWasm0(name, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); const len0 = WASM_VECTOR_LEN; wasm.register_native_callback(vm, ptr0, len0, args_num); }; -module.exports.__wbindgen_string_get = function (arg0, arg1) { +module.exports.__wbindgen_string_get = function(arg0, arg1) { const obj = getObject(arg1); - const ret = typeof obj === "string" ? obj : undefined; - var ptr1 = isLikeNone(ret) - ? 0 - : passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const ret = typeof(obj) === 'string' ? obj : undefined; + var ptr1 = isLikeNone(ret) ? 0 : passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); var len1 = WASM_VECTOR_LEN; getInt32Memory0()[arg0 / 4 + 1] = len1; getInt32Memory0()[arg0 / 4 + 0] = ptr1; }; -module.exports.__wbindgen_object_drop_ref = function (arg0) { +module.exports.__wbindgen_object_drop_ref = function(arg0) { takeObject(arg0); }; -module.exports.__wbindgen_string_new = function (arg0, arg1) { +module.exports.__wbindgen_string_new = function(arg0, arg1) { const ret = getStringFromWasm0(arg0, arg1); return addHeapObject(ret); }; -module.exports.__wbg_readfile_5b48d0f7e3518df2 = function () { - return handleError(function (arg0, arg1, arg2) { - const ret = read_file(getStringFromWasm0(arg1, arg2)); - const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); - const len1 = WASM_VECTOR_LEN; - getInt32Memory0()[arg0 / 4 + 1] = len1; - getInt32Memory0()[arg0 / 4 + 0] = ptr1; - }, arguments); -}; +module.exports.__wbg_readfile_5b48d0f7e3518df2 = function() { return handleError(function (arg0, arg1, arg2) { + const ret = read_file(getStringFromWasm0(arg1, arg2)); + const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len1 = WASM_VECTOR_LEN; + getInt32Memory0()[arg0 / 4 + 1] = len1; + getInt32Memory0()[arg0 / 4 + 0] = ptr1; +}, arguments) }; -module.exports.__wbindgen_object_clone_ref = function (arg0) { +module.exports.__wbindgen_object_clone_ref = function(arg0) { const ret = getObject(arg0); return addHeapObject(ret); }; -module.exports.__wbg_call_27c0f87801dedf93 = function () { - return handleError(function (arg0, arg1) { - const ret = getObject(arg0).call(getObject(arg1)); - return addHeapObject(ret); - }, arguments); -}; +module.exports.__wbg_call_27c0f87801dedf93 = function() { return handleError(function (arg0, arg1) { + const ret = getObject(arg0).call(getObject(arg1)); + return addHeapObject(ret); +}, arguments) }; -module.exports.__wbg_call_b3ca7c6051f9bec1 = function () { - return handleError(function (arg0, arg1, arg2) { - const ret = getObject(arg0).call(getObject(arg1), getObject(arg2)); - return addHeapObject(ret); - }, arguments); -}; +module.exports.__wbg_call_b3ca7c6051f9bec1 = function() { return handleError(function (arg0, arg1, arg2) { + const ret = getObject(arg0).call(getObject(arg1), getObject(arg2)); + return addHeapObject(ret); +}, arguments) }; -module.exports.__wbg_new_abda76e883ba8a5f = function () { +module.exports.__wbg_new_abda76e883ba8a5f = function() { const ret = new Error(); return addHeapObject(ret); }; -module.exports.__wbg_stack_658279fe44541cf6 = function (arg0, arg1) { +module.exports.__wbg_stack_658279fe44541cf6 = function(arg0, arg1) { const ret = getObject(arg1).stack; const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); const len1 = WASM_VECTOR_LEN; @@ -369,7 +357,7 @@ module.exports.__wbg_stack_658279fe44541cf6 = function (arg0, arg1) { getInt32Memory0()[arg0 / 4 + 0] = ptr1; }; -module.exports.__wbg_error_f851667af71bcfc6 = function (arg0, arg1) { +module.exports.__wbg_error_f851667af71bcfc6 = function(arg0, arg1) { let deferred0_0; let deferred0_1; try { @@ -381,7 +369,7 @@ module.exports.__wbg_error_f851667af71bcfc6 = function (arg0, arg1) { } }; -module.exports.__wbindgen_debug_string = function (arg0, arg1) { +module.exports.__wbindgen_debug_string = function(arg0, arg1) { const ret = debugString(getObject(arg1)); const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); const len1 = WASM_VECTOR_LEN; @@ -389,14 +377,15 @@ module.exports.__wbindgen_debug_string = function (arg0, arg1) { getInt32Memory0()[arg0 / 4 + 0] = ptr1; }; -module.exports.__wbindgen_throw = function (arg0, arg1) { +module.exports.__wbindgen_throw = function(arg0, arg1) { throw new Error(getStringFromWasm0(arg0, arg1)); }; -const path = require("path").join(__dirname, "jsonnet_wasm_bg.wasm"); -const bytes = require("fs").readFileSync(path); +const path = require('path').join(__dirname, 'jsonnet_wasm_bg.wasm'); +const bytes = require('fs').readFileSync(path); const wasmModule = new WebAssembly.Module(bytes); const wasmInstance = new WebAssembly.Instance(wasmModule, imports); wasm = wasmInstance.exports; module.exports.__wasm = wasm; + diff --git a/JS/jsonnet/src/jsonnet_wasm_bg.wasm b/JS/jsonnet/src/jsonnet_wasm_bg.wasm index 0a1c7abb7..320978dac 100644 Binary files a/JS/jsonnet/src/jsonnet_wasm_bg.wasm and b/JS/jsonnet/src/jsonnet_wasm_bg.wasm differ diff --git a/JS/jsonnet/src/jsonnet_wasm_bg.wasm.d.ts b/JS/jsonnet/src/jsonnet_wasm_bg.wasm.d.ts index b134fd153..82d72aa0d 100644 --- a/JS/jsonnet/src/jsonnet_wasm_bg.wasm.d.ts +++ b/JS/jsonnet/src/jsonnet_wasm_bg.wasm.d.ts @@ -3,16 +3,9 @@ export const memory: WebAssembly.Memory; export function jsonnet_make(): number; export function jsonnet_destroy(a: number): void; -export function jsonnet_evaluate_snippet( - a: number, - b: number, - c: number, - d: number, - e: number, - f: number -): void; +export function jsonnet_evaluate_snippet(a: number, b: number, c: number, d: number, e: number, f: number): void; export function jsonnet_evaluate_file(a: number, b: number, c: number, d: number): void; -export function ext_string(a: number, b: number, c: number, d: number, e: number): void; +export function jsonnet_ext_string(a: number, b: number, c: number, d: number, e: number): void; export function get_func(a: number, b: number): number; export function set_func(a: number, b: number, c: number): void; export function register_native_callback(a: number, b: number, c: number, d: number): void; diff --git a/JS/jsonnet/src/lib.rs b/JS/jsonnet/src/lib.rs index 04cbbb96a..620e7076f 100755 --- a/JS/jsonnet/src/lib.rs +++ b/JS/jsonnet/src/lib.rs @@ -127,7 +127,7 @@ pub fn jsonnet_evaluate_file(vm: *mut VM, filename: &str) -> String { } #[wasm_bindgen] -pub fn ext_string(vm: *mut VM, key: &str, value: &str) { +pub fn jsonnet_ext_string(vm: *mut VM, key: &str, value: &str) { let vm = unsafe { &mut *vm }; // let context_initializer_ref = vm.state.context_initializer(); @@ -226,7 +226,7 @@ mod test { // .unwrap(); unsafe { - ext_string( + jsonnet_ext_string( &mut *vm, // name.as_ptr() as *const c_char, "name", // value.as_ptr() as *const c_char, "afshan", diff --git a/JS/jsonnet/test/dist/test.js b/JS/jsonnet/test/dist/test.js index d9da59521..f30d37c0d 100644 --- a/JS/jsonnet/test/dist/test.js +++ b/JS/jsonnet/test/dist/test.js @@ -6,8 +6,7 @@ import { describe, it } from "mocha"; let jsonnet = new Jsonnet(); describe("Testing evaluateSnippet function of jsonnet library", () => { it("self reference", () => { - let result = JSON.parse( - jsonnet.evaluateSnippet(`{ + let result = JSON.parse(jsonnet.evaluateSnippet(`{ Martini: { local drink = self, ingredients: [ @@ -20,8 +19,7 @@ describe("Testing evaluateSnippet function of jsonnet library", () => { garnish: 'Olive', served: 'Straight Up', }, - }`) - ); + }`)); let expected = { Martini: { garnish: "Olive", @@ -42,15 +40,13 @@ describe("Testing evaluateSnippet function of jsonnet library", () => { expect(result).to.eql(expected); }); it("math operations", () => { - let result = JSON.parse( - jsonnet.evaluateSnippet(`{ + let result = JSON.parse(jsonnet.evaluateSnippet(`{ a: 1 + 2, b: 3 * 4, c: 5 / 6, d: 7 % 8, e: 9 - 10, - }`) - ); + }`)); let expected = { a: 3, b: 12, @@ -92,8 +88,7 @@ describe("Testing evaluateFile function of jsonnet library", () => { }); describe("Testing extString function of jsonnet library", () => { it("Test extString function", () => { - let result = JSON.parse( - jsonnet.extString("name", "Alice").evaluateSnippet(`local username = std.extVar('name'); + let result = JSON.parse(jsonnet.extString("name", "Alice").evaluateSnippet(`local username = std.extVar('name'); local Person(name='Alice') = { name: name, welcome: 'Hello ' + name + '!', @@ -101,8 +96,7 @@ describe("Testing extString function of jsonnet library", () => { { person1: Person(username), person2: Person('Bob'), - }`) - ); + }`)); let expected = { person1: { name: "Alice", @@ -135,13 +129,11 @@ describe("Testing regex function of jsonnet library", () => { }); describe("Testing join function of jsonnet library", () => { it("Test join function", () => { - let result = JSON.parse( - jsonnet.evaluateSnippet(`local a = "open"; + let result = JSON.parse(jsonnet.evaluateSnippet(`local a = "open"; local b = "source"; { "joined string":arakoo.join(a,b) - }`) - ); + }`)); let expected = { "joined string": "opensource", }; @@ -153,12 +145,10 @@ describe("Testing javascript native function of jsonnet library", () => { function add(a, b, c) { return a + b + c; } - let result = JSON.parse( - jsonnet.javascriptCallback("add", add).evaluateSnippet(`{ + let result = JSON.parse(jsonnet.javascriptCallback("add", add).evaluateSnippet(`{ "result": "Output "+arakoo.native("add")(1,2,3), "name":"Alice" - }`) - ); + }`)); expect(result).to.eql({ result: "Output 6", name: "Alice", @@ -173,12 +163,10 @@ describe("Testing javascript native function of jsonnet library", () => { } return sum; } - let result = JSON.parse( - jsonnet.javascriptCallback("arrsum", calcSum).evaluateSnippet(`{ + let result = JSON.parse(jsonnet.javascriptCallback("arrsum", calcSum).evaluateSnippet(`{ "result": "Output "+arakoo.native("arrsum")(${JSON.stringify(numArr)}), "name":"Alice" - }`) - ); + }`)); expect(result).to.eql({ result: "Output 15", name: "Alice", @@ -188,12 +176,10 @@ describe("Testing javascript native function of jsonnet library", () => { function concat(a, b) { return a + b; } - let result = JSON.parse( - jsonnet.javascriptCallback("concat", concat).evaluateSnippet(`{ + let result = JSON.parse(jsonnet.javascriptCallback("concat", concat).evaluateSnippet(`{ "result": "Output "+arakoo.native("concat")("Hello ","World"), "name":"Alice" - }`) - ); + }`)); expect(result).to.eql({ result: "Output Hello World", name: "Alice", @@ -202,11 +188,9 @@ describe("Testing javascript native function of jsonnet library", () => { }); describe("Testing includes function of jsonnet library", () => { it("Test includes function", () => { - let result = JSON.parse( - jsonnet.evaluateSnippet(`{ + let result = JSON.parse(jsonnet.evaluateSnippet(`{ "result":arakoo.includes("open source is awesome","source") - }`) - ); + }`)); let expected = { result: true, }; diff --git a/JS/wasi_snapshot_preview1.reactor.wasm b/JS/wasi_snapshot_preview1.reactor.wasm new file mode 100644 index 000000000..8149eb955 Binary files /dev/null and b/JS/wasi_snapshot_preview1.reactor.wasm differ diff --git a/JS/wasm/crates/apis/Cargo.toml b/JS/wasm/crates/apis/Cargo.toml deleted file mode 100644 index 0711d4b0e..000000000 --- a/JS/wasm/crates/apis/Cargo.toml +++ /dev/null @@ -1,21 +0,0 @@ -[package] -name = "apis" -edition.workspace = true -version.workspace = true - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[features] -console = [] -random = ["dep:fastrand"] -stream_io = [] -text_encoding = [] - -[dependencies] -anyhow = { workspace = true } -fastrand = { version = "2.0.1", optional = true } -javy = { workspace = true } -# wit-bindgen-rust = { git = "https://github.com/bytecodealliance/wit-bindgen", tag = "v0.2.0" } -tokio = "1.36.0" -serde = { workspace = true } -serde_json = { workspace = true } diff --git a/JS/wasm/crates/apis/src/http/mod.rs b/JS/wasm/crates/apis/src/http/mod.rs deleted file mode 100644 index 324646a1f..000000000 --- a/JS/wasm/crates/apis/src/http/mod.rs +++ /dev/null @@ -1,98 +0,0 @@ -pub mod types; - -use std::collections::HashMap; - -use anyhow::Result; -use javy::quickjs::{JSContextRef, JSValue, JSValueRef}; - -use crate::{fetch, get_response, get_response_len, http::types::Request, JSApiSet}; - -pub(super) struct Http; - -impl JSApiSet for Http { - fn register(&self, runtime: &javy::Runtime, _config: &crate::APIConfig) -> Result<()> { - let context = runtime.context(); - context.eval_global("http.js", include_str!("shims/dist/index.js"))?; - let global = context.global_object()?; - global.set_property("arakoo", context.value_from_bool(true)?)?; - global.set_property("fetch_internal", context.wrap_callback(fetch_callback())?)?; - Ok(()) - } -} - -fn fetch_callback( -) -> impl FnMut(&JSContextRef, JSValueRef, &[JSValueRef]) -> anyhow::Result { - move |_ctx, _this, args| { - if args.len() < 1 { - return Err(anyhow::anyhow!( - "Expected at least 1 argument, got {}", - args.len() - )); - } - let uri = args.get(0).unwrap().to_string(); - let opts: HashMap = args[1].try_into()?; - let method = opts.get("method").unwrap_or(&"GET".into()).to_string(); - let headers = match opts.get("headers") { - Some(JSValue::Object(headers)) => headers - .iter() - .map(|(k, v)| (k.to_string(), v.to_string())) - .collect(), - _ => HashMap::default(), - }; - let body = opts.get("body").unwrap_or(&"".into()).to_string(); - let params = match opts.get("params") { - Some(JSValue::Object(params)) => params - .iter() - .map(|(k, v)| (k.to_string(), v.to_string())) - .collect(), - _ => HashMap::default(), - }; - - let request = - serde_json::to_string(&Request::new(uri, method, headers, body, params)).unwrap(); - let request_ptr = request.as_ptr(); - let request_len = request.len() as i32; - unsafe { fetch(request_ptr, request_len) } - let response_len = unsafe { get_response_len() }; - let mut response_buffer = Vec::with_capacity(response_len as usize); - let response_ptr = response_buffer.as_mut_ptr(); - let response_buffer = unsafe { - get_response(response_ptr); - Vec::from_raw_parts(response_ptr, response_len as usize, response_len as usize) - }; - let response: serde_json::Value = match serde_json::from_slice(&response_buffer) { - Ok(response) => response, - Err(e) => { - eprintln!("Failed to parse fetch response: {}", e); - return Err(anyhow::anyhow!( - "Failed to parse fetch response: {}", - e.to_string() - )); - } - }; - let mut headers_map: HashMap = HashMap::new(); - for (key, value) in response["headers"].as_object().unwrap() { - headers_map.insert( - key.to_string(), - JSValue::String(value.as_str().unwrap().to_string()), - ); - } - let mut response_map: HashMap = HashMap::new(); - response_map.insert( - "status".to_string(), - JSValue::Int(response["status"].as_i64().unwrap().try_into().unwrap()), - ); - response_map.insert( - "statusText".to_string(), - JSValue::String(response["statusText"].as_str().unwrap().to_string()), - ); - response_map.insert( - "body".to_string(), - JSValue::String(response["body"].as_str().unwrap().to_string()), - ); - response_map.insert("headers".to_string(), JSValue::Object(headers_map)); - let response_obj = JSValue::Object(response_map); - // todo!("fetch"); - Ok(response_obj) - } -} diff --git a/JS/wasm/crates/apis/src/http/types.rs b/JS/wasm/crates/apis/src/http/types.rs deleted file mode 100644 index 121bb5f61..000000000 --- a/JS/wasm/crates/apis/src/http/types.rs +++ /dev/null @@ -1,38 +0,0 @@ -use serde::{Deserialize, Serialize}; -use std::collections::HashMap; - -#[derive(Serialize, Deserialize, Debug)] -pub struct Request { - url: String, - method: String, - headers: HashMap, - body: String, - params: HashMap, -} - -#[derive(Serialize, Deserialize, Debug)] -pub struct Response { - pub headers: HashMap, - pub status: u16, - #[serde(rename = "statusText")] - pub status_text: String, - pub body: Option, -} - -impl Request { - pub fn new( - url: String, - method: String, - headers: HashMap, - body: String, - params: HashMap, - ) -> Self { - Self { - url, - method, - headers, - body, - params, - } - } -} diff --git a/JS/wasm/crates/apis/src/jsonnet/mod.rs b/JS/wasm/crates/apis/src/jsonnet/mod.rs deleted file mode 100644 index a6a7175d6..000000000 --- a/JS/wasm/crates/apis/src/jsonnet/mod.rs +++ /dev/null @@ -1,103 +0,0 @@ -use javy::quickjs::{JSContextRef, JSValue, JSValueRef}; - -use crate::{ - jsonnet_evaluate, jsonnet_evaluate_file, jsonnet_output, jsonnet_output_len, JSApiSet, -}; - -pub(super) struct Jsonnet; - -impl JSApiSet for Jsonnet { - fn register(&self, runtime: &javy::Runtime, _config: &crate::APIConfig) -> anyhow::Result<()> { - let context = runtime.context(); - let global = context.global_object()?; - global.set_property( - "__jsonnet_evaluate_snippet", - context.wrap_callback(jsonnet_evaluate_snippet_callback())?, - )?; - global.set_property( - "__jsonnet_evaluate_file", - context.wrap_callback(jsonnet_evaluate_file_callback())?, - )?; - - Ok(()) - } -} - -fn jsonnet_evaluate_file_callback( -) -> impl FnMut(&JSContextRef, JSValueRef, &[JSValueRef]) -> anyhow::Result { - move |_ctx, _this, args| { - // check the number of arguments - if args.len() != 2 { - return Err(anyhow::anyhow!("Expected 2 arguments, got {}", args.len())); - } - let var = args.get(0).unwrap().to_string(); - let path = args.get(1).unwrap().to_string(); - let var_len = var.len() as i32; - let path_len = path.len() as i32; - let var_ptr = var.as_ptr(); - let path_ptr = path.as_ptr(); - - unsafe { jsonnet_evaluate_file(var_ptr, var_len, path_ptr, path_len) } - let out_len = unsafe { jsonnet_output_len() }; - - let mut out_buffer = Vec::with_capacity(out_len as usize); - let out_ptr = out_buffer.as_mut_ptr(); - let out_buffer = unsafe { - jsonnet_output(out_ptr); - Vec::from_raw_parts(out_ptr, out_len as usize, out_len as usize) - }; - // println!("out_buffer: {}", String::from_utf8(out_buffer.clone()).expect("unable to convert to string")); - - let jsonnet_output: serde_json::Value = match serde_json::from_slice(&out_buffer) { - Ok(output) => output, - Err(e) => { - eprintln!("Failed to parse jsonnet output: {}", e); - return Err(anyhow::anyhow!( - "Failed to parse jsonnet output: {}", - e.to_string() - )); - } - }; - let jsonnet_output = jsonnet_output.to_string(); - Ok(jsonnet_output.into()) - } -} - -fn jsonnet_evaluate_snippet_callback( -) -> impl FnMut(&JSContextRef, JSValueRef, &[JSValueRef]) -> anyhow::Result { - move |_ctx, _this, args| { - // check the number of arguments - if args.len() != 2 { - return Err(anyhow::anyhow!("Expected 2 arguments, got {}", args.len())); - } - let var = args.get(0).unwrap().to_string(); - let code = args.get(1).unwrap().to_string(); - let var_len = var.len() as i32; - let path_len = code.len() as i32; - let var_ptr = var.as_ptr(); - let path_ptr = code.as_ptr(); - - unsafe { jsonnet_evaluate(var_ptr, var_len, path_ptr, path_len) } - let out_len = unsafe { jsonnet_output_len() }; - - let mut out_buffer = Vec::with_capacity(out_len as usize); - let out_ptr = out_buffer.as_mut_ptr(); - let out_buffer = unsafe { - jsonnet_output(out_ptr); - Vec::from_raw_parts(out_ptr, out_len as usize, out_len as usize) - }; - - let jsonnet_output: serde_json::Value = match serde_json::from_slice(&out_buffer) { - Ok(output) => output, - Err(e) => { - eprintln!("Failed to parse jsonnet output: {}", e); - return Err(anyhow::anyhow!( - "Failed to parse jsonnet output: {}", - e.to_string() - )); - } - }; - let jsonnet_output = jsonnet_output.to_string(); - Ok(jsonnet_output.into()) - } -} diff --git a/JS/wasm/crates/arakoo-core/Cargo.toml b/JS/wasm/crates/arakoo-core/Cargo.toml index 92dc76e1a..af6c0ba2c 100644 --- a/JS/wasm/crates/arakoo-core/Cargo.toml +++ b/JS/wasm/crates/arakoo-core/Cargo.toml @@ -1,25 +1,24 @@ [package] -name = "arakoo-core" +name = "arakoo-js-engine" edition.workspace = true version.workspace = true -[[bin]] -name = "arakoo-core" -path = "src/main.rs" [lib] -name = "javy_quickjs_provider" crate-type = ["cdylib"] [dependencies] anyhow = { workspace = true } javy = { workspace = true, features = ["export_alloc_fns", "json"] } once_cell = { workspace = true } -apis = { path = "../apis", features = [ - "console", - "text_encoding", - "random", - "stream_io", -] } serde_json = { workspace = true } -[features] -experimental_event_loop = [] +serde = { workspace = true } +serde_bytes = { workspace = true } +send_wrapper = "0.6.0" +wit-bindgen = "0.24.0" +# fastrand = { version = "2.0.1", optional = true } +# wit-bindgen-rust = { git = "https://github.com/bytecodealliance/wit-bindgen", tag = "v0.2.0" } +tokio = "1.36.0" +http = { workspace = true } +quickjs-wasm-rs = "3.0.0" +bytes = { version = "1.6.0", features = ["serde"] } +fastrand = "2.1.0" diff --git a/JS/wasm/crates/apis/src/api_config.rs b/JS/wasm/crates/arakoo-core/src/apis/api_config.rs similarity index 71% rename from JS/wasm/crates/apis/src/api_config.rs rename to JS/wasm/crates/arakoo-core/src/apis/api_config.rs index 0b0955adc..1237a427d 100644 --- a/JS/wasm/crates/apis/src/api_config.rs +++ b/JS/wasm/crates/arakoo-core/src/apis/api_config.rs @@ -7,6 +7,5 @@ /// ``` #[derive(Debug, Default)] pub struct APIConfig { - #[cfg(feature = "console")] - pub(crate) console: crate::console::ConsoleConfig, + pub(crate) console: super::console::ConsoleConfig, } diff --git a/JS/wasm/crates/arakoo-core/src/apis/axios/.gitignore b/JS/wasm/crates/arakoo-core/src/apis/axios/.gitignore new file mode 100644 index 000000000..763301fc0 --- /dev/null +++ b/JS/wasm/crates/arakoo-core/src/apis/axios/.gitignore @@ -0,0 +1,2 @@ +dist/ +node_modules/ \ No newline at end of file diff --git a/JS/wasm/crates/arakoo-core/src/apis/axios/axios/index.js b/JS/wasm/crates/arakoo-core/src/apis/axios/axios/index.js new file mode 100644 index 000000000..93d8cf6f1 --- /dev/null +++ b/JS/wasm/crates/arakoo-core/src/apis/axios/axios/index.js @@ -0,0 +1,3 @@ +import axios from "axios"; + +const axios = axios; \ No newline at end of file diff --git a/JS/wasm/crates/arakoo-core/src/apis/axios/axios/package.json b/JS/wasm/crates/arakoo-core/src/apis/axios/axios/package.json new file mode 100644 index 000000000..815679d76 --- /dev/null +++ b/JS/wasm/crates/arakoo-core/src/apis/axios/axios/package.json @@ -0,0 +1,20 @@ +{ + "name": "axios", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1", + "build": "webpack" + }, + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "axios": "1.7.0-beta.2", + "webpack": "^5.91.0" + }, + "devDependencies": { + "webpack-cli": "^5.1.4" + } +} diff --git a/JS/wasm/crates/arakoo-core/src/apis/axios/axios/webpack.config.cjs b/JS/wasm/crates/arakoo-core/src/apis/axios/axios/webpack.config.cjs new file mode 100644 index 000000000..72351edfd --- /dev/null +++ b/JS/wasm/crates/arakoo-core/src/apis/axios/axios/webpack.config.cjs @@ -0,0 +1,16 @@ +const { optimize } = require('webpack'); +const webpack = require('webpack'); + +const config = { + entry: './index.js', + output: { + path: __dirname + '/dist', + filename: 'index.js', + iife:false + }, + optimization:{ + minimize:false + } +} + +module.exports = config; \ No newline at end of file diff --git a/JS/wasm/crates/arakoo-core/src/apis/axios/mod.rs b/JS/wasm/crates/arakoo-core/src/apis/axios/mod.rs new file mode 100644 index 000000000..6e4e25910 --- /dev/null +++ b/JS/wasm/crates/arakoo-core/src/apis/axios/mod.rs @@ -0,0 +1,57 @@ +use std::thread; +use std::time::Duration; + +use anyhow::{anyhow, Result}; +// use crate::{types::{HttpRequest, HttpResponse}, JSApiSet}; +use super::{ + APIConfig, JSApiSet, +}; +use javy::quickjs::{JSContextRef, JSValue, JSValueRef}; +use quickjs_wasm_rs::{from_qjs_value, to_qjs_value}; + +pub(super) struct Axios; + +impl JSApiSet for Axios { + fn register(&self, runtime: &javy::Runtime, _config: &APIConfig) -> Result<()> { + let context = runtime.context(); + context.eval_global("axios.js", include_str!("axios/dist/index.js"))?; + let global = context.global_object()?; + global + .set_property( + "setTimeout", + context + .wrap_callback(set_timeout_api()) + .expect("unable to get result"), + ) + .expect("unable to set property"); + + Ok(()) + } +} + +fn set_timeout_api() -> impl FnMut(&JSContextRef, JSValueRef, &[JSValueRef]) -> Result { + move |context: &JSContextRef, _this: JSValueRef, args: &[JSValueRef]| { + let callback = args.get(0).unwrap(); + let default = to_qjs_value(context, &JSValue::Int(0)).unwrap(); + let timeout = args.get(1).unwrap_or(&default); + thread::sleep(Duration::from_millis( + timeout + .as_f64() + .expect("Unable to convert timeout to milliseconds") as u64, + )); + println!("timeout reached"); + if callback.is_function() { + let mut argsvec: Vec = vec![]; + if args.len() > 2 { + for i in 2..args.len() { + argsvec.push(args.get(i).unwrap().to_owned()) + } + } + let res = callback.call( + &to_qjs_value(context.to_owned(), &JSValue::Undefined).unwrap(), + &argsvec, + ); + } + Ok(JSValue::Undefined) + } +} diff --git a/JS/wasm/crates/apis/src/console/config.rs b/JS/wasm/crates/arakoo-core/src/apis/console/config.rs similarity index 98% rename from JS/wasm/crates/apis/src/console/config.rs rename to JS/wasm/crates/arakoo-core/src/apis/console/config.rs index 3eb2152fb..1c2d05647 100644 --- a/JS/wasm/crates/apis/src/console/config.rs +++ b/JS/wasm/crates/arakoo-core/src/apis/console/config.rs @@ -1,6 +1,6 @@ use std::io::{self, Write}; -use crate::APIConfig; +use super::APIConfig; /// A selection of possible destination streams for `console.log` and /// `console.error`. diff --git a/JS/wasm/crates/apis/src/console/mod.rs b/JS/wasm/crates/arakoo-core/src/apis/console/mod.rs similarity index 66% rename from JS/wasm/crates/apis/src/console/mod.rs rename to JS/wasm/crates/arakoo-core/src/apis/console/mod.rs index eb75d3427..f596c37f9 100644 --- a/JS/wasm/crates/apis/src/console/mod.rs +++ b/JS/wasm/crates/arakoo-core/src/apis/console/mod.rs @@ -5,8 +5,9 @@ use javy::{ quickjs::{JSContextRef, JSValue, JSValueRef}, Runtime, }; +use quickjs_wasm_rs::{from_qjs_value, Exception}; -use crate::{APIConfig, JSApiSet}; +use super::{APIConfig, JSApiSet}; pub(super) use config::ConsoleConfig; pub use config::LogStream; @@ -58,11 +59,32 @@ where // will invoke a hostcall. let mut log_line = String::new(); for (i, arg) in args.iter().enumerate() { - if i != 0 { - log_line.push(' '); + if arg.is_exception() { + let exception = + Exception::from(*arg).expect("Unable to convert jsvaluleref to exception"); + let err = exception.into_error(); + log_line = format!("Exception : {:?}", err); + } else { + let val: JSValue = from_qjs_value(*arg)?; + if i != 0 { + log_line.push(' '); + } + if !arg.is_undefined() { + let proto = arg.get_property("__proto__").unwrap().to_string(); + if proto.contains("rror") { + log_line.push_str(&format!( + "__proto__ is {} Error in js evaluation : {:?}", + proto, val + )); + } else { + let line: String = log_js_value(&val); + log_line.push_str(&line); + } + } else { + let line: String = log_js_value(&val); + log_line.push_str(&line); + } } - let line = arg.to_string(); - log_line.push_str(&line); } writeln!(stream, "{log_line}")?; @@ -71,6 +93,41 @@ where } } +fn log_js_value(arg: &JSValue) -> String { + match arg { + JSValue::String(s) => s.to_string(), + JSValue::Int(n) => n.to_string(), + JSValue::Bool(b) => b.to_string(), + JSValue::Object(o) => { + let flatten_obj = o + .iter() + .map(|(k, v)| format!("{}: {}", k, log_js_value(v))) + .collect::>() + .join(", "); + format!("Object = {{ {:?} }}", flatten_obj) + } + JSValue::Null => "null".to_string(), + JSValue::Undefined => "undefined".to_string(), + JSValue::Float(f) => f.to_string(), + JSValue::Array(arr) => { + let flatten_vec = arr + .iter() + .map(|v| log_js_value(v)) + .collect::>() + .join(", "); + format!("Array = [{:?}]", flatten_vec) + } + JSValue::ArrayBuffer(buff) => { + let buffer = buff + .iter() + .map(|v| v.to_string()) + .collect::>() + .join(", "); + format!("ArrayBuffer = [{:?}]", buffer) + } + } +} + #[cfg(test)] mod tests { use anyhow::Result; @@ -79,8 +136,9 @@ mod tests { use std::rc::Rc; use std::{cmp, io}; - use crate::console::register_console; - use crate::{APIConfig, JSApiSet}; + use crate::apis::console::register_console; + + use super::{APIConfig, JSApiSet}; use super::Console; diff --git a/JS/wasm/crates/arakoo-core/src/apis/fetch/mod.rs b/JS/wasm/crates/arakoo-core/src/apis/fetch/mod.rs new file mode 100644 index 000000000..d40669d15 --- /dev/null +++ b/JS/wasm/crates/arakoo-core/src/apis/fetch/mod.rs @@ -0,0 +1,108 @@ +use anyhow::{anyhow, Result}; +use http::{request, HeaderName, HeaderValue}; +use serde_bytes::ByteBuf; +// use crate::{types::{HttpRequest, HttpResponse}, JSApiSet}; +use super::types::arakoo_http; +use super::wit; +use super::{ + types::{HttpRequest, HttpResponse}, + APIConfig, JSApiSet, +}; +use javy::quickjs::{JSContextRef, JSValue, JSValueRef}; +use quickjs_wasm_rs::{from_qjs_value, to_qjs_value, Deserializer, Serializer}; +use serde::{Deserialize, Serialize}; +use std::ops::Deref; +use std::sync::mpsc; +use std::time::Duration; +use std::{str, thread}; + +mod outbound_http; + +pub(super) struct Fetch; + +impl JSApiSet for Fetch { + fn register(&self, runtime: &javy::Runtime, _config: &APIConfig) -> Result<()> { + let context = runtime.context(); + let global = context.global_object()?; + global.set_property( + "__internal_http_send", + context.wrap_callback( + |context: &JSContextRef, _this: JSValueRef<'_>, args: &[JSValueRef<'_>]| { + send_http_request(context, &_this, args) + }, + )?, + )?; + + Ok(()) + } +} + + +fn send_http_request( + context: &JSContextRef, + _this: &JSValueRef, + args: &[JSValueRef], +) -> Result { + match args { + [request] => { + println!( + "Request recieved in send_http_request: {:?}", + from_qjs_value(*request).unwrap() + ); + let deserializer = &mut Deserializer::from(request.clone()); + let request = + HttpRequest::deserialize(deserializer).expect("Unable to deserialize request"); + + let mut builder = request::Builder::new() + .method(request.method.deref()) + .uri(request.uri.deref()); + + if let Some(headers) = builder.headers_mut() { + for (key, value) in &request.headers { + headers.insert( + HeaderName::from_bytes(key.as_bytes()) + .expect("Unable to convert key to HeaderName"), + HeaderValue::from_bytes(value.as_bytes()) + .expect("Unable to convert value to HeaderValue"), + ); + } + } + + let outbound_request = builder + .body(request.body.map(|buffer| buffer.into_vec().into())) + .expect("Unable to build request body"); + println!("outbound_request in wrap_callback: {:?}", outbound_request); + let response = outbound_http::send_request(outbound_request)?; + println!("outbound_response in wrap_callback: {:?}", response); + + let response = HttpResponse { + status: response.status().as_u16(), + headers: response + .headers() + .iter() + .map(|(key, value)| { + Ok(( + key.as_str().to_owned(), + str::from_utf8(value.as_bytes())?.to_owned(), + )) + }) + .collect::>()?, + body: response + .clone() + .into_body() + .map(|bytes| ByteBuf::from(bytes.deref())), + status_text: response + .status() + .canonical_reason() + .unwrap_or("") + .to_owned(), + }; + + let mut serializer = Serializer::from_context(context)?; + response.serialize(&mut serializer)?; + Ok(from_qjs_value(serializer.value)?) + } + + _ => Err(anyhow!("expected 1 argument, got {}", args.len())), + } +} diff --git a/JS/wasm/crates/arakoo-core/src/apis/fetch/outbound_http.rs b/JS/wasm/crates/arakoo-core/src/apis/fetch/outbound_http.rs new file mode 100644 index 000000000..e2405ff0b --- /dev/null +++ b/JS/wasm/crates/arakoo-core/src/apis/fetch/outbound_http.rs @@ -0,0 +1,83 @@ +use std::ops::Deref; + +use crate::wit::Method; + +use super::arakoo_http::{Request, Response}; +use super::wit::edgechains::http::{Request as OutboundRequest, Response as OutboundResponse}; +use super::wit::edgechains::http_types::HttpError as OutboundHttpError; +use super::wit::edgechains::http::send_request as outbound_send_request; +use http::{header::HeaderName, HeaderValue}; + + +pub fn send_request(req: Request) -> Result { + let (req, body) = req.into_parts(); + + let method = req.method.try_into()?; + + let uri = req.uri.to_string(); + + let params = vec![]; + + let headers = req + .headers + .iter() + .map(try_header_to_strs) + .collect::, OutboundHttpError>>()?; + + let body = body.as_ref().map(|bytes| bytes.as_ref()); + + let out_req = OutboundRequest { + method, + uri, + params, + headers, + body:Some(body.unwrap().to_vec()), + }; + + let OutboundResponse { + status, + headers, + body, + status_text, + } = outbound_send_request(&out_req)?; + + let resp_builder = http::response::Builder::new().status(status); + let resp_builder = headers + .into_iter() + .flatten() + .fold(resp_builder, |b, (k, v)| b.header(k, v)); + resp_builder + .body(body.map(Into::into)) + .map_err(|_| OutboundHttpError::RuntimeError) +} + +fn try_header_to_strs<'k, 'v>( + header: (&'k HeaderName, &'v HeaderValue), +) -> Result<(String, String), OutboundHttpError> { + Ok(( + header.0.as_str().to_string(), + header + .1 + .to_str() + .map_err(|_| OutboundHttpError::InvalidUrl)?.to_string(), + )) +} + +impl TryFrom for Method { + type Error = OutboundHttpError; + + fn try_from(method: http::Method) -> Result { + use http::Method; + use super::wit::Method::*; + Ok(match method { + Method::GET => Get, + Method::POST => Post, + Method::PUT => Put, + Method::DELETE => Delete, + Method::PATCH => Patch, + Method::HEAD => Head, + Method::OPTIONS => Options, + _ => return Err(super::wit::edgechains::http_types::HttpError::RequestError), + }) + } +} diff --git a/JS/wasm/crates/arakoo-core/src/apis/http/mod.rs b/JS/wasm/crates/arakoo-core/src/apis/http/mod.rs new file mode 100644 index 000000000..4cd7ee999 --- /dev/null +++ b/JS/wasm/crates/arakoo-core/src/apis/http/mod.rs @@ -0,0 +1,15 @@ +use anyhow::Result; +use super::JSApiSet; + +// use crate::{fetch, get_response, get_response_len, http::types::Request, JSApiSet}; + + +pub(super) struct Http; + +impl JSApiSet for Http { + fn register(&self, runtime: &javy::Runtime, _config: &super::APIConfig) -> Result<()> { + let context = runtime.context(); + context.eval_global("http.js", include_str!("shims/dist/index.js"))?; + Ok(()) + } +} diff --git a/JS/wasm/crates/apis/src/http/shims/.gitignore b/JS/wasm/crates/arakoo-core/src/apis/http/shims/.gitignore similarity index 100% rename from JS/wasm/crates/apis/src/http/shims/.gitignore rename to JS/wasm/crates/arakoo-core/src/apis/http/shims/.gitignore diff --git a/JS/wasm/crates/apis/src/http/shims/build.js b/JS/wasm/crates/arakoo-core/src/apis/http/shims/build.js similarity index 100% rename from JS/wasm/crates/apis/src/http/shims/build.js rename to JS/wasm/crates/arakoo-core/src/apis/http/shims/build.js diff --git a/JS/wasm/crates/apis/src/http/shims/index.js b/JS/wasm/crates/arakoo-core/src/apis/http/shims/index.js similarity index 56% rename from JS/wasm/crates/apis/src/http/shims/index.js rename to JS/wasm/crates/arakoo-core/src/apis/http/shims/index.js index e9d2919a2..0a7326a2d 100644 --- a/JS/wasm/crates/apis/src/http/shims/index.js +++ b/JS/wasm/crates/arakoo-core/src/apis/http/shims/index.js @@ -1,7 +1,13 @@ -import { TextEncoder, TextDecoder } from "@sinonjs/text-encoding"; import httpStatus from "http-status"; import Url from "url-parse"; import _queryString from "query-string"; +import "fast-text-encoding" + +let __require_map = {} + +// let encoder = new TextEncoder() +let decoder = new TextDecoder() + class URL { constructor(urlStr, base = undefined) { @@ -161,10 +167,28 @@ class Headers { } class Request { - constructor(input) { - this.url = input.url; + // constructor(input) { + // // console.log("In constructor of request input body len",input.body.byteLength); + // this.url = input.uri; + // this.method = input.method; + // this.headers = new Headers(input.headers || {}); + // let bodyArray = new Uint8Array(input.body); + // let bodyString = decoder.decode(bodyArray); + // if (bodyString != undefined && bodyString.length > 0) + // this.body = JSON.parse(bodyString); + // this.params = input.params || {}; + // this.geo = input.geo || {}; + // } + + constructor(url, input) { + console.log("In constructor of request , url = ",url, " input = ",input); + if (typeof url === "string") { + this.url = url + } else { + throw new Error("url in Request constructor is not a string") + } + this.headers = input.headers; this.method = input.method; - this.headers = new Headers(input.headers || {}); this.body = input.body; this.params = input.params || {}; this.geo = input.geo || {}; @@ -262,47 +286,120 @@ class Response { return this.body; } } -let handlerFunction; -globalThis.addEventListener = (_eventName, handler) => { - handlerFunction = handler; -}; +// let handlerFunction; +// globalThis.addEventListener = (_eventName, handler) => { +// handlerFunction = handler; +// }; + +// const requestToHandler = (input) => { +// const request = new Request(input); +// const event = { +// request, +// response: {}, +// respondWith(res) { +// this.response = res; +// }, +// }; + +// try { +// handlerFunction(event); + +// Promise.resolve(event.response) +// .then((res) => { +// console.log("res: ", res); +// result = { +// body: res.body, +// headers: res.headers.headers, +// status: res.status, +// statusText: res.statusText, +// }; +// }) +// .catch((err) => { +// error = `err: \n${err}`; +// }); +// } catch (err) { +// error = `err: ${err}\n${err.stack}`; +// } +// }; + +// globalThis.entrypoint = requestToHandler; +// globalThis.result = {}; +// globalThis.error = null; + +// globalThis.fetch = async (resource, options = { method: "GET" }) => { +// let response = await fetch_internal(resource, options); +// return Promise.resolve(new Response(response.body, response)); +// }; + + +function encodeBody(body) { + if (typeof (body) == "string") { + return encoder.encode(body).buffer + } else if (ArrayBuffer.isView(body)) { + return body.buffer + } else { + return body + } +} -const requestToHandler = (input) => { - const request = new Request(input); +globalThis.requestToEvent = (inputReq) => { + const request = new Request(inputReq.uri, inputReq); const event = { request, response: {}, respondWith(res) { + console.log("Response recieved ", res); this.response = res; }, }; + console.log("event: ", JSON.stringify(event)) + return event; +} +function fetch(uri, options) { + console.log("constructor name of uri ", uri.constructor.name); + console.log("uri is ", JSON.stringify(uri)) + if (uri.constructor.name == "Request") { + console.log("uri is instance of Request") + options = {} + options.headers = uri.headers; + options.method = uri.method; + options.params = uri.params; + options.geo = uri.geo; + options.body = uri.body; + uri = uri.url; + } + console.log("In fetch function", uri, options) + let encodedBodyData = (options && options.body) ? encodeBody(options.body) : new Uint8Array().buffer + const { status, headers, body } = __internal_http_send({ + method: (options && options.method) || "GET", + uri: (uri instanceof URL) ? uri.toString() : uri, + headers: (options && options.headers) || {}, + body: encodedBodyData, + params: (options && options.params) || {}, + }) + console.log("Response from fetch", status, headers, body) + let obj; try { - handlerFunction(event); - - Promise.resolve(event.response) - .then((res) => { - console.log("res: ", res); - result = { - body: res.body, - headers: res.headers.headers, - status: res.status, - statusText: res.statusText, - }; - }) - .catch((err) => { - error = `err: \n${err}`; - }); - } catch (err) { - error = `err: ${err}\n${err.stack}`; - } -}; - -globalThis.entrypoint = requestToHandler; -globalThis.result = {}; -globalThis.error = null; - -globalThis.fetch = async (resource, options = { method: "GET" }) => { - let response = await fetch_internal(resource, options); - return Promise.resolve(new Response(response.body, response)); -}; + obj = { + status, + headers: { + entries: () => Object.entries(headers || {}), + get: (key) => (headers && headers[key]) || null, + has: (key) => (headers && headers[key]) ? true : false + }, + arrayBuffer: () => Promise.resolve(body), + ok: (status > 199 && status < 300), + statusText: httpStatus[status], + text: () => Promise.resolve(new TextDecoder().decode(body || new Uint8Array())), + json: () => { + let text = new TextDecoder().decode(body || new Uint8Array()) + return Promise.resolve(JSON.parse(text)) + } + } + } catch (error) { + console.log("Error occured in sending response from fetch") + console.log(error) + } + return Promise.resolve(obj); +} \ No newline at end of file diff --git a/JS/wasm/crates/apis/src/http/shims/package.json b/JS/wasm/crates/arakoo-core/src/apis/http/shims/package.json similarity index 91% rename from JS/wasm/crates/apis/src/http/shims/package.json rename to JS/wasm/crates/arakoo-core/src/apis/http/shims/package.json index 732dbd3fc..a4166608e 100644 --- a/JS/wasm/crates/apis/src/http/shims/package.json +++ b/JS/wasm/crates/arakoo-core/src/apis/http/shims/package.json @@ -11,6 +11,7 @@ "dependencies": { "@sinonjs/text-encoding": "^0.7.2", "esbuild": "^0.20.0", + "fast-text-encoding": "^1.0.6", "http-status": "^1.7.3", "query-string": "^8.1.0", "url-parse": "^1.5.10" diff --git a/JS/wasm/crates/arakoo-core/src/apis/jsonnet/mod.rs b/JS/wasm/crates/arakoo-core/src/apis/jsonnet/mod.rs new file mode 100644 index 000000000..6b1474b34 --- /dev/null +++ b/JS/wasm/crates/arakoo-core/src/apis/jsonnet/mod.rs @@ -0,0 +1,99 @@ +use super::{wit::edgechains, APIConfig, JSApiSet}; +use javy::quickjs::{JSContextRef, JSValue, JSValueRef}; + +pub(super) struct Jsonnet; + +impl JSApiSet for Jsonnet { + fn register(&self, runtime: &javy::Runtime, _config: &APIConfig) -> anyhow::Result<()> { + let context = runtime.context(); + let global = context.global_object()?; + + global.set_property( + "__jsonnet_make", + context.wrap_callback(jsonnet_make_closure())?, + )?; + global.set_property( + "__jsonnet_ext_string", + context.wrap_callback(jsonnet_ext_string_closure())?, + )?; + global.set_property( + "__jsonnet_evaluate_snippet", + context.wrap_callback(jsonnet_evaluate_snippet_closure())?, + )?; + global.set_property( + "__jsonnet_evaluate_file", + context.wrap_callback(jsonnet_evaluate_file_closure())?, + )?; + global.set_property( + "__jsonnet_destroy", + context.wrap_callback(jsonnet_destroy_closure())?, + )?; + Ok(()) + } +} + +fn jsonnet_make_closure( +) -> impl FnMut(&JSContextRef, JSValueRef, &[JSValueRef]) -> anyhow::Result { + move |_ctx, _this, args| Ok(JSValue::Float(edgechains::jsonnet::jsonnet_make() as f64)) +} + +fn jsonnet_ext_string_closure( +) -> impl FnMut(&JSContextRef, JSValueRef, &[JSValueRef]) -> anyhow::Result { + move |_ctx, _this, args| { + // check the number of arguments + if args.len() != 3 { + return Err(anyhow::anyhow!( + "Expected 2 arguments, got {}", + args.len() - 1 + )); + } + let vm = args.get(0).unwrap().as_f64().unwrap(); + let key = args.get(1).unwrap().to_string(); + let value = args.get(2).unwrap().to_string(); + edgechains::jsonnet::jsonnet_ext_string(vm as u64, &key, &value); + Ok(JSValue::Undefined) + } +} + +fn jsonnet_evaluate_snippet_closure( +) -> impl FnMut(&JSContextRef, JSValueRef, &[JSValueRef]) -> anyhow::Result { + move |_ctx, _this, args| { + // check the number of arguments + if args.len() != 2 { + return Err(anyhow::anyhow!("Expected 2 arguments, got {}", args.len())); + } + let vm = args.get(0).unwrap().as_f64().unwrap(); + let code = args.get(1).unwrap().to_string(); + let code = code.as_str(); + let out = edgechains::jsonnet::jsonnet_evaluate_snippet(vm as u64, "snippet", code); + Ok(out.into()) + } +} + +fn jsonnet_evaluate_file_closure( +) -> impl FnMut(&JSContextRef, JSValueRef, &[JSValueRef]) -> anyhow::Result { + move |_ctx, _this, args| { + // check the number of arguments + if args.len() != 2 { + return Err(anyhow::anyhow!("Expected 2 arguments, got {}", args.len())); + } + let vm = args.get(0).unwrap().as_f64().unwrap(); + let path = args.get(1).unwrap().to_string(); + let path = path.as_str(); + let out = edgechains::jsonnet::jsonnet_evaluate_file(vm as u64, path); + Ok(out.into()) + } +} + +fn jsonnet_destroy_closure( +) -> impl FnMut(&JSContextRef, JSValueRef, &[JSValueRef]) -> anyhow::Result { + move |_ctx, _this, args| { + // check the number of arguments + if args.len() != 1 { + return Err(anyhow::anyhow!("Expected 1 arguments, got {}", args.len())); + } + let vm = args.get(0).unwrap().as_f64().unwrap(); + edgechains::jsonnet::jsonnet_destroy(vm as u64); + Ok(JSValue::Undefined) + } +} diff --git a/JS/wasm/crates/apis/src/lib.rs b/JS/wasm/crates/arakoo-core/src/apis/mod.rs similarity index 79% rename from JS/wasm/crates/apis/src/lib.rs rename to JS/wasm/crates/arakoo-core/src/apis/mod.rs index 357a34106..ccaacdd06 100644 --- a/JS/wasm/crates/apis/src/lib.rs +++ b/JS/wasm/crates/arakoo-core/src/apis/mod.rs @@ -44,39 +44,25 @@ use anyhow::Result; use javy::Runtime; +use super::wit; pub use api_config::APIConfig; -#[cfg(feature = "console")] pub use console::LogStream; pub use runtime_ext::RuntimeExt; +pub mod http; +pub mod types; + mod api_config; -#[cfg(feature = "console")] mod console; -#[cfg(feature = "random")] mod random; mod runtime_ext; -#[cfg(feature = "stream_io")] mod stream_io; -#[cfg(feature = "text_encoding")] mod text_encoding; - -pub mod http; -mod jsonnet; - -#[link(wasm_import_module = "arakoo")] -extern "C" { - fn jsonnet_evaluate(var_ptr: *const u8, var_len: i32, code_ptr: *const u8, code_len: i32); - fn jsonnet_evaluate_file(var_ptr: *const u8, var_len: i32, path_ptr: *const u8, path_len: i32); - fn jsonnet_output_len() -> i32; - fn jsonnet_output(ptr: *mut u8); - fn fetch(request_pointer: *const u8, request_len: i32); - fn get_response_len() -> i32; - fn get_response(ptr: *mut u8); -} - mod pdfparse; - +mod fetch; +mod jsonnet; +mod axios; pub(crate) trait JSApiSet { fn register(&self, runtime: &Runtime, config: &APIConfig) -> Result<()>; } @@ -93,16 +79,14 @@ pub(crate) trait JSApiSet { /// # Ok::<(), Error>(()) /// ``` pub fn add_to_runtime(runtime: &Runtime, config: APIConfig) -> Result<()> { - #[cfg(feature = "console")] console::Console::new().register(runtime, &config)?; - #[cfg(feature = "random")] random::Random.register(runtime, &config)?; - #[cfg(feature = "stream_io")] stream_io::StreamIO.register(runtime, &config)?; - #[cfg(feature = "text_encoding")] text_encoding::TextEncoding.register(runtime, &config)?; http::Http.register(runtime, &config)?; jsonnet::Jsonnet.register(runtime, &config)?; pdfparse::PDFPARSER.register(runtime, &config)?; + fetch::Fetch.register(runtime, &config)?; + axios::Axios.register(runtime, &config)?; Ok(()) } diff --git a/JS/wasm/crates/apis/src/pdfparse/mod.rs b/JS/wasm/crates/arakoo-core/src/apis/pdfparse/mod.rs similarity index 97% rename from JS/wasm/crates/apis/src/pdfparse/mod.rs rename to JS/wasm/crates/arakoo-core/src/apis/pdfparse/mod.rs index 6840fd0c1..7ff29f852 100644 --- a/JS/wasm/crates/apis/src/pdfparse/mod.rs +++ b/JS/wasm/crates/arakoo-core/src/apis/pdfparse/mod.rs @@ -1,4 +1,4 @@ -use crate::{APIConfig, JSApiSet}; +use super::{APIConfig, JSApiSet}; use anyhow::Result; use javy::{ quickjs::{from_qjs_value, to_qjs_value, JSContextRef, JSValue, JSValueRef}, diff --git a/JS/wasm/crates/apis/src/random/mod.rs b/JS/wasm/crates/arakoo-core/src/apis/random/mod.rs similarity index 89% rename from JS/wasm/crates/apis/src/random/mod.rs rename to JS/wasm/crates/arakoo-core/src/apis/random/mod.rs index e7bbb5708..65346115e 100644 --- a/JS/wasm/crates/apis/src/random/mod.rs +++ b/JS/wasm/crates/arakoo-core/src/apis/random/mod.rs @@ -1,7 +1,7 @@ use anyhow::Result; use javy::{quickjs::JSValue, Runtime}; -use crate::{APIConfig, JSApiSet}; +use super::{APIConfig, JSApiSet}; pub struct Random; @@ -19,7 +19,9 @@ impl JSApiSet for Random { #[cfg(test)] mod tests { - use crate::{random::Random, APIConfig, JSApiSet}; + use crate::apis::random::Random; + + use super::{ APIConfig, JSApiSet}; use anyhow::Result; use javy::Runtime; diff --git a/JS/wasm/crates/apis/src/runtime_ext.rs b/JS/wasm/crates/arakoo-core/src/apis/runtime_ext.rs similarity index 93% rename from JS/wasm/crates/apis/src/runtime_ext.rs rename to JS/wasm/crates/arakoo-core/src/apis/runtime_ext.rs index d64915741..703767685 100644 --- a/JS/wasm/crates/apis/src/runtime_ext.rs +++ b/JS/wasm/crates/arakoo-core/src/apis/runtime_ext.rs @@ -1,7 +1,7 @@ use anyhow::Result; use javy::{Config, Runtime}; -use crate::APIConfig; +use super::APIConfig; /// A extension trait for [`Runtime`] that creates a [`Runtime`] with APIs /// provided in this crate. @@ -25,7 +25,7 @@ pub trait RuntimeExt { impl RuntimeExt for Runtime { fn new_with_apis(config: Config, api_config: APIConfig) -> Result { let runtime = Runtime::new(config)?; - crate::add_to_runtime(&runtime, api_config)?; + super::add_to_runtime(&runtime, api_config)?; Ok(runtime) } diff --git a/JS/wasm/crates/apis/src/stream_io/io.js b/JS/wasm/crates/arakoo-core/src/apis/stream_io/io.js similarity index 100% rename from JS/wasm/crates/apis/src/stream_io/io.js rename to JS/wasm/crates/arakoo-core/src/apis/stream_io/io.js diff --git a/JS/wasm/crates/apis/src/stream_io/mod.rs b/JS/wasm/crates/arakoo-core/src/apis/stream_io/mod.rs similarity index 98% rename from JS/wasm/crates/apis/src/stream_io/mod.rs rename to JS/wasm/crates/arakoo-core/src/apis/stream_io/mod.rs index abdf12176..d6afd6726 100644 --- a/JS/wasm/crates/apis/src/stream_io/mod.rs +++ b/JS/wasm/crates/arakoo-core/src/apis/stream_io/mod.rs @@ -3,7 +3,7 @@ use std::io::{Read, Write}; use javy::Runtime; -use crate::{APIConfig, JSApiSet}; +use super::{APIConfig, JSApiSet}; pub(super) struct StreamIO; diff --git a/JS/wasm/crates/apis/src/text_encoding/mod.rs b/JS/wasm/crates/arakoo-core/src/apis/text_encoding/mod.rs similarity index 97% rename from JS/wasm/crates/apis/src/text_encoding/mod.rs rename to JS/wasm/crates/arakoo-core/src/apis/text_encoding/mod.rs index 7d8007647..ae595b1e3 100644 --- a/JS/wasm/crates/apis/src/text_encoding/mod.rs +++ b/JS/wasm/crates/arakoo-core/src/apis/text_encoding/mod.rs @@ -6,7 +6,7 @@ use javy::{ Runtime, }; -use crate::{APIConfig, JSApiSet}; +use super::{APIConfig, JSApiSet}; pub(super) struct TextEncoding; @@ -81,7 +81,7 @@ fn encode_js_string_to_utf8_buffer( #[cfg(test)] mod tests { - use crate::{APIConfig, JSApiSet}; + use super::{APIConfig, JSApiSet}; use anyhow::Result; use javy::Runtime; diff --git a/JS/wasm/crates/apis/src/text_encoding/text-encoding.js b/JS/wasm/crates/arakoo-core/src/apis/text_encoding/text-encoding.js similarity index 100% rename from JS/wasm/crates/apis/src/text_encoding/text-encoding.js rename to JS/wasm/crates/arakoo-core/src/apis/text_encoding/text-encoding.js diff --git a/JS/wasm/crates/arakoo-core/src/apis/types.rs b/JS/wasm/crates/arakoo-core/src/apis/types.rs new file mode 100644 index 000000000..3a9dc761e --- /dev/null +++ b/JS/wasm/crates/arakoo-core/src/apis/types.rs @@ -0,0 +1,48 @@ +use std::collections::HashMap; + +use serde::{Deserialize, Serialize}; +use serde_bytes::ByteBuf; + +#[derive(Serialize, Deserialize, Debug)] +pub struct HttpRequest { + pub method: String, + pub uri: String, + #[serde(default)] + pub headers: HashMap, + pub params: HashMap, + pub body: Option, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct HttpResponse { + pub status: u16, + #[serde(default)] + pub headers: HashMap, + pub body: Option, + pub status_text: String, + +} + +pub mod arakoo_http { + use anyhow::Result; + + /// The Arakoo HTTP request. + pub type Request = http::Request>; + + /// The Arakoo HTTP response. + pub type Response = http::Response>; + + /// Helper function to return a 404 Not Found response. + pub fn not_found() -> Result { + Ok(http::Response::builder() + .status(404) + .body(Some("Not Found".into()))?) + } + + /// Helper function to return a 500 Internal Server Error response. + pub fn internal_server_error() -> Result { + Ok(http::Response::builder() + .status(500) + .body(Some("Internal Server Error".into()))?) + } +} \ No newline at end of file diff --git a/JS/wasm/crates/arakoo-core/src/execution.rs b/JS/wasm/crates/arakoo-core/src/execution.rs index d03da0752..c79e7407a 100644 --- a/JS/wasm/crates/arakoo-core/src/execution.rs +++ b/JS/wasm/crates/arakoo-core/src/execution.rs @@ -1,86 +1,86 @@ -use std::process; +// use std::process; -use anyhow::{bail, Error, Result}; -use apis::http::types::Response; -use javy::{json, quickjs::JSContextRef, Runtime}; +// use anyhow::{bail, Error, Result}; +// use apis::http::types::Response; +// use javy::{json, quickjs::JSContextRef, Runtime}; -pub fn run_bytecode(runtime: &Runtime, bytecode: &[u8]) { - let context = runtime.context(); +// pub fn run_bytecode(runtime: &Runtime, bytecode: &[u8]) { +// let context = runtime.context(); - context - .eval_binary(bytecode) - .and_then(|_| process_event_loop(context)) - .unwrap_or_else(handle_error); -} +// context +// .eval_binary(bytecode) +// .and_then(|_| process_event_loop(context)) +// .unwrap_or_else(handle_error); +// } -#[allow(dead_code)] -pub fn invoke_entrypoint( - runtime: &Runtime, - bytecode: &[u8], - input: String, -) -> anyhow::Result { - let context = runtime.context(); +// #[allow(dead_code)] +// pub fn invoke_entrypoint( +// runtime: &Runtime, +// bytecode: &[u8], +// input: String, +// ) -> anyhow::Result { +// let context = runtime.context(); - match context.eval_binary(bytecode) { - Ok(_) => {} - Err(e) => { - eprintln!("error"); - eprintln!("Error while running bytecode: {e}"); - return Err(e); - } - } - let global = context.global_object().unwrap(); - let entry_point = global.get_property("entrypoint").unwrap(); +// match context.eval_binary(bytecode) { +// Ok(_) => {} +// Err(e) => { +// eprintln!("error"); +// eprintln!("Error while running bytecode: {e}"); +// return Err(e); +// } +// } +// let global = context.global_object().unwrap(); +// let entry_point = global.get_property("entrypoint").unwrap(); - let request = input; - let input_bytes = request.as_bytes(); - let input_value = json::transcode_input(context, input_bytes).unwrap_or_else(|e| { - eprintln!("Error when transcoding input: {e}"); - process::abort(); - }); - entry_point - .call(&global, &[input_value]) - .and_then(|_| process_event_loop(context)) - .unwrap_or_else(handle_error); +// let request = input; +// let input_bytes = request.as_bytes(); +// let input_value = json::transcode_input(context, input_bytes).unwrap_or_else(|e| { +// eprintln!("Error when transcoding input: {e}"); +// process::abort(); +// }); +// entry_point +// .call(&global, &[input_value]) +// .and_then(|_| process_event_loop(context)) +// .unwrap_or_else(handle_error); - let global = context.global_object().unwrap(); +// let global = context.global_object().unwrap(); - let error = global.get_property("error").unwrap(); - let output = global.get_property("result").unwrap(); +// let error = global.get_property("error").unwrap(); +// let output = global.get_property("result").unwrap(); - if !error.is_null_or_undefined() { - let error = error.to_string(); - eprintln!("Error while running JS: {error}"); - process::abort(); - } - let output = json::transcode_output(output).unwrap(); - let response: Response = serde_json::from_slice(&output).unwrap(); - Ok(response) -} +// if !error.is_null_or_undefined() { +// let error = error.to_string(); +// eprintln!("Error while running JS: {error}"); +// process::abort(); +// } +// let output = json::transcode_output(output).unwrap(); +// let response: Response = serde_json::from_slice(&output).unwrap(); +// Ok(response) +// } -pub fn invoke_function(runtime: &Runtime, fn_module: &str, fn_name: &str) { - let context = runtime.context(); - let js = if fn_name == "default" { - format!("import {{ default as defaultFn }} from '{fn_module}'; defaultFn();") - } else { - format!("import {{ {fn_name} }} from '{fn_module}'; {fn_name}();") - }; - context - .eval_module("runtime.mjs", &js) - .and_then(|_| process_event_loop(context)) - .unwrap_or_else(handle_error); -} +// pub fn invoke_function(runtime: &Runtime, fn_module: &str, fn_name: &str) { +// let context = runtime.context(); +// let js = if fn_name == "default" { +// format!("import {{ default as defaultFn }} from '{fn_module}'; defaultFn();") +// } else { +// format!("import {{ {fn_name} }} from '{fn_module}'; {fn_name}();") +// }; +// context +// .eval_module("runtime.mjs", &js) +// .and_then(|_| process_event_loop(context)) +// .unwrap_or_else(handle_error); +// } -fn process_event_loop(context: &JSContextRef) -> Result<()> { - if cfg!(feature = "experimental_event_loop") { - context.execute_pending()?; - } else if context.is_pending() { - bail!("Adding tasks to the event queue is not supported"); - } - Ok(()) -} +// fn process_event_loop(context: &JSContextRef) -> Result<()> { +// if cfg!(feature = "experimental_event_loop") { +// context.execute_pending()?; +// } else if context.is_pending() { +// bail!("Adding tasks to the event queue is not supported"); +// } +// Ok(()) +// } -fn handle_error(e: Error) { - eprintln!("Error while running JS: {e}"); - process::abort(); -} +// fn handle_error(e: Error) { +// eprintln!("Error while running JS: {e}"); +// process::abort(); +// } diff --git a/JS/wasm/crates/arakoo-core/src/lib.rs b/JS/wasm/crates/arakoo-core/src/lib.rs index 567aed64d..770939e77 100644 --- a/JS/wasm/crates/arakoo-core/src/lib.rs +++ b/JS/wasm/crates/arakoo-core/src/lib.rs @@ -1,21 +1,241 @@ +use anyhow::anyhow; +use anyhow::Result; +use javy::quickjs::from_qjs_value; +use javy::quickjs::to_qjs_value; +use javy::quickjs::JSContextRef; +use javy::quickjs::JSValue; +use javy::quickjs::JSValueRef; use javy::Runtime; use once_cell::sync::OnceCell; -use std::slice; -use std::str; +use send_wrapper::SendWrapper; +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; +use std::io; +use std::io::Read; +use std::ops::Deref; +use std::sync::Mutex; -mod execution; +use serde_bytes::ByteBuf; + +use crate::apis::types::HttpRequest; + +pub mod wit { + use wit_bindgen::generate; + + generate!({ + path:"../../wit", + world:"reactor", + }); + + use super::Guest; + export!(Guest); + + pub use self::arakoo::edgechains::http_types::{Method, Request, Response}; + pub use self::exports::arakoo::edgechains::inbound_http; + pub use self::arakoo::edgechains; +} + + +struct Guest; + +// mod execution; mod runtime; +mod apis; + +// const FUNCTION_MODULE_NAME: &str = "function.mjs"; -const FUNCTION_MODULE_NAME: &str = "function.mjs"; +// static mut COMPILE_SRC_RET_AREA: [u32; 2] = [0; 2]; +// static mut RUNTIME: OnceCell = OnceCell::new(); +static CONTEXT: OnceCell> = OnceCell::new(); +static HANDLER: OnceCell> = OnceCell::new(); +static GLOBAL: OnceCell> = OnceCell::new(); +static mut RUNTIME_INSTANCE: Option = None; +static ON_RESOLVE: OnceCell> = OnceCell::new(); +static ON_REJECT: OnceCell> = OnceCell::new(); +static RESPONSE: Mutex> = Mutex::new(None); +static EXCEPTION: Mutex> = Mutex::new(None); -static mut COMPILE_SRC_RET_AREA: [u32; 2] = [0; 2]; -static mut RUNTIME: OnceCell = OnceCell::new(); +fn on_resolve(context: &JSContextRef, _this: JSValueRef, args: &[JSValueRef]) -> Result { + // (*args).clone_into(&mut cloned_args); + let mut qjs_value = Option::None; + if args.len() > 0 { + for arg in args { + qjs_value = Some(from_qjs_value(*arg).unwrap()); + // println!("Arg resolve: {:?}", qjs_value.as_ref().unwrap()); + } + RESPONSE.lock().unwrap().replace(qjs_value.unwrap()); + Ok(JSValue::Undefined) + } else { + Err(anyhow!("expected 1 argument, got {}", args.len())) + } +} + +fn on_reject(context: &JSContextRef, _this: JSValueRef, args: &[JSValueRef]) -> Result { + // (*args).clone_into(&mut cloned_args); + let mut qjs_value = Option::None; + if (args.len() > 0) { + for arg in args { + qjs_value = Some(from_qjs_value(*arg).unwrap()); + println!("Arg reject : {:?}", qjs_value.as_ref().unwrap()); + } + EXCEPTION.lock().unwrap().replace(qjs_value.unwrap()); + Ok(JSValue::Undefined) + } else { + Err(anyhow!("expected 1 argument, got {}", args.len())) + } +} /// Used by Wizer to preinitialize the module #[export_name = "wizer.initialize"] pub extern "C" fn init() { - let runtime = runtime::new_runtime().unwrap(); - unsafe { RUNTIME.set(runtime).unwrap() }; + let mut contents = String::new(); + let res = io::stdin().read_to_string(&mut contents); + match res { + Ok(len) => println!("Read {} bytes", len), + Err(err) => println!( + "Error : no input file specified or corrupted input js file supplied \n{}", + err + ), + } + // println!("Contents : {}",contents); + unsafe { + if RUNTIME_INSTANCE.is_none() { + RUNTIME_INSTANCE = Some(runtime::new_runtime().unwrap()); + } + } + let runtime = unsafe { RUNTIME_INSTANCE.as_ref().unwrap() }; + let context = runtime.context(); + CONTEXT.set(SendWrapper::new(context)).unwrap(); + match context.eval_global("javascriptCode.js", &contents) { + Ok(_) => (), + Err(err) => println!("Error in evaluating script function.js : {:?}", err), + }; + + let global = context + .global_object() + .expect("Unable to get global object"); + GLOBAL.set(SendWrapper::new(global)).unwrap(); + + let hono = global.get_property("_export").unwrap(); + let handle_event = hono.get_property("fetch").expect("Hono app not exported"); + HANDLER.set(SendWrapper::new(handle_event)).unwrap(); + + let on_resolve = context.wrap_callback(on_resolve).unwrap(); + ON_RESOLVE.set(SendWrapper::new(on_resolve)).unwrap(); + let on_reject = context.wrap_callback(on_reject).unwrap(); + ON_REJECT.set(SendWrapper::new(on_reject)).unwrap(); +} + +impl wit::inbound_http::Guest for Guest { + fn handle_request(req: wit::Request) -> wit::Response { + println!("{:?}", req); + let context = **CONTEXT.get().unwrap(); + let mut serializer = + javy::quickjs::Serializer::from_context(context).expect("Unable to create serializer"); + let handler = **HANDLER.get().unwrap(); + let request = HttpRequest { + method: match req.method { + wit::Method::Get => "GET".to_string(), + wit::Method::Post => "POST".to_string(), + wit::Method::Put => "PUT".to_string(), + wit::Method::Delete => "DELETE".to_string(), + wit::Method::Patch => "PATCH".to_string(), + wit::Method::Head => "HEAD".to_string(), + wit::Method::Options => "OPTIONS".to_string(), + }, + uri: req.uri, + headers: req + .headers + .iter() + .map(|(k, v)| Ok((k.as_str().to_owned(), v.as_str().to_owned()))) + .collect::>>() + .unwrap(), + params: req + .params + .iter() + .map(|(k, v)| Ok((k.as_str().to_owned(), v.as_str().to_owned()))) + .collect::>>() + .unwrap(), + body: req.body.map(|bytes| ByteBuf::from::>(bytes)), + }; + // let hono_event = + // hono_event.serialize(&mut serializer).unwrap(); + request + .serialize(&mut serializer) + .expect("unable to serialize httprequest"); + let request_value = serializer.value; + // println!("body of httpRequest : {:?}", from_qjs_value(request_value).unwrap()); + let global = GLOBAL.get().unwrap() ; + let request_to_event = global + .get_property("requestToEvent") + .expect("Unable to get requestToEvent"); + let event = request_to_event + .call(global, &[request_value]) + .expect("Unable to call requestToEvent"); + let event_request = event + .get_property("request") + .expect("Unable to get request from event"); + let promise = handler + .call(global, &[event_request, event]) + .expect("Unable to call handler"); + + let on_resolve = ON_RESOLVE.get().unwrap().clone() ; + let on_reject = ON_REJECT.get().unwrap().clone() ; + let then_func = promise.get_property("then").unwrap(); + if then_func.is_function() { + then_func + .call( + &promise, + &[on_resolve.deref().clone(), on_reject.deref().clone()], + ) + .unwrap(); + } else { + RESPONSE + .lock() + .unwrap() + .replace(from_qjs_value(promise).unwrap()); + } + + context + .execute_pending() + .expect("Unable to execute pending tasks"); + + // let response = to_qjs_value(context, &RESPONSE.lock().unwrap().take().unwrap()).unwrap(); + let response = RESPONSE.lock().unwrap().take().unwrap(); + // println!("Response {:?}",response); + + // let deserializer = &mut Deserializer::from(response); + // let response = HttpResponse::deserialize(deserializer).unwrap(); + // println!("Http Response {:?}", response); + if let JSValue::Object(obj) = response { + let status_code_ref = to_qjs_value(context, obj.get("status").unwrap()).unwrap(); + let status_code = status_code_ref.as_i32_unchecked(); + let status_text_ref = to_qjs_value(context, obj.get("statusText").unwrap()).unwrap(); + let status_text = status_text_ref.as_str().unwrap(); + let body_ref = to_qjs_value(context, obj.get("body").unwrap()).unwrap(); + let headers = obj.get("headers").unwrap(); + let mut headers_vec = Vec::new(); + let headers_obj = match headers { + JSValue::Object(obj) => obj, + _ => panic!("Headers is not object {:?}", headers), + }; + if let JSValue::Object(headers_obj) = headers_obj.get("headers").unwrap() { + for (k, v) in headers_obj.iter() { + let key = k.clone(); + let value = (*v).to_string(); + headers_vec.push((key.to_string(), value.to_string())); + } + } + wit::Response { + status: status_code as u16, + headers: Some(headers_vec), + body: Some(body_ref.as_str().unwrap().as_bytes().to_vec()), + status_text: status_text.to_string(), + } + } else { + panic!("Response is not object {:?}", response); + } + } } /// Compiles JS source code to QuickJS bytecode. @@ -31,34 +251,34 @@ pub extern "C" fn init() { /// # Safety /// /// * `js_src_ptr` must reference a valid array of unsigned bytes of `js_src_len` length -#[export_name = "compile_src"] -pub unsafe extern "C" fn compile_src(js_src_ptr: *const u8, js_src_len: usize) -> *const u32 { - // Use fresh runtime to avoid depending on Wizened runtime - let runtime = runtime::new_runtime().unwrap(); - let js_src = str::from_utf8(slice::from_raw_parts(js_src_ptr, js_src_len)).unwrap(); - let bytecode = runtime - .context() - .compile_module(FUNCTION_MODULE_NAME, js_src) - .unwrap(); - let bytecode_len = bytecode.len(); - // We need the bytecode buffer to live longer than this function so it can be read from memory - let bytecode_ptr = Box::leak(bytecode.into_boxed_slice()).as_ptr(); - COMPILE_SRC_RET_AREA[0] = bytecode_ptr as u32; - COMPILE_SRC_RET_AREA[1] = bytecode_len.try_into().unwrap(); - COMPILE_SRC_RET_AREA.as_ptr() -} +// #[export_name = "compile_src"] +// pub unsafe extern "C" fn compile_src(js_src_ptr: *const u8, js_src_len: usize) -> *const u32 { +// // Use fresh runtime to avoid depending on Wizened runtime +// let runtime = runtime::new_runtime().unwrap(); +// let js_src = str::from_utf8(slice::from_raw_parts(js_src_ptr, js_src_len)).unwrap(); +// let bytecode = runtime +// .context() +// .compile_module(FUNCTION_MODULE_NAME, js_src) +// .unwrap(); +// let bytecode_len = bytecode.len(); +// // We need the bytecode buffer to live longer than this function so it can be read from memory +// let bytecode_ptr = Box::leak(bytecode.into_boxed_slice()).as_ptr(); +// COMPILE_SRC_RET_AREA[0] = bytecode_ptr as u32; +// COMPILE_SRC_RET_AREA[1] = bytecode_len.try_into().unwrap(); +// COMPILE_SRC_RET_AREA.as_ptr() +// } /// Evaluates QuickJS bytecode /// /// # Safety /// /// * `bytecode_ptr` must reference a valid array of unsigned bytes of `bytecode_len` length -#[export_name = "eval_bytecode"] -pub unsafe extern "C" fn eval_bytecode(bytecode_ptr: *const u8, bytecode_len: usize) { - let runtime = RUNTIME.get().unwrap(); - let bytecode = slice::from_raw_parts(bytecode_ptr, bytecode_len); - execution::run_bytecode(runtime, bytecode); -} +// #[export_name = "eval_bytecode"] +// pub unsafe extern "C" fn eval_bytecode(bytecode_ptr: *const u8, bytecode_len: usize) { +// let runtime = RUNTIME.get().unwrap(); +// let bytecode = slice::from_raw_parts(bytecode_ptr, bytecode_len); +// execution::run_bytecode(runtime, bytecode); +// } /// Evaluates QuickJS bytecode and invokes the exported JS function name. /// @@ -75,9 +295,9 @@ pub unsafe extern "C" fn invoke( fn_name_ptr: *const u8, fn_name_len: usize, ) { - let runtime = RUNTIME.get().unwrap(); - let bytecode = slice::from_raw_parts(bytecode_ptr, bytecode_len); - let fn_name = str::from_utf8_unchecked(slice::from_raw_parts(fn_name_ptr, fn_name_len)); - execution::run_bytecode(runtime, bytecode); - execution::invoke_function(runtime, FUNCTION_MODULE_NAME, fn_name); + // let runtime = RUNTIME.get().unwrap(); + // let bytecode = slice::from_raw_parts(bytecode_ptr, bytecode_len); + // let fn_name = str::from_utf8_unchecked(slice::from_raw_parts(fn_name_ptr, fn_name_len)); + // execution::run_bytecode(runtime, bytecode); + // execution::invoke_function(runtime, FUNCTION_MODULE_NAME, fn_name); } diff --git a/JS/wasm/crates/arakoo-core/src/main.rs b/JS/wasm/crates/arakoo-core/src/main.rs deleted file mode 100644 index 70bc1b337..000000000 --- a/JS/wasm/crates/arakoo-core/src/main.rs +++ /dev/null @@ -1,122 +0,0 @@ -use javy::Runtime; -use once_cell::sync::OnceCell; - -use std::io; -use std::io::Read; -use std::slice; -use std::str; -use std::string::String; - -mod execution; -mod runtime; -use apis::http::types::Request; -const FUNCTION_MODULE_NAME: &str = "function.mjs"; - -static mut RUNTIME: OnceCell = OnceCell::new(); -static mut BYTECODE: OnceCell> = OnceCell::new(); - -#[link(wasm_import_module = "arakoo")] -extern "C" { - fn set_output(ptr: *const u8, len: i32); - fn get_request_len() -> i32; - fn get_request(ptr: *mut u8); -} - -#[export_name = "wizer.initialize"] -pub extern "C" fn init() { - let _wasm_ctx = WasmCtx::new(); - let runtime = runtime::new_runtime().unwrap(); - - let mut contents = String::new(); - io::stdin().read_to_string(&mut contents).unwrap(); - let bytecode = runtime - .context() - .compile_module("function.mjs", &contents) - .unwrap(); - - unsafe { - RUNTIME.set(runtime).unwrap(); - BYTECODE.set(bytecode).unwrap(); - } -} - -fn main() { - let bytecode = unsafe { BYTECODE.take().unwrap() }; - let runtime = unsafe { RUNTIME.take().unwrap() }; - execution::run_bytecode(&runtime, &bytecode); -} - -#[export_name = "run_entrypoint"] -pub fn run_entrypoint() { - let runtime = unsafe { RUNTIME.take().unwrap() }; - let bytecode = unsafe { BYTECODE.take().unwrap() }; - - let input_len = unsafe { get_request_len() }; - let mut input_buffer = Vec::with_capacity(input_len as usize); - let input_ptr = input_buffer.as_mut_ptr(); - - let input_buffer = unsafe { - get_request(input_ptr); - Vec::from_raw_parts(input_ptr, input_len as usize, input_len as usize) - }; - let request: Request = serde_json::from_slice(&input_buffer).unwrap(); - let request_string = serde_json::to_string(&request).unwrap(); - let result = execution::invoke_entrypoint(&runtime, &bytecode, request_string).unwrap(); - - let result_string = serde_json::to_string(&result).unwrap(); - - let len = result_string.len() as i32; - let result_ptr = result_string.as_ptr(); - unsafe { - set_output(result_ptr, len); - }; -} - -// Removed in post-processing. -/// Evaluates QuickJS bytecode and invokes the exported JS function name. -/// -/// # Safety -/// -/// * `fn_name_ptr` must reference a UTF-8 string with `fn_name_size` byte -/// length. -#[export_name = "javy.invoke"] -pub unsafe extern "C" fn invoke(fn_name_ptr: *mut u8, fn_name_size: usize) { - let _wasm_ctx = WasmCtx::new(); - - let js_fn_name = str::from_utf8_unchecked(slice::from_raw_parts(fn_name_ptr, fn_name_size)); - let runtime = unsafe { RUNTIME.take().unwrap() }; - execution::invoke_function(&runtime, FUNCTION_MODULE_NAME, js_fn_name); -} - -// RAII abstraction for calling Wasm ctors and dtors for exported non-main functions. -struct WasmCtx; - -impl WasmCtx { - #[must_use = "Failing to assign the return value will result in the wasm dtors being run immediately"] - fn new() -> Self { - unsafe { __wasm_call_ctors() }; - Self - } -} - -impl Drop for WasmCtx { - fn drop(&mut self) { - unsafe { __wasm_call_dtors() }; - } -} - -extern "C" { - // `__wasm_call_ctors` is generated by `wasm-ld` and invokes all of the global constructors. - // In a Rust bin crate, the `_start` function will invoke this implicitly but no other exported - // Wasm functions will invoke this. - // If this is not invoked, access to environment variables and directory preopens will not be - // available. - // This should only be invoked at the start of exported Wasm functions that are not the `main` - // function. - // References: - // - [Rust 1.67.0 stopped initializing the WASI environment for exported functions](https://github.com/rust-lang/rust/issues/107635) - // - [Wizer header in Fastly's JS compute runtime](https://github.com/fastly/js-compute-runtime/blob/main/runtime/js-compute-runtime/third_party/wizer.h#L92) - fn __wasm_call_ctors(); - - fn __wasm_call_dtors(); -} diff --git a/JS/wasm/crates/arakoo-core/src/runtime.rs b/JS/wasm/crates/arakoo-core/src/runtime.rs index 51c7591c0..1fd3745d6 100644 --- a/JS/wasm/crates/arakoo-core/src/runtime.rs +++ b/JS/wasm/crates/arakoo-core/src/runtime.rs @@ -1,6 +1,6 @@ use anyhow::Result; -use apis::{APIConfig, LogStream, RuntimeExt}; use javy::{Config, Runtime}; +use super::apis::{APIConfig, LogStream, RuntimeExt}; pub fn new_runtime() -> Result { let mut api_config = APIConfig::default(); diff --git a/JS/wasm/crates/cli/Cargo.toml b/JS/wasm/crates/cli/Cargo.toml index fe47acc48..de325a491 100644 --- a/JS/wasm/crates/cli/Cargo.toml +++ b/JS/wasm/crates/cli/Cargo.toml @@ -2,47 +2,19 @@ name = "cli" edition.workspace = true version.workspace = true +build = "build.rs" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [[bin]] -name = "javy" +name = "arakoo-compiler" path = "src/main.rs" -[features] -dump_wat = ["dep:wasmprinter"] -experimental_event_loop = [] - [dependencies] wizer = { workspace = true } -structopt = "0.3" anyhow = { workspace = true } -binaryen = "0.13.0" -# binaryen = { git = "https://github.com/pepyakin/binaryen-rs", rev = "00c98174843f957681ba0bc5cdcc9d15f5d0cb23" } -brotli = "3.4.0" -wasmprinter = { version = "0.2.75", optional = true } -wasmtime = { workspace = true } -wasmtime-wasi = { workspace = true } -wasi-common = { workspace = true } -walrus = "0.20.3" -swc_core = { version = "0.87.19", features = [ - "common_sourcemap", - "ecma_ast", - "ecma_parser", -] } -wit-parser = "0.13.0" -convert_case = "0.6.0" -wat = "1.0.85" - -[dev-dependencies] -serde_json = "1.0" -uuid = { version = "1.6", features = ["v4"] } -lazy_static = "1.4" -serde = { version = "1.0", default-features = false, features = ["derive"] } -criterion = "0.5" -num-format = "0.4.4" -tempfile = "3.9.0" -wasmparser = "0.118.1" +clap = { version = "4.1.4", features = [ "derive" ] } +binaryen = { git = "https://github.com/pepyakin/binaryen-rs" } [build-dependencies] anyhow = "1.0.79" diff --git a/JS/wasm/crates/cli/build.rs b/JS/wasm/crates/cli/build.rs index 3bd70e472..9d2752189 100644 --- a/JS/wasm/crates/cli/build.rs +++ b/JS/wasm/crates/cli/build.rs @@ -20,25 +20,25 @@ fn main() -> Result<()> { fn stub_javy_core_for_clippy() -> Result<()> { let out_dir = PathBuf::from(env::var("OUT_DIR")?); let engine_path = out_dir.join("engine.wasm"); - let provider_path = out_dir.join("provider.wasm"); + // let provider_path = out_dir.join("provider.wasm"); if !engine_path.exists() { std::fs::write(engine_path, [])?; println!("cargo:warning=using stubbed engine.wasm for static analysis purposes..."); } - if !provider_path.exists() { - std::fs::write(provider_path, [])?; - println!("cargo:warning=using stubbed provider.wasm for static analysis purposes..."); - } + // if !provider_path.exists() { + // std::fs::write(provider_path, [])?; + // println!("cargo:warning=using stubbed provider.wasm for static analysis purposes..."); + // } Ok(()) } -fn read_file(path: impl AsRef) -> Result> { - let mut buf: Vec = vec![]; - fs::File::open(path.as_ref())?.read_to_end(&mut buf)?; - Ok(buf) -} +// fn read_file(path: impl AsRef) -> Result> { +// let mut buf: Vec = vec![]; +// fs::File::open(path.as_ref())?.read_to_end(&mut buf)?; +// Ok(buf) +// } // Copy the engine binary build from the `core` crate fn copy_javy_core() -> Result<()> { @@ -53,6 +53,7 @@ fn copy_javy_core() -> Result<()> { .unwrap() .join("../../target/wasm32-wasi/debug/") } else { + println!("cargo:warning=using release build for arakoo_js_engine..."); PathBuf::from(&cargo_manifest_dir) .parent() .unwrap() @@ -61,31 +62,32 @@ fn copy_javy_core() -> Result<()> { .join("../../target/wasm32-wasi/release/") }; - let engine_path = module_path.join("arakoo-core.wasm"); - let quickjs_provider_path = module_path.join("javy_quickjs_provider.wasm"); - let quickjs_provider_wizened_path = module_path.join("javy_quickjs_provider_wizened.wasm"); + let engine_path = module_path.join("arakoo_js_engine.wasm"); + // let quickjs_provider_path = module_path.join("javy_quickjs_provider.wasm"); + // let quickjs_provider_wizened_path = module_path.join("javy_quickjs_provider_wizened.wasm"); - let mut wizer = wizer::Wizer::new(); - let wizened = wizer - .allow_wasi(true)? - .wasm_bulk_memory(true) - .run(read_file(&quickjs_provider_path)?.as_slice())?; - fs::File::create(&quickjs_provider_wizened_path)?.write_all(&wizened)?; + // let mut wizer = wizer::Wizer::new(); + // let wizened = wizer + // .allow_wasi(true)? + // .wasm_bulk_memory(true) + // .run(read_file(&quickjs_provider_path)?.as_slice())?; + // fs::File::create(&quickjs_provider_wizened_path)?.write_all(&wizened)?; println!("cargo:rerun-if-changed={}", engine_path.to_str().unwrap()); - println!( - "cargo:rerun-if-changed={}", - quickjs_provider_path.to_str().unwrap() - ); + // println!( + // "cargo:rerun-if-changed={}", + // quickjs_provider_path.to_str().unwrap() + // ); println!("cargo:rerun-if-changed=build.rs"); if engine_path.exists() { let out_dir = env::var("OUT_DIR")?; let copied_engine_path = Path::new(&out_dir).join("engine.wasm"); - let copied_provider_path = Path::new(&out_dir).join("provider.wasm"); + // let copied_provider_path = Path::new(&out_dir).join("provider.wasm"); fs::copy(&engine_path, copied_engine_path)?; - fs::copy(&quickjs_provider_wizened_path, copied_provider_path)?; + println!("cargo:warning=copied engine.wasm to OUT_DIR"); + // fs::copy(&quickjs_provider_wizened_path, copied_provider_path)?; } Ok(()) } diff --git a/JS/wasm/crates/cli/src/bytecode.rs b/JS/wasm/crates/cli/src/bytecode.rs deleted file mode 100644 index abe4ec4ee..000000000 --- a/JS/wasm/crates/cli/src/bytecode.rs +++ /dev/null @@ -1,79 +0,0 @@ -use anyhow::{anyhow, Result}; -use wasmtime::{Engine, Instance, Linker, Memory, Module, Store}; -use wasmtime_wasi::{WasiCtx, WasiCtxBuilder}; - -pub const QUICKJS_PROVIDER_MODULE: &[u8] = - include_bytes!(concat!(env!("OUT_DIR"), "/provider.wasm")); - -pub fn compile_source(js_source_code: &[u8]) -> Result> { - let (mut store, instance, memory) = create_wasm_env()?; - let (js_src_ptr, js_src_len) = - copy_source_code_into_instance(js_source_code, &mut store, &instance, &memory)?; - let ret_ptr = call_compile(js_src_ptr, js_src_len, &mut store, &instance)?; - let bytecode = copy_bytecode_from_instance(ret_ptr, &mut store, &memory)?; - Ok(bytecode) -} - -fn create_wasm_env() -> Result<(Store, Instance, Memory)> { - let engine = Engine::default(); - let module = Module::new(&engine, QUICKJS_PROVIDER_MODULE)?; - let mut linker = Linker::new(&engine); - wasmtime_wasi::snapshots::preview_1::add_wasi_snapshot_preview1_to_linker(&mut linker, |s| s)?; - let wasi = WasiCtxBuilder::new().inherit_stderr().build(); - let mut store = Store::new(&engine, wasi); - let instance = linker.instantiate(&mut store, &module)?; - let memory = instance.get_memory(&mut store, "memory").unwrap(); - Ok((store, instance, memory)) -} - -fn copy_source_code_into_instance( - js_source_code: &[u8], - mut store: &mut Store, - instance: &Instance, - memory: &Memory, -) -> Result<(u32, u32)> { - let realloc_fn = instance - .get_typed_func::<(u32, u32, u32, u32), u32>(&mut store, "canonical_abi_realloc")?; - let js_src_len = js_source_code.len().try_into()?; - - let original_ptr = 0; - let original_size = 0; - let alignment = 1; - let size = js_src_len; - let js_source_ptr = - realloc_fn.call(&mut store, (original_ptr, original_size, alignment, size))?; - - memory.write(&mut store, js_source_ptr.try_into()?, js_source_code)?; - - Ok((js_source_ptr, js_src_len)) -} - -fn call_compile( - js_src_ptr: u32, - js_src_len: u32, - mut store: &mut Store, - instance: &Instance, -) -> Result { - let compile_src_fn = instance.get_typed_func::<(u32, u32), u32>(&mut store, "compile_src")?; - let ret_ptr = compile_src_fn - .call(&mut store, (js_src_ptr, js_src_len)) - .map_err(|_| anyhow!("JS compilation failed"))?; - Ok(ret_ptr) -} - -fn copy_bytecode_from_instance( - ret_ptr: u32, - mut store: &mut Store, - memory: &Memory, -) -> Result> { - let mut ret_buffer = [0; 8]; - memory.read(&mut store, ret_ptr.try_into()?, &mut ret_buffer)?; - - let bytecode_ptr = u32::from_le_bytes(ret_buffer[0..4].try_into()?); - let bytecode_len = u32::from_le_bytes(ret_buffer[4..8].try_into()?); - - let mut bytecode = vec![0; bytecode_len.try_into()?]; - memory.read(&mut store, bytecode_ptr.try_into()?, &mut bytecode)?; - - Ok(bytecode) -} diff --git a/JS/wasm/crates/cli/src/commands.rs b/JS/wasm/crates/cli/src/commands.rs deleted file mode 100644 index ad335f09c..000000000 --- a/JS/wasm/crates/cli/src/commands.rs +++ /dev/null @@ -1,48 +0,0 @@ -use std::path::PathBuf; -use structopt::StructOpt; - -#[derive(Debug, StructOpt)] -#[structopt(name = "javy", about = "JavaScript to WebAssembly toolchain")] -pub enum Command { - /// Compiles JavaScript to WebAssembly. - Compile(CompileCommandOpts), - /// Emits the provider binary that is required to run dynamically - /// linked WebAssembly modules. - EmitProvider(EmitProviderCommandOpts), -} - -#[derive(Debug, StructOpt)] -pub struct CompileCommandOpts { - #[structopt(parse(from_os_str))] - /// Path of the JavaScript input file. - pub input: PathBuf, - - #[structopt(short = "o", parse(from_os_str), default_value = "index.wasm")] - /// Desired path of the WebAssembly output file. - pub output: PathBuf, - - #[structopt(short = "d")] - /// Creates a smaller module that requires a dynamically linked QuickJS provider Wasm - /// module to execute (see `emit-provider` command). - pub dynamic: bool, - - #[structopt(long = "wit")] - /// Optional path to WIT file describing exported functions. - /// Only supports function exports with no arguments and no return values. - pub wit: Option, - - #[structopt(short = "n")] - /// Optional WIT world name for WIT file. Must be specified if WIT is file path is specified. - pub wit_world: Option, - - #[structopt(long = "no-source-compression")] - /// Disable source code compression, which reduces compile time at the expense of generating larger WebAssembly files. - pub no_source_compression: bool, -} - -#[derive(Debug, StructOpt)] -pub struct EmitProviderCommandOpts { - #[structopt(long = "out", short = "o")] - /// Output path for the provider binary (default is stdout). - pub out: Option, -} diff --git a/JS/wasm/crates/cli/src/exports.rs b/JS/wasm/crates/cli/src/exports.rs deleted file mode 100644 index 19e5eb52d..000000000 --- a/JS/wasm/crates/cli/src/exports.rs +++ /dev/null @@ -1,48 +0,0 @@ -use anyhow::{anyhow, Result}; -use convert_case::{Case, Casing}; -use std::{env, path::Path}; - -use crate::{js::JS, wit}; - -pub struct Export { - pub wit: String, - pub js: String, -} - -pub fn process_exports(js: &JS, wit: &Path, wit_world: &str) -> Result> { - let js_exports = js.exports()?; - parse_wit_exports(wit, wit_world)? - .into_iter() - .map(|wit_export| { - let export = wit_export.from_case(Case::Kebab).to_case(Case::Camel); - if !js_exports.contains(&export) { - Err(anyhow!("JS module does not export {export}")) - } else { - Ok(Export { - wit: wit_export, - js: export, - }) - } - }) - .collect::>>() -} - -fn parse_wit_exports(wit: &Path, wit_world: &str) -> Result> { - // Configure wit-parser to not require semicolons but only if the relevant - // environment variable is not already set. - const SEMICOLONS_OPTIONAL_ENV_VAR: &str = "WIT_REQUIRE_SEMICOLONS"; - let semicolons_env_var_already_set = env::var(SEMICOLONS_OPTIONAL_ENV_VAR).is_ok(); - if !semicolons_env_var_already_set { - env::set_var(SEMICOLONS_OPTIONAL_ENV_VAR, "0"); - } - - let exports = wit::parse_exports(wit, wit_world); - - // If we set the environment variable to not require semicolons, remove - // that environment variable now that we no longer need it set. - if !semicolons_env_var_already_set { - env::remove_var(SEMICOLONS_OPTIONAL_ENV_VAR); - } - - exports -} diff --git a/JS/wasm/crates/cli/src/js.rs b/JS/wasm/crates/cli/src/js.rs deleted file mode 100644 index c0f47fbfd..000000000 --- a/JS/wasm/crates/cli/src/js.rs +++ /dev/null @@ -1,317 +0,0 @@ -/// Higher-level representation of JavaScript. -/// -/// This is intended to be used to derive different representations of source -/// code. For example, as a byte array, a string, QuickJS bytecode, compressed -/// bytes, or attributes of the source code like what it exports. -use std::{ - collections::HashMap, - fs::File, - io::{Cursor, Read}, - path::Path, - rc::Rc, -}; - -use anyhow::{anyhow, bail, Context, Result}; -use brotli::enc::{self, BrotliEncoderParams}; -use swc_core::{ - common::{FileName, SourceMap}, - ecma::{ - ast::{ - Decl, EsVersion, ExportDecl, ExportSpecifier, Module, ModuleDecl, ModuleExportName, - ModuleItem, Stmt, - }, - parser::{self, EsConfig, Syntax}, - }, -}; - -use crate::bytecode; - -#[derive(Clone, Debug)] -pub struct JS { - source_code: Rc, -} - -impl JS { - fn from_string(source_code: String) -> JS { - JS { - source_code: Rc::new(source_code), - } - } - - pub fn from_file(path: &Path) -> Result { - let mut input_file = File::open(path) - .with_context(|| format!("Failed to open input file {}", path.display()))?; - let mut contents: Vec = vec![]; - input_file.read_to_end(&mut contents)?; - Ok(Self::from_string(String::from_utf8(contents)?)) - } - - pub fn as_bytes(&self) -> &[u8] { - self.source_code.as_bytes() - } - - pub fn compile(&self) -> Result> { - bytecode::compile_source(self.source_code.as_bytes()) - } - - pub fn compress(&self) -> Result> { - let mut compressed_source_code: Vec = vec![]; - enc::BrotliCompress( - &mut Cursor::new(&self.source_code.as_bytes()), - &mut compressed_source_code, - &BrotliEncoderParams { - quality: 11, - ..Default::default() - }, - )?; - Ok(compressed_source_code) - } - - pub fn exports(&self) -> Result> { - let module = self.parse_module()?; - - // function foo() ... - let mut functions = HashMap::new(); - // export { foo, bar as baz } - let mut named_exports = vec![]; - // export function foo() ... - let mut exported_functions = vec![]; - for item in module.body { - match item { - ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(ExportDecl { - decl: Decl::Fn(f), - .. - })) => { - if !f.function.params.is_empty() { - bail!("Exported functions with parameters are not supported"); - } - if f.function.is_generator { - bail!("Exported generators are not supported"); - } - exported_functions.push(f.ident.sym); - } - ModuleItem::ModuleDecl(ModuleDecl::ExportNamed(e)) => { - for specifier in e.specifiers { - if let ExportSpecifier::Named(n) = specifier { - let orig = match n.orig { - ModuleExportName::Ident(i) => i.sym, - ModuleExportName::Str(s) => s.value, - }; - let exported_name = n.exported.map(|e| match e { - ModuleExportName::Ident(i) => i.sym, - ModuleExportName::Str(s) => s.value, - }); - named_exports.push((orig, exported_name)); - } - } - } - ModuleItem::ModuleDecl(ModuleDecl::ExportDefaultDecl(e)) if e.decl.is_fn_expr() => { - exported_functions.push("default".into()) - } - ModuleItem::ModuleDecl(ModuleDecl::ExportDefaultExpr(e)) if e.expr.is_arrow() => { - exported_functions.push("default".into()) - } - ModuleItem::Stmt(Stmt::Decl(Decl::Fn(f))) => { - functions.insert( - f.ident.sym, - (f.function.params.is_empty(), f.function.is_generator), - ); - } - _ => continue, - } - } - - let mut named_exported_functions = named_exports - .into_iter() - .filter_map(|(orig, exported)| { - if let Some((no_params, is_generator)) = functions.get(&orig) { - if !no_params { - Some(Err(anyhow!( - "Exported functions with parameters are not supported" - ))) - } else if *is_generator { - Some(Err(anyhow!("Exported generators are not supported"))) - } else { - Some(Ok(exported.unwrap_or(orig))) - } - } else { - None - } - }) - .collect::, _>>()?; - exported_functions.append(&mut named_exported_functions); - Ok(exported_functions - .into_iter() - .map(|f| f.to_string()) - .collect()) - } - - fn parse_module(&self) -> Result { - let source_map: SourceMap = Default::default(); - let file = source_map.new_source_file_from(FileName::Anon, self.source_code.clone()); - let mut errors = vec![]; - parser::parse_file_as_module( - &file, - Syntax::Es(EsConfig::default()), - EsVersion::Es2020, - None, - &mut errors, - ) - .map_err(|e| anyhow!(e.into_kind().msg())) - .with_context(|| "Invalid JavaScript") - } -} - -#[cfg(test)] -mod tests { - use crate::js::JS; - - use anyhow::Result; - - #[test] - fn parse_no_exports() -> Result<()> { - let exports = parse("function foo() {}")?; - assert_eq!(Vec::<&str>::default(), exports); - Ok(()) - } - - #[test] - fn parse_invalid_js() -> Result<()> { - let res = parse("fun foo() {}"); - assert_eq!("Invalid JavaScript", res.err().unwrap().to_string()); - Ok(()) - } - - #[test] - fn parse_one_func_export() -> Result<()> { - let exports = parse("export function foo() {}")?; - assert_eq!(vec!["foo"], exports); - Ok(()) - } - - #[test] - fn parse_func_export_with_parameter() -> Result<()> { - let res = parse("export function foo(bar) {}"); - assert_eq!( - "Exported functions with parameters are not supported", - res.err().unwrap().to_string() - ); - Ok(()) - } - - #[test] - fn parse_generator_export() -> Result<()> { - let res = parse("export function *foo() {}"); - assert_eq!( - "Exported generators are not supported", - res.err().unwrap().to_string() - ); - Ok(()) - } - - #[test] - fn parse_two_func_exports() -> Result<()> { - let exports = parse("export function foo() {}; export function bar() {};")?; - assert_eq!(vec!["foo", "bar"], exports); - Ok(()) - } - - #[test] - fn parse_const_export() -> Result<()> { - let exports = parse("export const x = 1;")?; - let expected_exports: Vec<&str> = vec![]; - assert_eq!(expected_exports, exports); - Ok(()) - } - - #[test] - fn parse_const_export_and_func_export() -> Result<()> { - let exports = parse("export const x = 1; export function foo() {}")?; - assert_eq!(vec!["foo"], exports); - Ok(()) - } - - #[test] - fn parse_named_func_export() -> Result<()> { - let exports = parse("function foo() {}; export { foo };")?; - assert_eq!(vec!["foo"], exports); - Ok(()) - } - - #[test] - fn parse_named_func_export_with_arg() -> Result<()> { - let res = parse("function foo(bar) {}; export { foo };"); - assert_eq!( - "Exported functions with parameters are not supported", - res.err().unwrap().to_string() - ); - Ok(()) - } - - #[test] - fn parse_funcs_with_args() -> Result<()> { - let exports = parse("function foo(bar) {}")?; - assert_eq!(Vec::<&str>::default(), exports); - Ok(()) - } - - #[test] - fn parse_named_func_export_and_const_export() -> Result<()> { - let exports = parse("function foo() {}; const bar = 1; export { foo, bar };")?; - assert_eq!(vec!["foo"], exports); - Ok(()) - } - - #[test] - fn parse_func_export_and_named_func_export() -> Result<()> { - let exports = parse("export function foo() {}; function bar() {}; export { bar };")?; - assert_eq!(vec!["foo", "bar"], exports); - Ok(()) - } - - #[test] - fn parse_renamed_func_export() -> Result<()> { - let exports = parse("function foo() {}; export { foo as bar };")?; - assert_eq!(vec!["bar"], exports); - Ok(()) - } - - #[test] - fn parse_hoisted_func_export() -> Result<()> { - let exports = parse("export { foo }; function foo() {}")?; - assert_eq!(vec!["foo"], exports); - Ok(()) - } - - #[test] - fn parse_renamed_hosted_func_export() -> Result<()> { - let exports = parse("export { foo as bar }; function foo() {}")?; - assert_eq!(vec!["bar"], exports); - Ok(()) - } - - #[test] - fn parse_hoisted_exports_with_func_and_const() -> Result<()> { - let exports = parse("export { foo, bar }; function foo() {}; const bar = 1;")?; - assert_eq!(vec!["foo"], exports); - Ok(()) - } - - #[test] - fn parse_default_arrow_export() -> Result<()> { - let exports = parse("export default () => {}")?; - assert_eq!(vec!["default"], exports); - Ok(()) - } - - #[test] - fn parse_default_function_export() -> Result<()> { - let exports = parse("export default function() {}")?; - assert_eq!(vec!["default"], exports); - Ok(()) - } - - fn parse(js: &str) -> Result> { - JS::from_string(js.to_string()).exports() - } -} diff --git a/JS/wasm/crates/cli/src/main.rs b/JS/wasm/crates/cli/src/main.rs index a98dd9a50..37816c94c 100644 --- a/JS/wasm/crates/cli/src/main.rs +++ b/JS/wasm/crates/cli/src/main.rs @@ -1,49 +1,90 @@ -mod bytecode; -mod commands; -mod exports; -mod js; -mod wasm_generator; -mod wit; - -use crate::commands::{Command, EmitProviderCommandOpts}; -use crate::wasm_generator::r#static as static_generator; -use anyhow::{bail, Result}; -use js::JS; +use anyhow::bail; +use anyhow::Context; +use anyhow::Result; +use binaryen::CodegenConfig; +use binaryen::Module; +use clap::Parser; use std::fs; -use std::fs::File; -use std::io::Write; -use structopt::StructOpt; -use wasm_generator::dynamic as dynamic_generator; +use std::io::Read; +use std::process::Command; +use std::{env, fs::File, path::PathBuf}; +use wizer::Wizer; + +const VERSION: &str = env!("CARGO_PKG_VERSION"); + +#[derive(Debug, Parser)] +#[clap( + about = "A utility to convert js to arakoo runtime compatible wasm component", + version = VERSION +)] +pub struct Options { + pub input: PathBuf, + #[arg(short = 'o', default_value = "index.wasm")] + pub output: PathBuf, +} fn main() -> Result<()> { - let cmd = Command::from_args(); - - match &cmd { - Command::EmitProvider(opts) => emit_provider(opts), - Command::Compile(opts) => { - let js = JS::from_file(&opts.input)?; - let exports = match (&opts.wit, &opts.wit_world) { - (None, None) => Ok(vec![]), - (None, Some(_)) => Ok(vec![]), - (Some(_), None) => bail!("Must provide WIT world when providing WIT file"), - (Some(wit), Some(world)) => exports::process_exports(&js, wit, world), - }?; - let wasm = if opts.dynamic { - dynamic_generator::generate(&js, exports, opts.no_source_compression)? - } else { - static_generator::generate(&js, exports, opts.no_source_compression)? - }; - fs::write(&opts.output, wasm)?; - Ok(()) + let opts = Options::parse(); + + if env::var("EDECHAINS_JS_WIZEN").eq(&Ok("1".into())) { + env::remove_var("EDECHAINS_JS_WIZEN"); + + println!("\nStarting to build arakoo compatible module"); + let wasm: Vec = if let Ok(wasm_bytes) = std::fs::read(concat!(env!("OUT_DIR"), "/engine.wasm")) { + wasm_bytes + } else { + // Provide a fallback wasm binary if the file is not found + panic!("Engine wasm not found"); + }; + + println!("Preinitializing using Wizer"); + + let mut wasm = Wizer::new() + .allow_wasi(true)? + .inherit_stdio(true) + .wasm_bulk_memory(true) + .run(wasm.as_slice())?; + + let codegen_config = CodegenConfig { + optimization_level: 3, + shrink_level: 0, + debug_info: false, + }; + + println!("Optimizing wasm binary using wasm-opt"); + + if let Ok(mut module) = Module::read(&wasm) { + module.optimize(&codegen_config); + module + .run_optimization_passes(vec!["strip"], &codegen_config) + .expect("Unable to optimize"); + wasm = module.write(); + } else { + bail!("Unable to read wasm binary for wasm-opt optimizations"); } + + fs::write(&opts.output, wasm)?; + return Ok(()); } -} -fn emit_provider(opts: &EmitProviderCommandOpts) -> Result<()> { - let mut file: Box = match opts.out.as_ref() { - Some(path) => Box::new(File::create(path)?), - _ => Box::new(std::io::stdout()), - }; - file.write_all(bytecode::QUICKJS_PROVIDER_MODULE)?; + let script = File::open(&opts.input) + .with_context(|| format!("Failed to open input file {}", opts.input.display()))?; + + let self_cmd = env::args().next().expect("No self command"); + env::set_var("EDECHAINS_JS_WIZEN", "1"); + + let status = Command::new(self_cmd) + .arg(&opts.input) + .arg("-o") + .arg(&opts.output) + .stdin(script) + .status()?; + + if !status.success() { + anyhow::bail!("Failed to convert js to wasm"); + } + + println!("Arakoo compatible module built successfully"); + Ok(()) } diff --git a/JS/wasm/crates/cli/src/wasm_generator/dynamic.rs b/JS/wasm/crates/cli/src/wasm_generator/dynamic.rs deleted file mode 100644 index a89455a1e..000000000 --- a/JS/wasm/crates/cli/src/wasm_generator/dynamic.rs +++ /dev/null @@ -1,193 +0,0 @@ -use crate::{exports::Export, js::JS}; - -use super::transform::{self, SourceCodeSection}; -use anyhow::Result; -use walrus::{DataKind, FunctionBuilder, Module, ValType}; - -// Run the calling code with the `dump_wat` feature enabled to print the WAT to stdout -// -// For the example generated WAT, the `bytecode_len` is 137 -// (module -// (type (;0;) (func)) -// (type (;1;) (func (param i32 i32))) -// (type (;2;) (func (param i32 i32 i32 i32))) -// (type (;3;) (func (param i32 i32 i32 i32) (result i32))) -// (import "javy_quickjs_provider_v1" "canonical_abi_realloc" (func (;0;) (type 3))) -// (import "javy_quickjs_provider_v1" "eval_bytecode" (func (;1;) (type 1))) -// (import "javy_quickjs_provider_v1" "memory" (memory (;0;) 0)) -// (import "javy_quickjs_provider_v1" "invoke" (func (;2;) (type 2))) -// (func (;3;) (type 0) -// (local i32 i32) -// i32.const 0 -// i32.const 0 -// i32.const 1 -// i32.const 137 -// call 0 -// local.tee 0 -// i32.const 0 -// i32.const 137 -// memory.init 0 -// data.drop 0 -// i32.const 0 -// i32.const 0 -// i32.const 1 -// i32.const 3 -// call 0 -// local.tee 1 -// i32.const 0 -// i32.const 3 -// memory.init 1 -// data.drop 1 -// local.get 0 -// i32.const 137 -// local.get 1 -// i32.const 3 -// call 2 -// ) -// (func (;4;) (type 0) -// (local i32) -// i32.const 0 -// i32.const 0 -// i32.const 1 -// i32.const 137 -// call 0 -// local.tee 0 -// i32.const 0 -// i32.const 137 -// memory.init 0 -// local.get 0 -// i32.const 137 -// call 1 -// ) -// (export "_start" (func 4)) -// (export "foo" (func 3)) -// (data (;0;) "\02\05\18function.mjs\06foo\0econsole\06log\06bar\0f\bc\03\00\01\00\00\be\03\00\00\0e\00\06\01\a0\01\00\00\00\03\01\01\1a\00\be\03\00\01\08\ea\05\c0\00\e1)8\e0\00\00\00B\e1\00\00\00\04\e2\00\00\00$\01\00)\bc\03\01\04\01\00\07\0a\0eC\06\01\be\03\00\00\00\03\00\00\13\008\e0\00\00\00B\e1\00\00\00\04\df\00\00\00$\01\00)\bc\03\01\02\03]") -// (data (;1;) "foo") -// ) -pub fn generate( - js: &JS, - exported_functions: Vec, - no_source_compression: bool, -) -> Result> { - let mut module = Module::with_config(transform::module_config()); - - const IMPORT_NAMESPACE: &str = "javy_quickjs_provider_v1"; - - let canonical_abi_realloc_type = module.types.add( - &[ValType::I32, ValType::I32, ValType::I32, ValType::I32], - &[ValType::I32], - ); - let (canonical_abi_realloc_fn, _) = module.add_import_func( - IMPORT_NAMESPACE, - "canonical_abi_realloc", - canonical_abi_realloc_type, - ); - - let eval_bytecode_type = module.types.add(&[ValType::I32, ValType::I32], &[]); - let (eval_bytecode_fn, _) = - module.add_import_func(IMPORT_NAMESPACE, "eval_bytecode", eval_bytecode_type); - - let (memory, _) = module.add_import_memory(IMPORT_NAMESPACE, "memory", false, 0, None); - - transform::add_producers_section(&mut module.producers); - if no_source_compression { - module.customs.add(SourceCodeSection::uncompressed(js)?); - } else { - module.customs.add(SourceCodeSection::compressed(js)?); - } - - let bytecode = js.compile()?; - let bytecode_len: i32 = bytecode.len().try_into()?; - let bytecode_data = module.data.add(DataKind::Passive, bytecode); - - let mut main = FunctionBuilder::new(&mut module.types, &[], &[]); - let bytecode_ptr_local = module.locals.add(ValType::I32); - main.func_body() - // Allocate memory in javy_quickjs_provider for bytecode array. - .i32_const(0) // orig ptr - .i32_const(0) // orig size - .i32_const(1) // alignment - .i32_const(bytecode_len) // new size - .call(canonical_abi_realloc_fn) - // Copy bytecode array into allocated memory. - .local_tee(bytecode_ptr_local) // save returned address to local and set as dest addr for mem.init - .i32_const(0) // offset into data segment for mem.init - .i32_const(bytecode_len) // size to copy from data segment - // top-2: dest addr, top-1: offset into source, top-0: size of memory region in bytes. - .memory_init(memory, bytecode_data) - // Evaluate bytecode. - .local_get(bytecode_ptr_local) // ptr to bytecode - .i32_const(bytecode_len) - .call(eval_bytecode_fn); - let main = main.finish(vec![], &mut module.funcs); - - module.exports.add("_start", main); - - if !exported_functions.is_empty() { - let invoke_type = module.types.add( - &[ValType::I32, ValType::I32, ValType::I32, ValType::I32], - &[], - ); - let (invoke_fn, _) = module.add_import_func(IMPORT_NAMESPACE, "invoke", invoke_type); - - let fn_name_ptr_local = module.locals.add(ValType::I32); - for export in exported_functions { - // For each JS function export, add an export that copies the name of the function into memory and invokes it. - let js_export_bytes = export.js.as_bytes(); - let js_export_len: i32 = js_export_bytes.len().try_into().unwrap(); - let fn_name_data = module.data.add(DataKind::Passive, js_export_bytes.to_vec()); - - let mut export_fn = FunctionBuilder::new(&mut module.types, &[], &[]); - export_fn - .func_body() - // Copy bytecode. - .i32_const(0) // orig ptr - .i32_const(0) // orig len - .i32_const(1) // alignment - .i32_const(bytecode_len) // size to copy - .call(canonical_abi_realloc_fn) - .local_tee(bytecode_ptr_local) - .i32_const(0) // offset into data segment - .i32_const(bytecode_len) // size to copy - .memory_init(memory, bytecode_data) // copy bytecode into allocated memory - .data_drop(bytecode_data) - // Copy function name. - .i32_const(0) // orig ptr - .i32_const(0) // orig len - .i32_const(1) // alignment - .i32_const(js_export_len) // new size - .call(canonical_abi_realloc_fn) - .local_tee(fn_name_ptr_local) - .i32_const(0) // offset into data segment - .i32_const(js_export_len) // size to copy - .memory_init(memory, fn_name_data) // copy fn name into allocated memory - .data_drop(fn_name_data) - // Call invoke. - .local_get(bytecode_ptr_local) - .i32_const(bytecode_len) - .local_get(fn_name_ptr_local) - .i32_const(js_export_len) - .call(invoke_fn); - let export_fn = export_fn.finish(vec![], &mut module.funcs); - module.exports.add(&export.wit, export_fn); - } - } - - let wasm = module.emit_wasm(); - print_wat(&wasm)?; - Ok(wasm) -} - -#[cfg(feature = "dump_wat")] -fn print_wat(wasm_binary: &[u8]) -> Result<()> { - println!( - "Generated WAT: \n{}", - wasmprinter::print_bytes(&wasm_binary)? - ); - Ok(()) -} - -#[cfg(not(feature = "dump_wat"))] -fn print_wat(_wasm_binary: &[u8]) -> Result<()> { - Ok(()) -} diff --git a/JS/wasm/crates/cli/src/wasm_generator/mod.rs b/JS/wasm/crates/cli/src/wasm_generator/mod.rs deleted file mode 100644 index 186453527..000000000 --- a/JS/wasm/crates/cli/src/wasm_generator/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -pub mod dynamic; -pub mod r#static; -mod transform; diff --git a/JS/wasm/crates/cli/src/wasm_generator/static.rs b/JS/wasm/crates/cli/src/wasm_generator/static.rs deleted file mode 100644 index 14440b494..000000000 --- a/JS/wasm/crates/cli/src/wasm_generator/static.rs +++ /dev/null @@ -1,143 +0,0 @@ -use std::{collections::HashMap, rc::Rc, sync::OnceLock}; - -use anyhow::{anyhow, Result}; -use binaryen::{CodegenConfig, Module}; -use walrus::{DataKind, ExportItem, FunctionBuilder, FunctionId, MemoryId, ValType}; -use wasi_common::{pipe::ReadPipe, WasiCtx}; -use wasmtime::Linker; -use wasmtime_wasi::WasiCtxBuilder; -use wizer::Wizer; - -use crate::{exports::Export, js::JS}; - -use super::transform::{self, SourceCodeSection}; - -static mut WASI: OnceLock = OnceLock::new(); - -pub fn generate(js: &JS, exports: Vec, no_source_compression: bool) -> Result> { - let wasm = include_bytes!(concat!(env!("OUT_DIR"), "/engine.wasm")); - - let wasi = WasiCtxBuilder::new() - .stdin(Box::new(ReadPipe::from(js.as_bytes()))) - .inherit_stdout() - .inherit_stderr() - .build(); - // We can't move the WasiCtx into `make_linker` since WasiCtx doesn't implement the `Copy` trait. - // So we move the WasiCtx into a mutable static OnceLock instead. - // Setting the value in the `OnceLock` and getting the reference back from it should be safe given - // we're never executing this code concurrently. This code will also fail if `generate` is invoked - // more than once per execution. - if unsafe { WASI.set(wasi) }.is_err() { - panic!("Failed to set WASI static variable") - } - - let wasm = Wizer::new() - .make_linker(Some(Rc::new(|engine| { - let mut linker = Linker::new(engine); - wasmtime_wasi::add_to_linker(&mut linker, |_ctx: &mut Option| { - unsafe { WASI.get_mut() }.unwrap() - })?; - Ok(linker) - })))? - .wasm_bulk_memory(true) - .run(wasm) - .map_err(|_| anyhow!("JS compilation failed"))?; - - let mut module = transform::module_config().parse(&wasm)?; - - let (realloc, free, invoke, memory) = { - let mut exports = HashMap::new(); - for export in module.exports.iter() { - exports.insert(export.name.as_str(), export); - } - ( - *exports.get("canonical_abi_realloc").unwrap(), - *exports.get("canonical_abi_free").unwrap(), - *exports.get("javy.invoke").unwrap(), - *exports.get("memory").unwrap(), - ) - }; - - let realloc_export = realloc.id(); - let free_export = free.id(); - let invoke_export = invoke.id(); - - if !exports.is_empty() { - let ExportItem::Function(realloc_fn) = realloc.item else { - unreachable!() - }; - let ExportItem::Function(invoke_fn) = invoke.item else { - unreachable!() - }; - let ExportItem::Memory(memory) = memory.item else { - unreachable!() - }; - export_exported_js_functions(&mut module, realloc_fn, invoke_fn, memory, exports); - } - - // We no longer need these exports so remove them. - module.exports.delete(realloc_export); - module.exports.delete(free_export); - module.exports.delete(invoke_export); - - let wasm = module.emit_wasm(); - - let codegen_cfg = CodegenConfig { - optimization_level: 3, // Aggressively optimize for speed. - shrink_level: 0, // Don't optimize for size at the expense of performance. - debug_info: false, - }; - - let mut module = Module::read(&wasm) - .map_err(|_| anyhow!("Unable to read wasm binary for wasm-opt optimizations"))?; - module.optimize(&codegen_cfg); - module - .run_optimization_passes(vec!["strip"], &codegen_cfg) - .map_err(|_| anyhow!("Running wasm-opt optimization passes failed"))?; - let wasm = module.write(); - - let mut module = transform::module_config().parse(&wasm)?; - if no_source_compression { - module.customs.add(SourceCodeSection::uncompressed(js)?); - } else { - module.customs.add(SourceCodeSection::compressed(js)?); - } - transform::add_producers_section(&mut module.producers); - Ok(module.emit_wasm()) -} - -fn export_exported_js_functions( - module: &mut walrus::Module, - realloc_fn: FunctionId, - invoke_fn: FunctionId, - memory: MemoryId, - js_exports: Vec, -) { - let ptr_local = module.locals.add(ValType::I32); - for export in js_exports { - println!("Exporting JS function: {}", export.js); - // For each JS function export, add an export that copies the name of the function into memory and invokes it. - let js_export_bytes = export.js.as_bytes(); - let js_export_len: i32 = js_export_bytes.len().try_into().unwrap(); - let fn_name_data = module.data.add(DataKind::Passive, js_export_bytes.to_vec()); - - let mut export_fn = FunctionBuilder::new(&mut module.types, &[], &[]); - export_fn - .func_body() - .i32_const(0) // orig ptr - .i32_const(0) // orig len - .i32_const(1) // alignment - .i32_const(js_export_len) // new size - .call(realloc_fn) - .local_tee(ptr_local) - .i32_const(0) // offset into data segment - .i32_const(js_export_len) // size to copy - .memory_init(memory, fn_name_data) // copy fn name into allocated memory - .data_drop(fn_name_data) - .local_get(ptr_local) - .i32_const(js_export_len) - .call(invoke_fn); - let export_fn = export_fn.finish(vec![], &mut module.funcs); - module.exports.add(&export.wit, export_fn); - } -} diff --git a/JS/wasm/crates/cli/src/wasm_generator/transform.rs b/JS/wasm/crates/cli/src/wasm_generator/transform.rs deleted file mode 100644 index 68023dcbd..000000000 --- a/JS/wasm/crates/cli/src/wasm_generator/transform.rs +++ /dev/null @@ -1,47 +0,0 @@ -use std::borrow::Cow; - -use anyhow::Result; -use walrus::{CustomSection, IdsToIndices, ModuleConfig, ModuleProducers}; - -use crate::js::JS; - -#[derive(Debug)] -pub struct SourceCodeSection { - source_code: Vec, -} - -impl SourceCodeSection { - pub fn compressed(js: &JS) -> Result { - Ok(SourceCodeSection { - source_code: js.compress()?, - }) - } - - pub fn uncompressed(js: &JS) -> Result { - Ok(SourceCodeSection { - source_code: js.as_bytes().to_vec(), - }) - } -} - -impl CustomSection for SourceCodeSection { - fn name(&self) -> &str { - "javy_source" - } - - fn data(&self, _ids_to_indices: &IdsToIndices) -> Cow<[u8]> { - (&self.source_code).into() - } -} - -pub fn module_config() -> ModuleConfig { - let mut config = ModuleConfig::new(); - config.generate_name_section(false); - config -} - -pub fn add_producers_section(producers: &mut ModuleProducers) { - producers.clear(); // removes Walrus and Rust - producers.add_language("JavaScript", "ES2020"); - producers.add_processed_by("Javy", env!("CARGO_PKG_VERSION")); -} diff --git a/JS/wasm/crates/cli/src/wit.rs b/JS/wasm/crates/cli/src/wit.rs deleted file mode 100644 index 027dc22da..000000000 --- a/JS/wasm/crates/cli/src/wit.rs +++ /dev/null @@ -1,35 +0,0 @@ -use std::path::Path; - -use anyhow::{bail, Result}; - -use wit_parser::{Resolve, UnresolvedPackage, WorldItem}; - -pub fn parse_exports(wit: impl AsRef, world: &str) -> Result> { - let mut resolve = Resolve::default(); - let package = UnresolvedPackage::parse_path(wit.as_ref())?; - resolve.push(package)?; - let (_, package_id) = resolve.package_names.first().unwrap(); - let world_id = resolve.select_world(*package_id, Some(world))?; - let world = resolve.worlds.get(world_id).unwrap(); - - if !world.imports.is_empty() { - bail!("Imports in WIT file are not supported"); - } - let mut exported_functions = vec![]; - for (_, export) in &world.exports { - match export { - WorldItem::Interface(_) => bail!("Exported interfaces are not supported"), - WorldItem::Function(f) => { - if !f.params.is_empty() { - bail!("Exported functions with parameters are not supported") - } else if f.results.len() != 0 { - bail!("Exported functions with return values are not supported") - } else { - exported_functions.push(f.name.clone()) - } - } - WorldItem::Type(_) => bail!("Exported types are not supported"), - } - } - Ok(exported_functions) -} diff --git a/JS/wasm/crates/serve/Cargo.toml b/JS/wasm/crates/serve/Cargo.toml index d845dd83e..de912720b 100644 --- a/JS/wasm/crates/serve/Cargo.toml +++ b/JS/wasm/crates/serve/Cargo.toml @@ -9,7 +9,6 @@ path = "src/main.rs" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -wasi-common = { workspace = true } wasmtime = { workspace = true } wasmtime-wasi = { workspace = true } anyhow = { workspace = true } @@ -23,5 +22,8 @@ tracing-subscriber = { version = "^0.3.18", features = ["env-filter", "fmt"] } jrsonnet-evaluator = { version = "0.5.0-pre95" } jrsonnet-parser = { version = "0.5.0-pre95" } jrsonnet-stdlib = { version = "0.5.0-pre95" } -reqwest = { version = "0.11", features = ["json"] } -arakoo-jsonnet = {path = "../../../jsonnet"} \ No newline at end of file + +arakoo-jsonnet = { path = "../../../jsonnet" } +async-trait = "0.1.80" +http = {workspace = true} +reqwest = {workspace = true} diff --git a/JS/wasm/crates/serve/src/binding.rs b/JS/wasm/crates/serve/src/binding.rs index b1ca79d59..3752d28cb 100644 --- a/JS/wasm/crates/serve/src/binding.rs +++ b/JS/wasm/crates/serve/src/binding.rs @@ -1,336 +1,173 @@ -use std::{ - env, - sync::{Arc, Mutex}, -}; - -use arakoo_jsonnet::{ - ext_string, jsonnet_destroy, jsonnet_evaluate_file, jsonnet_evaluate_snippet, jsonnet_make, -}; +use super::http_types::{Headers, HttpError, Method, Response}; +use async_trait::async_trait; +use http::HeaderMap; +use reqwest::Url; + +// use std::{ +// env, +// sync::{Arc, Mutex}, +// }; + +// use arakoo_jsonnet::{ +// ext_string, jsonnet_destroy, jsonnet_evaluate_file, jsonnet_evaluate_snippet, jsonnet_make, +// }; +use jrsonnet_evaluator::{function::TlaArg, gc::GcHashMap, manifest::ManifestFormat, trace::TraceFormat, State}; +use jrsonnet_parser::IStr; use std::{fs, io}; -use tokio::runtime::Builder; +// use tokio::runtime::Builder; use tracing::error; -use wasi_common::WasiCtx; -use wasmtime::*; +// use wasmtime::*; use crate::io::{WasmInput, WasmOutput}; -// pub struct VM { -// state: State, -// manifest_format: Box, -// trace_format: Box, -// tla_args: GcHashMap, -// } - -/// Adds exported functions to the Wasm linker. -/// -/// This function wraps the `jsonnet_evaluate`, `jsonnet_output_len`, and `jsonnet_output` -/// functions to be called from WebAssembly. It sets up the necessary state and -/// memory management for evaluating Jsonnet code and writing the output back to -/// WebAssembly memory. -pub fn add_jsonnet_to_linker(linker: &mut Linker) -> anyhow::Result<()> { - // Create a shared output buffer that will be used to store the result of the Jsonnet evaluation. - let output: Arc> = Arc::new(Mutex::new(String::new())); - let mut output_clone = output.clone(); - - // Wrap the `jsonnet_evaluate` function to be called from WebAssembly. - linker.func_wrap( - "arakoo", - "jsonnet_evaluate", - move |mut caller: Caller<'_, WasiCtx>, - var_ptr: i32, - var_len: i32, - path_ptr: i32, - code_len: i32| { - // Clone the output buffer for use within the closure. - let output = output_clone.clone(); - // Get the WebAssembly memory instance. - let mem = match caller.get_export("memory") { - Some(Extern::Memory(mem)) => mem, - _ => return Err(Trap::NullReference.into()), - }; - // Calculate the offsets for the variable and path buffers in WebAssembly memory. - let var_offset = var_ptr as u32 as usize; - let path_offset = path_ptr as u32 as usize; - // Create buffers to read the variable and path data from WebAssembly memory. - let mut var_buffer = vec![0; var_len as usize]; - let mut path_buffer = vec![0; code_len as usize]; - - // Read the path data from WebAssembly memory and convert it to a string. - let path = match mem.read(&caller, path_offset, &mut path_buffer) { - Ok(_) => match std::str::from_utf8(&path_buffer) { - Ok(s) => s, - Err(_) => return Err(Trap::BadSignature.into()), - }, - _ => return Err(Trap::MemoryOutOfBounds.into()), - }; - // Read the variable data from WebAssembly memory and convert it to a string. - let var = match mem.read(&caller, var_offset, &mut var_buffer) { - Ok(_) => match std::str::from_utf8(&var_buffer) { - Ok(s) => s, - Err(_) => return Err(Trap::BadSignature.into()), - }, - _ => return Err(Trap::MemoryOutOfBounds.into()), - }; - // Parse the variable data as JSON. - let var_json: serde_json::Value = match serde_json::from_str(var) { - Ok(v) => v, - Err(e) => { - error!("Error parsing var: {}", e); - return Err(Trap::BadSignature.into()); - } - }; - - // Initialize the Jsonnet VM state with default settings. - let vm = jsonnet_make(); - - // Evaluate the Jsonnet code snippet using the provided path and variables. - let code = path; - for (key, value) in var_json.as_object().unwrap() { - // context.add_ext_var(key.into(), Val::Str(value.as_str().unwrap().into())); - ext_string( - vm, - key, - value.as_str().expect("ext_string value is not a string"), - ); - } - let out = jsonnet_evaluate_snippet(vm, "deleteme", code); - // Store the output of the Jsonnet evaluation in the shared output buffer. - let mut output = output.lock().unwrap(); - *output = out; - jsonnet_destroy(vm); - Ok(()) - }, - )?; - - output_clone = output.clone(); - // Wrap the `jsonnet_evaluate_file` function to be called from WebAssembly. - linker.func_wrap( - "arakoo", - "jsonnet_evaluate_file", - move |mut caller: Caller<'_, WasiCtx>, - var_ptr: i32, - var_len: i32, - path_ptr: i32, - code_len: i32| { - // Clone the output buffer for use within the closure. - let output = output_clone.clone(); - // Get the WebAssembly memory instance. - let mem = match caller.get_export("memory") { - Some(Extern::Memory(mem)) => mem, - _ => return Err(Trap::NullReference.into()), - }; - // Calculate the offsets for the variable and path buffers in WebAssembly memory. - let var_offset = var_ptr as u32 as usize; - let path_offset = path_ptr as u32 as usize; - // Create buffers to read the variable and path data from WebAssembly memory. - let mut var_buffer = vec![0; var_len as usize]; - let mut path_buffer = vec![0; code_len as usize]; - // Read the path data from WebAssembly memory and convert it to a string. - let path = match mem.read(&caller, path_offset, &mut path_buffer) { - Ok(_) => match std::str::from_utf8(&path_buffer) { - Ok(s) => s, - Err(_) => return Err(Trap::BadSignature.into()), - }, - _ => return Err(Trap::MemoryOutOfBounds.into()), - }; - // Read the variable data from WebAssembly memory and convert it to a string. - let var = match mem.read(&caller, var_offset, &mut var_buffer) { - Ok(_) => match std::str::from_utf8(&var_buffer) { - Ok(s) => s, - Err(_) => return Err(Trap::BadSignature.into()), - }, - _ => return Err(Trap::MemoryOutOfBounds.into()), - }; - // Parse the variable data as JSON. - let var_json: serde_json::Value = match serde_json::from_str(var) { - Ok(v) => v, - Err(e) => { - error!("Error parsing var: {}", e); - return Err(Trap::BadSignature.into()); - } - }; - // println!("var_json: {:?}", var_json); - - let vm = jsonnet_make(); +#[async_trait] +impl super::jsonnet::Host for super::Host{ + async fn jsonnet_make(&mut self,) -> wasmtime::Result { + let ptr = arakoo_jsonnet::jsonnet_make(); + Ok(ptr as u64) + } + + async fn jsonnet_evaluate_snippet(&mut self, vm: u64,file:String, code: String) -> wasmtime::Result { + let out = arakoo_jsonnet::jsonnet_evaluate_snippet(vm as *mut arakoo_jsonnet::VM, &file, &code); + Ok(out) + } + + async fn jsonnet_evaluate_file(&mut self, vm: u64, path: String) -> wasmtime::Result { + let code = fs::read_to_string(&path).map_err(|e| { + error!("Failed to read file {}: {}", path, e); + io::Error::new(io::ErrorKind::Other, e) + })?; + let out = arakoo_jsonnet::jsonnet_evaluate_snippet(vm as *mut arakoo_jsonnet::VM, "snippet", &code); + Ok(out) + } + + async fn jsonnet_ext_string(&mut self, vm: u64, key: String, value: String) -> wasmtime::Result<()> { + arakoo_jsonnet::jsonnet_ext_string(vm as *mut arakoo_jsonnet::VM, &key, &value); + Ok(()) + } + + async fn jsonnet_destroy(&mut self, vm: u64) -> wasmtime::Result<()> { + arakoo_jsonnet::jsonnet_destroy(vm as *mut arakoo_jsonnet::VM); + Ok(()) + } +} - for (key, value) in var_json.as_object().unwrap() { - ext_string( - vm, - key, - value.as_str().expect("ext_string value is not a string"), - ); +// Bindings for jsonnet + +#[async_trait] +impl super::outbound_http::Host for super::Host { + async fn send_request( + &mut self, + req: super::http_types::Request, + ) -> wasmtime::Result> { + // println!("Sending request: {:?}", request); + Ok(async { + tracing::log::trace!("Attempting to send outbound HTTP request to {}", req.uri); + + let method = method_from(req.method); + let url = Url::parse(&req.uri).map_err(|_| HttpError::InvalidUrl)?; + let headers = + request_headers(req.headers).map_err(|_| HttpError::RuntimeError)?; + let body = req.body.unwrap_or_default().to_vec(); + + if !req.params.is_empty() { + tracing::log::warn!("HTTP params field is deprecated"); } - let code = fs::read_to_string(path).expect("File not found"); - let out = jsonnet_evaluate_snippet(vm, "deleteme", &code); - let mut output: std::sync::MutexGuard<'_, String> = output.lock().unwrap(); - *output = out; - Ok(()) - }, - )?; - - // Wrap the `jsonnet_output_len` function to be called from WebAssembly. - // This function returns the length of the output string. - let output_clone = output.clone(); - linker.func_wrap("arakoo", "jsonnet_output_len", move || -> i32 { - let output_clone = output_clone.clone(); - let output = output_clone.lock().unwrap().clone(); - output.len() as i32 - })?; + // Allow reuse of Client's internal connection pool for multiple requests + // in a single component execution + let client = self.client.get_or_insert_with(Default::default); + + let resp = client + .request(method, url) + .headers(headers) + .body(body) + .send() + .await + .map_err(log_reqwest_error)?; + tracing::log::trace!("Returning response from outbound request to {}", req.uri); + response_from_reqwest(resp).await + } + .await) + } +} - // Wrap the `jsonnet_output` function to be called from WebAssembly. - // This function writes the output string to the specified memory location. - linker.func_wrap( - "arakoo", - "jsonnet_output", - move |mut caller: Caller<'_, WasiCtx>, ptr: i32| { - let output_clone = output.clone(); - let mem = match caller.get_export("memory") { - Some(Extern::Memory(mem)) => mem, - _ => return Err(Trap::NullReference.into()), - }; - let offset = ptr as u32 as usize; - let out = output_clone.lock().unwrap().clone(); - match mem.write(&mut caller, offset, out.as_bytes()) { - Ok(_) => {} - _ => return Err(Trap::MemoryOutOfBounds.into()), - }; - Ok(()) - }, - )?; +pub fn log_reqwest_error(err: reqwest::Error) -> HttpError { + let error_desc = if err.is_timeout() { + "timeout error" + } else if err.is_connect() { + "connection error" + } else if err.is_body() || err.is_decode() { + "message body error" + } else if err.is_request() { + "request error" + } else { + "error" + }; + tracing::warn!( + "Outbound HTTP {}: URL {}, error detail {:?}", + error_desc, + err.url() + .map(|u| u.to_string()) + .unwrap_or_else(|| "".to_owned()), + err + ); + HttpError::RuntimeError +} - Ok(()) +pub fn method_from(m: Method) -> http::Method { + match m { + Method::Get => http::Method::GET, + Method::Post => http::Method::POST, + Method::Put => http::Method::PUT, + Method::Delete => http::Method::DELETE, + Method::Patch => http::Method::PATCH, + Method::Head => http::Method::HEAD, + Method::Options => http::Method::OPTIONS, + } } -pub fn add_fetch_to_linker(linker: &mut Linker) -> anyhow::Result<()> { - let response: Arc> = Arc::new(Mutex::new(String::new())); - let error: Arc> = Arc::new(Mutex::new(String::new())); +pub async fn response_from_reqwest(res: reqwest::Response) -> Result { + let status = res.status().as_u16(); + let headers = response_headers(res.headers()).map_err(|_| HttpError::RuntimeError)?; + let status_text = (&res.status().canonical_reason().unwrap_or("")).to_string(); + let body = Some( + res.bytes() + .await + .map_err(|_| HttpError::RuntimeError)? + .to_vec(), + ); + + Ok(Response { + status, + headers, + body, + status_text, + }) +} - let response_clone = response.clone(); - let error_clone = error.clone(); - linker.func_wrap( - "arakoo", - "fetch", - move |mut caller: Caller<'_, WasiCtx>, request_ptr: i32, request_len: i32| { - let response_arc = response_clone.clone(); - let error = error_clone.clone(); - let mem = match caller.get_export("memory") { - Some(Extern::Memory(mem)) => mem, - _ => { - let mut error = error.lock().unwrap(); - *error = "Memory not found".to_string(); - return Err(Trap::NullReference.into()); - } - }; - let request_offset = request_ptr as u32 as usize; - let mut request_buffer = vec![0; request_len as usize]; - let request = match mem.read(&caller, request_offset, &mut request_buffer) { - Ok(_) => match std::str::from_utf8(&request_buffer) { - Ok(s) => s.to_string(), // Clone the string here - Err(_) => { - let mut error = error.lock().unwrap(); - *error = "Bad signature".to_string(); - return Err(Trap::BadSignature.into()); - } - }, - _ => { - let mut error = error.lock().unwrap(); - *error = "Memory out of bounds".to_string(); - return Err(Trap::MemoryOutOfBounds.into()); - } - }; +pub fn request_headers(h: Headers) -> anyhow::Result { + let mut res = HeaderMap::new(); + for (k, v) in h { + res.insert( + http::header::HeaderName::try_from(k)?, + http::header::HeaderValue::try_from(v)?, + ); + } + Ok(res) +} - let thread_result = std::thread::spawn(move || { - Builder::new_current_thread() - .enable_all() - .build() - .unwrap() - .block_on(async { - let request: WasmInput = match serde_json::from_str(&request) { - Ok(r) => r, - Err(e) => { - return Err(anyhow::anyhow!("Error parsing request: {}", e)); - } - }; - let method: reqwest::Method = match request.method().parse() { - Ok(m) => m, - Err(_) => { - return Err(anyhow::anyhow!( - "Invalid method: {}", - request.method() - )); - } - }; - let client = reqwest::Client::new(); - let mut builder = client.request(method, request.url()); - let header = request.headers(); - for (k, v) in header { - builder = builder.header(k, v); - } - builder = builder.body(request.body().to_string()); - match builder.send().await { - Ok(r) => { - let response = WasmOutput::from_reqwest_response(r).await?; - Ok(response) - } - Err(e) => { - error!("Error sending request: {}", e); - return Err(anyhow::anyhow!("Error sending request: {}", e)); - } - } - }) - }) - .join(); - let response = match thread_result { - Ok(Ok(r)) => r, - Ok(Err(e)) => { - let mut error = error.lock().unwrap(); - *error = format!("Error sending request: {}", e); - error!("Error sending request: {}", e); - return Err(Trap::BadSignature.into()); - } - Err(_) => { - let mut error = error.lock().unwrap(); - *error = "Error sending request: thread join error".to_string(); - error!("Error sending request: thread join error"); - return Err(Trap::BadSignature.into()); - } - }; +pub fn response_headers(h: &HeaderMap) -> anyhow::Result>> { + let mut res: Vec<(String, String)> = vec![]; - let res = serde_json::to_string(&response).unwrap(); - let mut response = response_arc.lock().unwrap(); - *response = res; - Ok(()) - }, - )?; - // add the fetch_output_len and fetch_output functions here - let response_clone = response.clone(); - linker.func_wrap("arakoo", "get_response_len", move || -> i32 { - let response_clone = response_clone.clone(); - let response = response_clone.lock().unwrap().clone(); - response.len() as i32 - })?; + for (k, v) in h { + res.push(( + k.to_string(), + std::str::from_utf8(v.as_bytes())?.to_string(), + )); + } - // also add the fetch_error_len and fetch_error functions here - linker.func_wrap( - "arakoo", - "get_response", - move |mut _caller: Caller<'_, WasiCtx>, ptr: i32| { - let response_clone = response.clone(); - let mem = match _caller.get_export("memory") { - Some(Extern::Memory(mem)) => mem, - _ => return Err(Trap::NullReference.into()), - }; - let offset = ptr as u32 as usize; - let response = response_clone.lock().unwrap().clone(); - match mem.write(&mut _caller, offset, response.as_bytes()) { - Ok(_) => {} - _ => return Err(Trap::MemoryOutOfBounds.into()), - }; - Ok(()) - }, - )?; - Ok(()) + Ok(Some(res)) } diff --git a/JS/wasm/crates/serve/src/io.rs b/JS/wasm/crates/serve/src/io.rs index de7883cd5..30668a23c 100644 --- a/JS/wasm/crates/serve/src/io.rs +++ b/JS/wasm/crates/serve/src/io.rs @@ -3,13 +3,24 @@ use std::collections::HashMap; use hyper::{header::HOST, http::request::Parts, HeaderMap, Uri}; use serde::{Deserialize, Serialize}; +#[derive(Serialize,Deserialize,Debug,Clone)] +pub enum Method { + GET, + POST, + PUT, + DELETE, + PATCH, + HEAD, + OPTIONS, +} + #[derive(Serialize, Deserialize, Debug)] -pub struct WasmInput<'a> { - url: String, - method: &'a str, - headers: HashMap, - body: String, - params: HashMap, +pub struct WasmInput { + pub uri: String, + pub method: Method, + pub headers: Vec<(String, String)>, + pub body: Option>, + pub params: Vec<(String, String)>, } #[derive(Deserialize, Debug, Clone, Serialize)] @@ -18,51 +29,35 @@ pub struct WasmOutput { pub status: u16, #[serde(rename = "statusText")] pub status_text: String, - body: Option, + pub body: Option, } -impl<'a> WasmInput<'a> { - pub fn new(request: &'a Parts, body: String) -> Self { - let mut params = HashMap::new(); +impl WasmInput { + pub fn new(request: &Parts, body: String) -> Self { + let mut params: Vec<(String, String)> = vec![]; if let Some(query) = request.uri.query() { for pair in query.split('&') { let mut parts = pair.split('='); let key = parts.next().unwrap(); let value = parts.next().unwrap(); - params.insert(key.to_string(), value.to_string()); + params.push((key.to_string(), value.to_string())); } } - let url = Self::build_url(request); + let uri = Self::build_uri(request); Self { - url, - method: request.method.as_str(), - headers: Self::build_headers_hash(&request.headers), - body, + uri, + method: Self::build_method(&request.method), + headers: Self::build_headers(&request.headers), + body: Self::build_body(body), params, } } - pub fn url(&self) -> &str { - &self.url - } - - pub fn method(&self) -> &str { - self.method - } - pub fn headers(&self) -> &HashMap { - &self.headers - } - - pub fn body(&self) -> &str { - &self.body - } - - - fn build_url(request: &Parts) -> String { + fn build_uri(request: &Parts) -> String { Uri::builder() .scheme("http") .authority(request.headers.get(HOST).unwrap().to_str().unwrap()) @@ -72,19 +67,43 @@ impl<'a> WasmInput<'a> { .to_string() } - fn build_headers_hash(headers: &HeaderMap) -> HashMap { - let mut parsed_headers = HashMap::new(); + fn build_headers(req_headers: &HeaderMap) -> Vec<(String, String)> { + let mut headers: Vec<(String, String)> = vec![]; - for (key, value) in headers.iter() { - parsed_headers.insert( + for (key, value) in req_headers.iter() { + headers.push(( String::from(key.as_str()), - String::from(value.to_str().unwrap()), - ); + String::from(value.to_str().unwrap().to_string()), + )); } - parsed_headers + headers + } + + fn build_method(method: &hyper::Method) -> Method { + let method: Method = match method { + &hyper::Method::GET => Method::GET, + &hyper::Method::POST => Method::POST, + &hyper::Method::PUT => Method::PUT, + &hyper::Method::DELETE => Method::DELETE, + &hyper::Method::PATCH => Method::PATCH, + &hyper::Method::HEAD => Method::HEAD, + &hyper::Method::OPTIONS => Method::OPTIONS, + _ => Method::GET, + }; + method + } + + fn build_body(body:String) -> Option> { + if body.is_empty() { + None + } else { + Some(body.into_bytes()) + } } } + + impl WasmOutput { pub fn body(&self) -> String { self.body.clone().unwrap_or_default() diff --git a/JS/wasm/crates/serve/src/lib.rs b/JS/wasm/crates/serve/src/lib.rs index 6ec3c0d15..6e600d757 100644 --- a/JS/wasm/crates/serve/src/lib.rs +++ b/JS/wasm/crates/serve/src/lib.rs @@ -1,8 +1,13 @@ // mod binding; +use wit::arakoo::edgechains::http as outbound_http; +use wit::arakoo::edgechains::http_types::HttpError; +use wit::arakoo::edgechains::jsonnet; + mod binding; mod io; use std::{ + collections::HashMap, convert::Infallible, env, future::Future, @@ -14,7 +19,8 @@ use std::{ task::{self, Poll}, }; -use binding::add_fetch_to_linker; +use anyhow::Context; +// use binding::add_fetch_to_linker; // use binding::add_exports_to_linker; use futures::future::{self, Ready}; use hyper::{ @@ -27,16 +33,27 @@ use hyper::{ use tracing::{error, event, info, Level}; use tracing_subscriber::{filter::EnvFilter, FmtSubscriber}; -use wasi_common::WasiCtx; -use wasmtime_wasi::WasiCtxBuilder; +// use wasi_common::WasiCtx; +use wasmtime_wasi::{bindings, ResourceTable, WasiCtx, WasiCtxBuilder, WasiView}; -use wasmtime::{Caller, Config, Engine, Extern, Linker, Module, Store, Trap, WasmBacktraceDetails}; +use wasmtime::component::{Component, Linker}; +use wasmtime::{Config, Engine, Store, WasmBacktraceDetails}; +use wit::arakoo::edgechains::http_types; +use wit::exports::arakoo::edgechains::inbound_http::{self}; use crate::{ - binding::add_jsonnet_to_linker, + // binding::add_jsonnet_to_linker, io::{WasmInput, WasmOutput}, }; +mod wit { + wasmtime::component::bindgen!({ + path:"../../wit", + world:"reactor", + async:true + }); +} + #[derive(Clone)] pub struct RequestService { worker_ctx: WorkerCtx, @@ -52,37 +69,58 @@ impl RequestService { #[derive(Clone)] pub struct WorkerCtx { engine: Engine, - module: Module, + component: Component, +} + +struct Host { + table: ResourceTable, + wasi: WasiCtx, + client: Option, +} + +impl WasiView for Host { + fn table(&mut self) -> &mut ResourceTable { + &mut self.table + } + + fn ctx(&mut self) -> &mut wasmtime_wasi::WasiCtx { + &mut self.wasi + } } +impl http_types::Host for Host {} + impl WorkerCtx { - pub fn new(module_path: impl AsRef) -> anyhow::Result { + pub fn new(component_path: impl AsRef) -> anyhow::Result { tracing_subscriber(); - info!("Loading module from {:?}", module_path.as_ref()); + info!("Loading Component from {:?}", component_path.as_ref()); let mut binding = Config::default(); - let config = binding.async_support(true); + let config = binding.async_support(true).wasm_component_model(true); // check if env has debug flag if env::var("DEBUG").is_ok() { config .debug_info(true) .wasm_backtrace(true) + .wasm_threads(true) .coredump_on_trap(true) // Enable core dumps on trap .wasm_backtrace_details(WasmBacktraceDetails::Enable); } let engine = Engine::new(&config)?; - let module = Module::from_file(&engine, module_path)?; + let component = Component::from_file(&engine, component_path) + .with_context(|| format!("Failed to load component : invalid path"))?; - Ok(Self { engine, module }) + Ok(Self { engine, component }) } - pub fn module(&self) -> &Module { - &self.module + pub fn component(&self) -> &Component { + &self.component } pub fn engine(&self) -> &Engine { &self.engine } + pub async fn serve(self, addr: SocketAddr) -> Result<(), hyper::Error> { info!("Starting server ..."); let server = hyper::Server::bind(&addr).serve(self); @@ -100,6 +138,7 @@ impl WorkerCtx { let body = hyper::body::to_bytes(body).await.unwrap(); let body_str = String::from_utf8_lossy(&body).to_string(); let result = self.run(&parts, body_str).await; + match result { Ok(output) => { let mut response = Response::builder(); @@ -122,7 +161,7 @@ impl WorkerCtx { } Err(e) => { - error!("Error: {}", e); + error!("Error: {:?}", e); let response = Response::builder() .status(500) .body(Body::from("Internal Server Error")) @@ -139,105 +178,183 @@ impl WorkerCtx { /// functions to be called from WebAssembly. async fn run(&self, parts: &Parts, body: String) -> anyhow::Result { // Serialize the request parts and body into a JSON input for the WebAssembly module. - let input = serde_json::to_vec(&WasmInput::new(parts, body)).unwrap(); - let mem_len = input.len() as i32; + // let input = serde_json::to_vec(&WasmInput::new(parts, body)).unwrap(); + // let mem_len = input.len() as i32; // Create a new linker with the WASI context. - let mut linker: Linker = Linker::new(self.engine()); - wasmtime_wasi::add_to_linker(&mut linker, |ctx| ctx)?; + let mut linker = Linker::new(self.engine()); + // wasmtime_wasi::add_to_linker(&mut linker, |ctx| ctx)?; + bindings::cli::environment::add_to_linker(&mut linker, |x| x) + .expect("Unable to add environment"); + bindings::cli::exit::add_to_linker(&mut linker, |x| x).expect("Unable to add cli"); + bindings::io::error::add_to_linker(&mut linker, |x| x).expect("Unable to add io error"); + // bindings::sync::io::streams::add_to_linker(&mut linker, |x| x) + // .expect("Unable to add io streams"); + bindings::io::poll::add_to_linker(&mut linker, |x| x).expect("Unable to add io poll"); + bindings::io::streams::add_to_linker(&mut linker, |x| x).expect("Unable to add io streams"); + bindings::cli::stdin::add_to_linker(&mut linker, |x| x).expect("Unable to add cli stdin"); + bindings::cli::stdout::add_to_linker(&mut linker, |x| x).expect("Unable to add cli stdout"); + bindings::cli::stderr::add_to_linker(&mut linker, |x| x).expect("Unable to add cli stderr"); + bindings::cli::terminal_input::add_to_linker(&mut linker, |x| x) + .expect("Unable to add cli terminal input"); + bindings::cli::terminal_output::add_to_linker(&mut linker, |x| x) + .expect("Unable to add cli terminal output"); + bindings::cli::terminal_stdin::add_to_linker(&mut linker, |x| x) + .expect("Unable to add cli terminal stdin"); + bindings::cli::terminal_stdout::add_to_linker(&mut linker, |x| x) + .expect("Unable to add cli terminal stdout"); + bindings::cli::terminal_stderr::add_to_linker(&mut linker, |x| x) + .expect("Unable to add cli terminal stderr"); + bindings::clocks::monotonic_clock::add_to_linker(&mut linker, |x| x) + .expect("Unable to add clocks monotonic clock"); + bindings::clocks::wall_clock::add_to_linker(&mut linker, |x| x) + .expect("Unable to add clocks wallclock"); + // bindings::sync::filesystem::types::add_to_linker(&mut linker, |x| x) + // .expect("Unable to add filesystem types"); + bindings::filesystem::types::add_to_linker(&mut linker, |x| x) + .expect("Unable to add filesystem types"); + bindings::filesystem::preopens::add_to_linker(&mut linker, |x| x) + .expect("Unable to add filesystem preopens"); + bindings::random::random::add_to_linker(&mut linker, |x| x).expect("Unable to add random"); // Wrap the `get_request_len` function to be called from WebAssembly. // This function returns the length of the input buffer. - linker.func_wrap("arakoo", "get_request_len", move || -> i32 { mem_len })?; + // linker.func_wrap("arakoo", "get_request_len", move || -> i32 { mem_len })?; // Wrap the `get_request` function to be called from WebAssembly. // This function writes the input buffer to the specified memory location. - match linker.func_wrap( - "arakoo", - "get_request", - move |mut caller: Caller<'_, WasiCtx>, ptr: i32| { - let mem = match caller.get_export("memory") { - Some(Extern::Memory(mem)) => mem, - _ => return Err(Trap::NullReference.into()), - }; - let offset = ptr as u32 as usize; - match mem.write(&mut caller, offset, &input) { - Ok(_) => {} - _ => return Err(Trap::MemoryOutOfBounds.into()), - }; - Ok(()) - }, - ) { - Ok(_) => {} - Err(e) => { - println!("Error adding get_request: {}", e); - } - } + // match linker.func_wrap( + // "arakoo", + // "get_request", + // move |mut caller: Caller<'_, WasiCtx>, ptr: i32| { + // let mem = match caller.get_export("memory") { + // Some(Extern::Memory(mem)) => mem, + // _ => return Err(Trap::NullReference.into()), + // }; + // let offset = ptr as u32 as usize; + // match mem.write(&mut caller, offset, &input) { + // Ok(_) => {} + // _ => return Err(Trap::MemoryOutOfBounds.into()), + // }; + // Ok(()) + // }, + // ) { + // Ok(_) => {} + // Err(e) => { + // println!("Error adding get_request: {}", e); + // } + // } // Create a shared output buffer that will be used to store the result of the WebAssembly execution. - let output: Arc> = Arc::new(Mutex::new(WasmOutput::new())); - let output_clone = output.clone(); + // let output: Arc> = Arc::new(Mutex::new(WasmOutput::new())); + // let output_clone = output.clone(); // Wrap the `set_output` function to be called from WebAssembly. // This function reads the output buffer from the specified memory location and updates the shared output buffer. - linker.func_wrap( - "arakoo", - "set_output", - move |mut caller: Caller<'_, WasiCtx>, ptr: i32, len: i32| { - let output = output_clone.clone(); - let mem = match caller.get_export("memory") { - Some(Extern::Memory(mem)) => mem, - _ => return Err(Trap::NullReference.into()), - }; - let offset = ptr as u32 as usize; - let mut buffer = vec![0; len as usize]; - match mem.read(&caller, offset, &mut buffer) { - Ok(_) => match serde_json::from_slice::(&buffer) { - Ok(parsed_output) => { - let mut output = output.lock().unwrap(); - *output = parsed_output; - Ok(()) - } - Err(_e) => Err(Trap::BadSignature.into()), - }, - _ => Err(Trap::MemoryOutOfBounds.into()), - } - }, - )?; + // linker.func_wrap( + // "arakoo", + // "set_output", + // move |mut caller: Caller<'_, WasiCtx>, ptr: i32, len: i32| { + // let output = output_clone.clone(); + // let mem = match caller.get_export("memory") { + // Some(Extern::Memory(mem)) => mem, + // _ => return Err(Trap::NullReference.into()), + // }; + // let offset = ptr as u32 as usize; + // let mut buffer = vec![0; len as usize]; + // match mem.read(&caller, offset, &mut buffer) { + // Ok(_) => match serde_json::from_slice::(&buffer) { + // Ok(parsed_output) => { + // let mut output = output.lock().unwrap(); + // *output = parsed_output; + // Ok(()) + // } + // Err(_e) => Err(Trap::BadSignature.into()), + // }, + // _ => Err(Trap::MemoryOutOfBounds.into()), + // } + // }, + // )?; // Add additional exports to the linker, such as Jsonnet evaluation functions. - add_jsonnet_to_linker(&mut linker)?; - add_fetch_to_linker(&mut linker)?; + // add_jsonnet_to_linker(&mut linker)?; + // add_fetch_to_linker(&mut linker)?; // Create a WASI context builder with inherited standard output and error streams. - let wasi_builder = WasiCtxBuilder::new() + let wasi = WasiCtxBuilder::new() .inherit_stdout() .inherit_stderr() .build(); + let table: ResourceTable = ResourceTable::new(); + // Create a new store with the WASI context. - let mut store = Store::new(self.engine(), wasi_builder); + let mut store = Store::new( + self.engine(), + Host { + table, + wasi, + client: None, + }, + ); // Instantiate the WebAssembly module with the linker and store. - linker.module(&mut store, "", self.module())?; + // linker.module(&mut store, "", self.module())?; // Get the entrypoint function from the WebAssembly instance and call it. - let instance = linker - .instantiate_async(&mut store, self.module()) - .await - .map_err(anyhow::Error::msg)?; - let run_entrypoint_fn = instance.get_typed_func::<(), ()>(&mut store, "run_entrypoint")?; - run_entrypoint_fn - .call_async(&mut store, ()) - .await - .map_err(anyhow::Error::msg)?; + // let instance = linker + // .instantiate_async(&mut store, self.module()) + // .await + // .map_err(anyhow::Error::msg)?; + // let run_entrypoint_fn = instance.get_typed_func::<(), ()>(&mut store, "run_entrypoint")?; + // run_entrypoint_fn + // .call_async(&mut store, ()) + // .await + // .map_err(anyhow::Error::msg)?; + let wasm_input = WasmInput::new(parts, body); + let request = inbound_http::Request { + method: match wasm_input.method { + io::Method::GET => http_types::Method::Get, + io::Method::POST => http_types::Method::Post, + io::Method::PUT => http_types::Method::Put, + io::Method::DELETE => http_types::Method::Delete, + io::Method::PATCH => http_types::Method::Patch, + io::Method::HEAD => http_types::Method::Head, + io::Method::OPTIONS => http_types::Method::Options, + }, + uri: wasm_input.uri, + headers: wasm_input.headers, + params: wasm_input.params, + body: wasm_input.body, + }; + wit::Reactor::add_to_linker(&mut linker, |x| x)?; + let (reactor, instance) = + wit::Reactor::instantiate_async(&mut store, self.component(), &linker).await?; + let guest = reactor.arakoo_edgechains_inbound_http(); + let result: Result = + guest.call_handle_request(&mut store, &request).await; + let mut wasm_output = WasmOutput::new(); + // println!("Result of guest calling: {:?}", &result); + match result { + Ok(res) => { + wasm_output.status = res.status; + wasm_output.status_text = res.status_text; + let mut headers_map = HashMap::new(); + for (key, val) in res.headers.unwrap().iter() { + headers_map.insert(key.to_owned(), val.to_owned()); + } + wasm_output.headers = headers_map; + let body_vec = res.body.unwrap(); + if body_vec.len() > 0 { + wasm_output.body = Some(String::from_utf8(body_vec).unwrap()); + } + } + Err(err) => println!("Error occured : {:?}", err), + }; // Drop the store to release resources. drop(store); - - // Lock the output buffer and return a clone of the result. - let output = output.lock().unwrap().clone(); - Ok(output) + Ok(wasm_output) } fn make_service(&self) -> RequestService { RequestService::new(self.clone()) diff --git a/JS/wasm/examples/axios/build.js b/JS/wasm/examples/axios/build.js new file mode 100644 index 000000000..cb5cd3950 --- /dev/null +++ b/JS/wasm/examples/axios/build.js @@ -0,0 +1,51 @@ +import { build } from "esbuild"; +import fs from "fs"; +let runtime = process.argv[2]; + +// let jsonnetLoadPlugin = { +// name: "jsonnet-load", +// setup(build) { +// build.onLoad({ filter: /\.jsonnet$/ }, async (args) => { +// let code = await fs.promises.readFile(args.path, "utf-8"); +// return { +// contents: code, +// loader: "text", +// }; +// }) +// } +// } + + +build({ + entryPoints: ["src/index.js"], + // entryPoints: ["src/index.js"], + bundle: true, + // minify: true, + minifySyntax: true, + outfile: "bin/app.js", + // outdir: "bin", + // inject:["shim.js"], + // define:{ + // "export":"_export" + // }, + format: "cjs", + target: "esnext", + external: ["axios"], + // banner: { + // js: ` + // function require (name) { + // console.log("Requiring",name) + // return __require_map[name]; + // }` + // }, + platform: "node", + loader: { + ".jsonnet": "copy", + }, + define: { + "process.env.arakoo": JSON.stringify(runtime === "arakoo"), + }, +}).catch((error) => { + console.error(error); + process.exit(1); +}); diff --git a/JS/wasm/examples/axios/package.json b/JS/wasm/examples/axios/package.json new file mode 100644 index 000000000..9cf337acb --- /dev/null +++ b/JS/wasm/examples/axios/package.json @@ -0,0 +1,24 @@ +{ + "name": "axios", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "@vespaiach/axios-fetch-adapter": "^0.3.1", + "axios": "1.7.0-beta.2", + "hono": "^3.9", + "javy": "^0.1.2" + }, + "devDependencies": { + "esbuild": "^0.21.3", + "webpack": "^5.91.0", + "webpack-cli": "^5.1.4" + }, + "type": "module" +} diff --git a/JS/wasm/examples/axios/src/index.js b/JS/wasm/examples/axios/src/index.js new file mode 100644 index 000000000..1eadb37f5 --- /dev/null +++ b/JS/wasm/examples/axios/src/index.js @@ -0,0 +1,58 @@ +import { Hono } from "hono"; +const app = new Hono(); +import { writeFileSync, STDIO } from "javy/fs" + +app.get("/", (c) => { + console.log("axios") + return c.text("Hello World!"); +}); + +function writeOutput(output) { + const encodedOutput = new TextEncoder().encode(JSON.stringify(output)); + const buffer = new Uint8Array(encodedOutput); + writeFileSync(STDIO.Stdout, buffer) +} + +app.get("/get", async (c) => { + console.log("In axios") + let result = await axios.get("https://dummy.restapiexample.com/api/v1/employee/1"); + let json = result.data; + return c.json(json); +}) + +app.get("/post", async (c) => { + console.log("post") + // let result = await axios.post("https://dummy.restapiexample.com/api/v1/create", { + // name: "test", + // salary: "123", + // age: "23" + // }); + // let json = result.data; + let result = await axios.post("https://jsonplaceholder.typicode.com/posts", { + title: 'foo', + body: 'bar', + userId: 1, + }); + let json = result.data; + return c.json(json); +}) + +app.get("/fetch", async (c) => { + try { + let result = await fetch("https://jsonplaceholder.typicode.com/todos/1"); + let body = await result.json() + return c.json(body); + } catch (error) { + console.log("Error occured - ") + writeOutput(error); + console.log(error); + return c.json(error) + } + +}) + +globalThis._export = app; + +// let result = await axios.get("https://jsonplaceholder.typicode.com/todos/1"); +// let json = result.data; +// console.log(json); \ No newline at end of file diff --git a/JS/wasm/examples/axios/wasi_snapshot_preview1.reactor.wasm b/JS/wasm/examples/axios/wasi_snapshot_preview1.reactor.wasm new file mode 100644 index 000000000..89b68cf7a Binary files /dev/null and b/JS/wasm/examples/axios/wasi_snapshot_preview1.reactor.wasm differ diff --git a/JS/wasm/examples/ec-wasmjs-hono/build.js b/JS/wasm/examples/ec-wasmjs-hono/build.js index 57c3e469d..6c346df91 100644 --- a/JS/wasm/examples/ec-wasmjs-hono/build.js +++ b/JS/wasm/examples/ec-wasmjs-hono/build.js @@ -1,4 +1,5 @@ import { build } from "esbuild"; +import textReplace from "esbuild-plugin-text-replace"; import fs from "fs"; let runtime = process.argv[2]; @@ -23,6 +24,18 @@ build({ minifySyntax: true, // outfile: "bin/app.js", outdir: "bin", + // inject:["shim.js"], + // define:{ + // "export":"_export" + // }, + plugins:[ + textReplace({ + include:/src\/index.js$/, + pattern:[ + ["export default","_export = "] + ] + }) + ], format: "esm", target: "esnext", platform: "node", diff --git a/JS/wasm/examples/ec-wasmjs-hono/package.json b/JS/wasm/examples/ec-wasmjs-hono/package.json index df62e5ce1..3fdd21070 100644 --- a/JS/wasm/examples/ec-wasmjs-hono/package.json +++ b/JS/wasm/examples/ec-wasmjs-hono/package.json @@ -8,13 +8,14 @@ "devDependencies": { "@planetscale/database": "^1.4.0", "esbuild": "^0.19", + "esbuild-plugin-text-replace": "^1.3.0", "hono": "^3.9" }, "dependencies": { + "@arakoodev/jsonnet": "file:../../../jsonnet/", "@hono/node-server": "^1.3.1", "axios": "^1.6.2", "crypto": "^1.0.1", - "@arakoodev/jsonnet": "file:../../../jsonnet/", "http": "^0.0.1-security", "stream": "^0.0.2" } diff --git a/JS/wasm/examples/ec-wasmjs-hono/src/index.js b/JS/wasm/examples/ec-wasmjs-hono/src/index.js index 76029fb89..d1af19b79 100644 --- a/JS/wasm/examples/ec-wasmjs-hono/src/index.js +++ b/JS/wasm/examples/ec-wasmjs-hono/src/index.js @@ -24,7 +24,7 @@ app.get("/", (c) => { app.get("/file", (c) => { try { - let result = jsonnet.extString("extName", "Mohan").evaluateFile("example.jsonnet"); + let result = jsonnet.extString("extName", "Mohan").evaluateFile("/home/afshan/EdgeChains/JS/wasm/examples/ec-wasmjs-hono/src/example.jsonnet"); return c.json(JSON.parse(result)); } catch (error) { console.log("Error occured"); @@ -66,4 +66,5 @@ app.notFound((c) => { return c.text("404 not found", 404); }); -app.fire(); +// app.fire(); +_export = app; diff --git a/JS/wasm/wit/arakoo.wit b/JS/wasm/wit/arakoo.wit index f4ea1ddba..a4433c5f2 100644 --- a/JS/wasm/wit/arakoo.wit +++ b/JS/wasm/wit/arakoo.wit @@ -1,5 +1,7 @@ -use * from http-types - - -fetch-request: func(request: http-request) -> expected +package arakoo:edgechains; +world reactor { + import http; + import jsonnet; + export inbound-http; +} \ No newline at end of file diff --git a/JS/wasm/wit/http-types.wit b/JS/wasm/wit/http-types.wit index 168cafc4a..869fecd36 100644 --- a/JS/wasm/wit/http-types.wit +++ b/JS/wasm/wit/http-types.wit @@ -1,45 +1,42 @@ - type uri = string - type http-status = u16 - type http-header = tuple - type http-headers = list - enum http-method { - get, - post, - put, - patch, - delete, - options, - head - } - type http-param = tuple - type http-params = list - type http-body = list - record http-request { - body: option, - headers: http-headers, - method: http-method, - params: http-params, - uri: uri, - } - record http-request-error { - error: http-error, - message: string - } - record http-response { - body: option, - headers: http-headers, - status: http-status, - } - enum http-error { - invalid-request, - invalid-request-body, - invalid-response-body, - not-allowed, - internal-error, - timeout, - redirect-loop, - } - enum file-error { - not-found, - invalid-path, - } +interface http-types { + type http-status = u16; + type body = list; + type headers = list>; + type params = list>; + type uri = string; + + enum method { + get, + post, + put, + delete, + patch, + head, + options, + } + + record request { + method: method, + uri: uri, + headers: headers, + params: params, + body: option, + } + + record response { + status: http-status, + headers: option, + body: option, + status-text: string, + } + + enum http-error { + success, + destination-not-allowed, + invalid-url, + request-error, + runtime-error, + too-many-requests, + } +} + diff --git a/JS/wasm/wit/http.wit b/JS/wasm/wit/http.wit new file mode 100644 index 000000000..6ca943cfd --- /dev/null +++ b/JS/wasm/wit/http.wit @@ -0,0 +1,5 @@ +interface http { + use http-types.{request, response, http-error}; + + send-request: func(req: request) -> result; +} diff --git a/JS/wasm/wit/inbound-http.wit b/JS/wasm/wit/inbound-http.wit new file mode 100644 index 000000000..31bb2b774 --- /dev/null +++ b/JS/wasm/wit/inbound-http.wit @@ -0,0 +1,5 @@ +interface inbound-http { + use http-types.{request, response, method}; + + handle-request: func(req: request) -> response; +} \ No newline at end of file diff --git a/JS/wasm/wit/jsonnet.wit b/JS/wasm/wit/jsonnet.wit new file mode 100644 index 000000000..dd9e200f8 --- /dev/null +++ b/JS/wasm/wit/jsonnet.wit @@ -0,0 +1,10 @@ +interface jsonnet { + record vars { + key:string + } + jsonnet-make: func() -> u64; + jsonnet-evaluate-snippet: func(vm: u64,file: string,code: string) -> string; + jsonnet-evaluate-file: func(vm: u64,path: string) -> string; + jsonnet-ext-string: func(vm: u64,key: string, value: string); + jsonnet-destroy: func(vm: u64); +}