diff --git a/packages/cli/src/lib/defaults/build-images/wasm/rust/Dockerfile.mustache b/packages/cli/src/lib/defaults/build-images/wasm/rust/Dockerfile.mustache index e90873246c..5efaee0309 100644 --- a/packages/cli/src/lib/defaults/build-images/wasm/rust/Dockerfile.mustache +++ b/packages/cli/src/lib/defaults/build-images/wasm/rust/Dockerfile.mustache @@ -1,4 +1,4 @@ -FROM rustlang/rust:nightly-slim as base +FROM rust:1.60.0 as base # Install the wasm32 rust build target RUN rustup target add wasm32-unknown-unknown @@ -7,7 +7,7 @@ WORKDIR /build-deps # Install curl RUN apt-get update -RUN apt-get -y install curl +RUN apt-get -y install curl clang llvm build-essential # Install wasm-opt RUN curl -L https://github.com/WebAssembly/binaryen/releases/download/version_101/binaryen-version_101-x86_64-linux.tar.gz | tar -xz \ @@ -16,10 +16,13 @@ RUN curl -L https://github.com/WebAssembly/binaryen/releases/download/version_10 && rm -rf binary-version_101 # Install the toml-cli -RUN cargo install toml-cli +RUN cargo install -f toml-cli # Install wasm-snip -RUN cargo install wasm-snip +RUN cargo install -f wasm-snip + +# Install wasm-bindgen +RUN cargo install -f wasm-bindgen-cli {{#polywrap_linked_packages.length}} WORKDIR /linked-packages @@ -55,38 +58,50 @@ RUN PACKAGE_NAME={{name}}; \ {{/polywrap_module}} true {{/polywrap_linked_packages}} + {{/polywrap_linked_packages.length}} -# Remove any Cargo.lock files {{#polywrap_module}} +# Remove any Cargo.lock files RUN rm -rf {{dir}}/Cargo.lock # Ensure the Wasm module is configured to use imported memory ENV RUSTFLAGS="-C link-arg=-z -C link-arg=stack-size=65536 -C link-arg=--import-memory" -# Cleanup an artifact left by the toml CLI program ("[]" -> []) -RUN sed -i 's/"\[\]"/\[\]/g' ./{{dir}}/Cargo.toml - # Ensure the module at {{dir}} has the crate-type = ["cdylib"] -RUN toml set ./{{dir}}/Cargo.toml lib.crate-type ["cdylib","rlib"] > ./{{dir}}/Cargo-local.toml && \ +RUN toml set ./{{dir}}/Cargo.toml lib.crate-type ["cdylib"] > ./{{dir}}/Cargo-local.toml && \ rm -rf ./{{dir}}/Cargo.toml && \ mv ./{{dir}}/Cargo-local.toml ./{{dir}}/Cargo.toml && \ true -# Clean up artifacts left by the toml CLI program ("["cdylib", "rlib"]" -> ["cdylib", "rlib"]) -RUN sed -i 's/"\[cdylib,rlib\]"/\["cdylib","rlib"\]/g' ./{{dir}}/Cargo.toml +# Clean up artifacts left by the toml CLI program ("["cdylib"]" -> ["cdylib"]) +RUN sed -i 's/"\[cdylib\]"/\["cdylib"\]/g' ./{{dir}}/Cargo.toml -# Build the module at {{dir}} -RUN cargo +nightly build --manifest-path ./{{dir}}/Cargo.toml \ - --target wasm32-unknown-unknown --release +# Ensure the package name = "module" +RUN toml set ./{{dir}}/Cargo.toml package.name "module" > ./{{dir}}/Cargo-local.toml && \ + rm -rf ./{{dir}}/Cargo.toml && \ + mv ./{{dir}}/Cargo-local.toml ./{{dir}}/Cargo.toml && \ + true # Make the build directory RUN rm -rf ./build RUN mkdir ./build +# Build the module at {{dir}} +RUN cargo build --manifest-path ./{{dir}}/Cargo.toml \ + --target wasm32-unknown-unknown --release + +# Enable the "WASM_INTERFACE_TYPES" feature, which will remove the __wbindgen_throw import. +# See: https://github.com/rustwasm/wasm-bindgen/blob/7f4663b70bd492278bf0e7bba4eeddb3d840c868/crates/cli-support/src/lib.rs#L397-L403 +ENV WASM_INTERFACE_TYPES=1 + +# Run wasm-bindgen over the module, replacing all placeholder __wbindgen_... imports +RUN wasm-bindgen ./{{dir}}/target/wasm32-unknown-unknown/release/module.wasm --out-dir ./build --out-name bg_module.wasm + +RUN wasm-snip ./build/bg_module.wasm -o ./build/snipped_module.wasm && \ + rm -rf ./build/bg_module.wasm + # Use wasm-opt to perform the "asyncify" post-processing step over all modules -RUN WASM_MODULE=$(ls ./{{dir}}/target/wasm32-unknown-unknown/release/*.wasm); \ - wasm-snip $WASM_MODULE -o ./build/snipped_{{name}}.wasm && \ - wasm-opt --asyncify -Os ./build/snipped_{{name}}.wasm -o ./build/{{name}}.wasm && \ - rm -rf ./build/snipped_{{name}}.wasm +RUN wasm-opt --asyncify -Os ./build/snipped_module.wasm -o ./build/module.wasm && \ + rm -rf ./build/snipped_module.wasm {{/polywrap_module}} diff --git a/packages/js/asyncify/src/AsyncWasmInstance.ts b/packages/js/asyncify/src/AsyncWasmInstance.ts index 7ca2f7a2d1..f43845cf93 100644 --- a/packages/js/asyncify/src/AsyncWasmInstance.ts +++ b/packages/js/asyncify/src/AsyncWasmInstance.ts @@ -44,9 +44,7 @@ export class AsyncWasmInstance { private constructor() {} - public static createMemory(config: { module: ArrayBuffer }): WasmMemory { - const bytecode = new Uint8Array(config.module); - + public static createMemory(config: { module: Uint8Array }): WasmMemory { // extract the initial memory page size, as it will // throw an error if the imported page size differs: // https://chromium.googlesource.com/v8/v8/+/644556e6ed0e6e4fac2dfabb441439820ec59813/src/wasm/module-instantiate.cc#924 @@ -73,7 +71,7 @@ export class AsyncWasmInstance { // 0x__, ]); - const sigIdx = indexOfArray(bytecode, envMemoryImportSignature); + const sigIdx = indexOfArray(config.module, envMemoryImportSignature); if (sigIdx < 0) { throw Error( @@ -86,7 +84,7 @@ export class AsyncWasmInstance { // Extract the initial memory page-range size const memoryInitalLimits = - bytecode[sigIdx + envMemoryImportSignature.length + 1]; + config.module[sigIdx + envMemoryImportSignature.length + 1]; if (memoryInitalLimits === undefined) { throw Error( @@ -98,7 +96,7 @@ export class AsyncWasmInstance { } public static async createInstance(config: { - module: ArrayBuffer; + module: Uint8Array; imports: WasmImports; requiredExports?: readonly string[]; }): Promise { diff --git a/packages/js/client/src/PolywrapClient.ts b/packages/js/client/src/PolywrapClient.ts index 013c957e44..96db5439e1 100644 --- a/packages/js/client/src/PolywrapClient.ts +++ b/packages/js/client/src/PolywrapClient.ts @@ -18,6 +18,7 @@ import { InterfaceImplementations, InvokeOptions, InvokeResult, + InvokerOptions, PluginRegistration, QueryOptions, QueryResult, @@ -47,6 +48,8 @@ import { JobRunner, PluginPackage, RunOptions, + msgpackEncode, + msgpackDecode, } from "@polywrap/core-js"; import { Tracer } from "@polywrap/tracing-js"; @@ -197,7 +200,7 @@ export class PolywrapClient implements Client { public async getFile( uri: TUri, options: GetFileOptions - ): Promise { + ): Promise { const wrapper = await this._loadWrapper(this._toUri(uri), options); const client = contextualizeClient(this, options.contextId); return await wrapper.getFile(options, client); @@ -309,14 +312,14 @@ export class PolywrapClient implements Client { @Tracer.traceMethod("PolywrapClient: invoke") public async invoke( - options: InvokeOptions + options: InvokerOptions ): Promise> { const { contextId, shouldClearContext } = this._setContext( options.contextId, options.config ); - let result: InvokeResult; + let error: Error | undefined; try { const typedOptions: InvokeOptions = { @@ -327,18 +330,39 @@ export class PolywrapClient implements Client { const wrapper = await this._loadWrapper(typedOptions.uri, { contextId }); - result = (await wrapper.invoke( + const invocableResult = await wrapper.invoke( typedOptions, contextualizeClient(this, contextId) - )) as TData; - } catch (error) { - result = { error }; + ); + + if (invocableResult.data !== undefined) { + if (options.encodeResult && !invocableResult.encoded) { + return { + // TODO: if options.encodeResult, fix return type to Uint8Array + data: (msgpackEncode(invocableResult.data) as unknown) as TData, + }; + } else if (invocableResult.encoded && !options.encodeResult) { + return { + // TODO: if result.encoded, fix return type to Uint8Array + data: msgpackDecode(invocableResult.data as Uint8Array) as TData, + }; + } else { + return { + data: invocableResult.data as TData, + }; + } + } else { + error = invocableResult.error; + } + } catch (e) { + error = e; } if (shouldClearContext) { this._clearContext(contextId); } - return result; + + return { error }; } @Tracer.traceMethod("PolywrapClient: run") diff --git a/packages/js/client/src/__tests__/core/interface-impls.spec.ts b/packages/js/client/src/__tests__/core/interface-impls.spec.ts index 266ef84d6c..732026e0f2 100644 --- a/packages/js/client/src/__tests__/core/interface-impls.spec.ts +++ b/packages/js/client/src/__tests__/core/interface-impls.spec.ts @@ -83,7 +83,7 @@ describe("interface-impls", () => { { uri: implementation4Uri, plugin: { - factory: () => ({} as PluginModule), + factory: () => ({} as PluginModule<{}>), manifest: { schema: "", implements: [], @@ -145,7 +145,7 @@ describe("interface-impls", () => { { uri: interface1Uri, plugin: { - factory: () => ({} as PluginModule), + factory: () => ({} as PluginModule<{}>), manifest: { schema: "", implements: [], @@ -155,7 +155,7 @@ describe("interface-impls", () => { { uri: interface2Uri, plugin: { - factory: () => ({} as PluginModule), + factory: () => ({} as PluginModule<{}>), manifest: { schema: "", implements: [], @@ -197,7 +197,7 @@ describe("interface-impls", () => { { uri: interfaceUri, plugin: { - factory: () => ({} as PluginModule), + factory: () => ({} as PluginModule<{}>), manifest: { schema: "", implements: [], @@ -295,7 +295,7 @@ describe("interface-impls", () => { { uri: implementation1Uri, plugin: { - factory: () => ({} as PluginModule), + factory: () => ({} as PluginModule<{}>), manifest: { schema: "", implements: [new Uri(interfaceUri)], @@ -330,7 +330,7 @@ describe("interface-impls", () => { { uri: implementation1Uri, plugin: { - factory: () => ({} as PluginModule), + factory: () => ({} as PluginModule<{}>), manifest: { schema: "", implements: [], diff --git a/packages/js/client/src/__tests__/core/resolveUri.spec.ts b/packages/js/client/src/__tests__/core/resolveUri.spec.ts index 7e7209394e..e4635a2587 100644 --- a/packages/js/client/src/__tests__/core/resolveUri.spec.ts +++ b/packages/js/client/src/__tests__/core/resolveUri.spec.ts @@ -190,7 +190,7 @@ describe("resolveUri", () => { uri: pluginUri.uri, plugin: { factory: () => { - return ({} as unknown) as PluginModule; + return ({} as unknown) as PluginModule<{}>; }, manifest: { schema: "", diff --git a/packages/js/client/src/__tests__/core/wasm-wrapper.spec.ts b/packages/js/client/src/__tests__/core/wasm-wrapper.spec.ts index f58ce78a34..6ea30b2d41 100644 --- a/packages/js/client/src/__tests__/core/wasm-wrapper.spec.ts +++ b/packages/js/client/src/__tests__/core/wasm-wrapper.spec.ts @@ -113,13 +113,13 @@ describe("wasm-wrapper", () => { networkNameOrChainId: "testnet", }, }, - noDecode: true, + encodeResult: true, }); expect(result.error).toBeFalsy(); expect(result.data).toBeTruthy(); - expect(result.data instanceof ArrayBuffer).toBeTruthy(); - expect(msgpackDecode(result.data as ArrayBuffer)).toContain("0x"); + expect(result.data instanceof Uint8Array).toBeTruthy(); + expect(msgpackDecode(result.data as Uint8Array)).toContain("0x"); }); it("should invoke wrapper with custom redirects", async () => { @@ -291,9 +291,9 @@ describe("wasm-wrapper", () => { ): Int! `); - const fileBuffer: ArrayBuffer = (await client.getFile(wrapperUri, { + const fileBuffer: Uint8Array = (await client.getFile(wrapperUri, { path: manifest.schema!, - })) as ArrayBuffer; + })) as Uint8Array; const decoder = new TextDecoder("utf8"); const text = decoder.decode(fileBuffer); expect(text).toContain(`getData( diff --git a/packages/js/client/src/plugin/PluginWrapper.ts b/packages/js/client/src/plugin/PluginWrapper.ts index 38c44e3b53..79116087a5 100644 --- a/packages/js/client/src/plugin/PluginWrapper.ts +++ b/packages/js/client/src/plugin/PluginWrapper.ts @@ -3,7 +3,7 @@ import { Client, GetManifestOptions, InvokeOptions, - InvokeResult, + InvocableResult, PluginModule, PluginPackage, Uri, @@ -11,8 +11,8 @@ import { ManifestArtifactType, GetFileOptions, Env, - msgpackEncode, msgpackDecode, + isBuffer, } from "@polywrap/core-js"; import { Tracer } from "@polywrap/tracing-js"; @@ -51,15 +51,15 @@ export class PluginWrapper extends Wrapper { public async getFile( _options: GetFileOptions, _client: Client - ): Promise { + ): Promise { throw Error("client.getFile(...) is not implemented for Plugins."); } @Tracer.traceMethod("PluginWrapper: invoke") - public async invoke( + public async invoke( options: InvokeOptions, client: Client - ): Promise> { + ): Promise> { try { const { method } = options; const args = options.args || {}; @@ -79,7 +79,7 @@ export class PluginWrapper extends Wrapper { let jsArgs: Record; // If the args are a msgpack buffer, deserialize it - if (args instanceof ArrayBuffer) { + if (isBuffer(args)) { const result = msgpackDecode(args); Tracer.addEvent("msgpack-decoded", result); @@ -97,36 +97,16 @@ export class PluginWrapper extends Wrapper { // Invoke the function try { - const result = (await module._wrap_invoke( - method, - jsArgs, - client - )) as TData; + const result = await module._wrap_invoke(method, jsArgs, client); if (result !== undefined) { const data = result as unknown; - if (process.env.TEST_PLUGIN) { - // try to encode the returned result, - // ensuring it's msgpack compliant - try { - msgpackEncode(data); - } catch (e) { - throw Error( - `TEST_PLUGIN msgpack encode failure.` + - `uri: ${this._uri.uri}\nmodule: ${module}\n` + - `method: ${method}\n` + - `args: ${JSON.stringify(jsArgs, null, 2)}\n` + - `result: ${JSON.stringify(data, null, 2)}\n` + - `exception: ${e}` - ); - } - } - Tracer.addEvent("Result", data); return { - data: data as TData, + data: data, + encoded: false, }; } else { return {}; diff --git a/packages/js/client/src/wasm/WasmWrapper.ts b/packages/js/client/src/wasm/WasmWrapper.ts index 56e82b8d80..29e43bb189 100644 --- a/packages/js/client/src/wasm/WasmWrapper.ts +++ b/packages/js/client/src/wasm/WasmWrapper.ts @@ -5,6 +5,7 @@ import { createImports } from "./imports"; import { InvokeOptions, InvokeResult, + InvocableResult, Wrapper, PolywrapManifest, Uri, @@ -20,13 +21,13 @@ import { UriResolverInterface, GetFileOptions, msgpackEncode, - msgpackDecode, + isBuffer, } from "@polywrap/core-js"; import { Tracer } from "@polywrap/tracing-js"; import { AsyncWasmInstance } from "@polywrap/asyncify-js"; type InvokeResultOrError = - | { type: "InvokeResult"; invokeResult: ArrayBuffer } + | { type: "InvokeResult"; invokeResult: Uint8Array } | { type: "InvokeError"; invokeError: string }; const hasExport = (name: string, exports: Record): boolean => { @@ -39,36 +40,36 @@ const hasExport = (name: string, exports: Record): boolean => { export interface State { method: string; - args: ArrayBuffer; + args: Uint8Array; invoke: { - result?: ArrayBuffer; + result?: Uint8Array; error?: string; }; subinvoke: { - result?: ArrayBuffer; + result?: Uint8Array; error?: string; args: unknown[]; }; subinvokeImplementation: { - result?: ArrayBuffer; + result?: Uint8Array; error?: string; args: unknown[]; }; invokeResult: InvokeResult; - getImplementationsResult?: ArrayBuffer; + getImplementationsResult?: Uint8Array; sanitizeEnv: { - args?: ArrayBuffer; - result?: ArrayBuffer; + args?: Uint8Array; + result?: Uint8Array; }; - env?: ArrayBuffer; + env?: Uint8Array; } export class WasmWrapper extends Wrapper { public static requiredExports: readonly string[] = ["_wrap_invoke"]; private _schema?: string; - private _wasm: ArrayBuffer | undefined = undefined; - private _sanitizedEnv: ArrayBuffer | undefined = undefined; + private _wasm: Uint8Array | undefined = undefined; + private _sanitizedEnv: Uint8Array | undefined = undefined; constructor( private _uri: Uri, @@ -136,12 +137,14 @@ export class WasmWrapper extends Wrapper { public async getFile( options: GetFileOptions, client: Client - ): Promise { + ): Promise { const { path, encoding } = options; const { data, error } = await UriResolverInterface.Query.getFile( - ( - options: InvokeOptions - ): Promise> => client.invoke(options), + { + invoke: ( + options: InvokeOptions + ): Promise> => client.invoke(options), + }, // TODO: support all types of URI resolvers (cache, etc) new Uri(this._uriResolver), combinePaths(this._uri.path, path) @@ -176,9 +179,9 @@ export class WasmWrapper extends Wrapper { public async invoke( options: InvokeOptions, client: Client - ): Promise> { + ): Promise> { try { - const { method, noDecode } = options; + const { method } = options; const args = options.args || {}; const wasm = await this._getWasmModule(client); @@ -193,7 +196,7 @@ export class WasmWrapper extends Wrapper { invokeResult: {} as InvokeResult, method, sanitizeEnv: {}, - args: args instanceof ArrayBuffer ? args : msgpackEncode(args), + args: isBuffer(args) ? args : msgpackEncode(args), }; const abort = (message: string) => { @@ -238,23 +241,10 @@ export class WasmWrapper extends Wrapper { ); } case "InvokeResult": { - if (noDecode) { - return { - data: invokeResult.invokeResult, - } as InvokeResult; - } - - try { - return { - data: msgpackDecode(invokeResult.invokeResult as ArrayBuffer), - } as InvokeResult; - } catch (err) { - throw Error( - `WasmWrapper: Failed to decode query result.\nResult: ${JSON.stringify( - invokeResult.invokeResult - )}\nError: ${err}` - ); - } + return { + data: invokeResult.invokeResult, + encoded: true, + }; } default: { throw Error(`WasmWrapper: Unknown state "${state}"`); @@ -318,7 +308,7 @@ export class WasmWrapper extends Wrapper { ): Promise { if (hasExport("_wrap_load_env", exports)) { if (this._sanitizedEnv !== undefined) { - state.env = this._sanitizedEnv as ArrayBuffer; + state.env = this._sanitizedEnv; } else { const clientEnv = this._getClientEnv(); @@ -326,7 +316,7 @@ export class WasmWrapper extends Wrapper { state.sanitizeEnv.args = msgpackEncode({ env: clientEnv }); await exports._wrap_sanitize_env(state.sanitizeEnv.args.byteLength); - state.env = state.sanitizeEnv.result as ArrayBuffer; + state.env = state.sanitizeEnv.result; this._sanitizedEnv = state.env; } else { state.env = msgpackEncode(clientEnv); @@ -334,7 +324,7 @@ export class WasmWrapper extends Wrapper { } } - await exports._wrap_load_env(state.env.byteLength); + await exports._wrap_load_env(state.env?.byteLength || 0); } } @@ -347,9 +337,9 @@ export class WasmWrapper extends Wrapper { } @Tracer.traceMethod("WasmWrapper: getWasmModule") - private async _getWasmModule(client: Client): Promise { + private async _getWasmModule(client: Client): Promise { if (this._wasm !== undefined) { - return this._wasm as ArrayBuffer; + return this._wasm; } const moduleManifest = this._manifest.module; @@ -361,7 +351,7 @@ export class WasmWrapper extends Wrapper { const data = (await this.getFile( { path: moduleManifest }, client - )) as ArrayBuffer; + )) as Uint8Array; this._wasm = data; return data; diff --git a/packages/js/client/src/wasm/imports.ts b/packages/js/client/src/wasm/imports.ts index e717edf191..08e9ef65ef 100644 --- a/packages/js/client/src/wasm/imports.ts +++ b/packages/js/client/src/wasm/imports.ts @@ -33,22 +33,15 @@ export const createImports = (config: { const method = readString(memory.buffer, methodPtr, methodLen); const args = readBytes(memory.buffer, argsPtr, argsLen); - const { data, error } = await client.invoke({ + const { data, error } = await client.invoke({ uri: uri, method: method, - args: args, - noDecode: true, + args: new Uint8Array(args), + encodeResult: true, }); if (!error) { - let msgpack: ArrayBuffer; - if (data instanceof ArrayBuffer) { - msgpack = data; - } else { - msgpack = msgpackEncode(data); - } - - state.subinvoke.result = msgpack; + state.subinvoke.result = data; } else { state.subinvoke.error = `${error.name}: ${error.message}`; } @@ -106,22 +99,15 @@ export const createImports = (config: { state.subinvokeImplementation.args = [implUri, method, args]; - const { data, error } = await client.invoke({ + const { data, error } = await client.invoke({ uri: implUri, method: method, - args: args, - noDecode: true, + args: new Uint8Array(args), + encodeResult: true, }); if (!error) { - let msgpack: ArrayBuffer; - if (data instanceof ArrayBuffer) { - msgpack = data; - } else { - msgpack = msgpackEncode(data); - } - - state.subinvokeImplementation.result = msgpack; + state.subinvokeImplementation.result = data; } else { state.subinvokeImplementation.error = `${error.name}: ${error.message}`; } @@ -179,7 +165,9 @@ export const createImports = (config: { }, // Store the invocation's result __wrap_invoke_result: (ptr: u32, len: u32): void => { - state.invoke.result = readBytes(memory.buffer, ptr, len); + state.invoke.result = new Uint8Array( + readBytes(memory.buffer, ptr, len) + ); }, // Store the invocation's error __wrap_invoke_error: (ptr: u32, len: u32): void => { @@ -219,7 +207,9 @@ export const createImports = (config: { writeBytes(state.sanitizeEnv.args, memory.buffer, ptr); }, __wrap_sanitize_env_result: (ptr: u32, len: u32): void => { - state.sanitizeEnv.result = readBytes(memory.buffer, ptr, len); + state.sanitizeEnv.result = new Uint8Array( + readBytes(memory.buffer, ptr, len) + ); }, __wrap_abort: ( msgPtr: u32, diff --git a/packages/js/core/src/__tests__/PluginRegistrations.spec.ts b/packages/js/core/src/__tests__/PluginRegistrations.spec.ts index 630c818f2f..18ee20c5eb 100644 --- a/packages/js/core/src/__tests__/PluginRegistrations.spec.ts +++ b/packages/js/core/src/__tests__/PluginRegistrations.spec.ts @@ -12,14 +12,14 @@ describe("sanitizePluginRegistrations", () => { const plugins = sanitizePluginRegistrations([ { uri: "wrap://polywrap/wrapper", - plugin: {} as PluginPackage, + plugin: {} as PluginPackage<{}>, } ]); expect(plugins).toEqual([ { uri: new Uri("wrap://polywrap/wrapper"), - plugin: {} as PluginPackage + plugin: {} as PluginPackage<{}> } ]); }); diff --git a/packages/js/core/src/__tests__/msgpack.spec.ts b/packages/js/core/src/__tests__/msgpack.spec.ts new file mode 100644 index 0000000000..f5e97e0c3a --- /dev/null +++ b/packages/js/core/src/__tests__/msgpack.spec.ts @@ -0,0 +1,42 @@ +import {msgpackEncode, msgpackDecode, isBuffer} from "../msgpack"; + +describe("msgpack", () => { + const expectedArrayLike = [ + 130, 168, 102, 105, 114, 115, 116, 75, + 101, 121, 170, 102, 105, 114, 115, 116, + 86, 97, 108, 117, 101, 169, 115, 101, + 99, 111, 110, 100, 75, 101, 121, 171, + 115, 101, 99, 111, 110, 100, 86, 97, + 108, 117, 101 + ] + it("Should encode and decode, returning the same object", () => { + const customObject = { + "firstKey": "firstValue", + "secondKey": "secondValue", + } + const encoded = msgpackEncode(customObject) + expect(encoded).toEqual(Uint8Array.from(expectedArrayLike)) + const decoded = msgpackDecode(encoded) + expect(decoded).toEqual(customObject); + }); + + it("Should encode and decode, returning the same map", () => { + const customMap = new Map() + customMap.set("firstKey", "firstValue") + customMap.set("secondKey", "secondValue") + + const encoded = msgpackEncode(customMap) + // [199, 43, 1] are being added because of the map structure + expect(encoded).toEqual(Uint8Array.from([ 199, 43, 1, ...expectedArrayLike])) + const decoded = msgpackDecode(encoded) + expect(decoded).toEqual(customMap); + }); + + it("Should check if object is buffer", () => { + const notBuf = isBuffer(expectedArrayLike); + expect(notBuf).toBeFalsy() + const buf = isBuffer(Uint8Array.from(expectedArrayLike)); + expect(buf).toBeTruthy() + }) + +}); diff --git a/packages/js/core/src/__tests__/resolveUri.spec.ts b/packages/js/core/src/__tests__/resolveUri.spec.ts index 910a604a22..a61e1e1cd2 100644 --- a/packages/js/core/src/__tests__/resolveUri.spec.ts +++ b/packages/js/core/src/__tests__/resolveUri.spec.ts @@ -31,7 +31,7 @@ import { describe("resolveUri", () => { const client = ( - wrappers: Record, + wrappers: Record>, plugins: PluginRegistration[] = [], interfaces: InterfaceImplementations[] = [], redirects: UriRedirect[] = [] @@ -67,7 +67,6 @@ describe("resolveUri", () => { {} as Client ) as TData }); - }, subscribe: < TData extends Record = Record @@ -111,13 +110,16 @@ describe("resolveUri", () => { }, } as unknown) as Client); - const createPluginWrapper = (uri: Uri, plugin: PluginPackage): Wrapper => { + const createPluginWrapper = (uri: Uri, plugin: PluginPackage<{}>): Wrapper => { return { invoke: () => Promise.resolve({ - uri, - plugin, - } as InvokeResult), + data: { + uri, + plugin, + }, + encoded: false + }), getSchema: (_client: Client): Promise => Promise.resolve(""), getFile: (options: GetFileOptions, client: Client) => Promise.resolve(""), getManifest: ( @@ -142,10 +144,13 @@ describe("resolveUri", () => { return { invoke: () => Promise.resolve({ - uri, - manifest, - uriResolver, - } as InvokeResult), + data: { + uri, + manifest, + uriResolver, + }, + encoded: false + }), getSchema: (_client: Client): Promise => Promise.resolve(""), getFile: (options: GetFileOptions, client: Client) => Promise.resolve(""), getManifest: ( @@ -205,7 +210,7 @@ describe("resolveUri", () => { { uri: new Uri("ens/my-plugin"), plugin: { - factory: () => ({} as Plugin), + factory: () => ({} as PluginModule<{}>), manifest: { schema: "", implements: [coreInterfaceUris.uriResolver], @@ -225,15 +230,15 @@ describe("resolveUri", () => { }, ]; - const wrappers: Record = { - "wrap://ens/ens": ensWrapper as unknown as PluginModule, - "wrap://ens/ipfs": ipfsWrapper as unknown as PluginModule, - "wrap://ens/my-plugin": pluginWrapper as unknown as PluginModule, + const wrappers: Record> = { + "wrap://ens/ens": ensWrapper as unknown as PluginModule<{}>, + "wrap://ens/ipfs": ipfsWrapper as unknown as PluginModule<{}>, + "wrap://ens/my-plugin": pluginWrapper as unknown as PluginModule<{}>, }; const uriResolvers: UriResolver[] = [ new RedirectsResolver(), - new PluginResolver((uri: Uri, plugin: PluginPackage) => + new PluginResolver((uri: Uri, plugin: PluginPackage<{}>) => createPluginWrapper(uri, plugin) ), new ExtendableUriResolver( @@ -257,8 +262,8 @@ describe("resolveUri", () => { const query = UriResolverInterface.Query; const uri = new Uri("wrap/some-uri"); - expect(query.tryResolveUri(client(wrappers).invoke, wrapper, uri)).toBeDefined(); - expect(query.getFile(client(wrappers).invoke, file, path)).toBeDefined(); + expect(query.tryResolveUri(client(wrappers), wrapper, uri)).toBeDefined(); + expect(query.getFile(client(wrappers), file, path)).toBeDefined(); }); it("works in the typical case", async () => { @@ -276,7 +281,7 @@ describe("resolveUri", () => { {} as Client ); - expect(wrapperIdentity).toMatchObject({ + expect(wrapperIdentity.data).toMatchObject({ uri: new Uri("ipfs/QmHash"), manifest: { format: "0.0.1-prealpha.9", @@ -300,7 +305,7 @@ describe("resolveUri", () => { {} as Client ); - expect(wrapperIdentity).toMatchObject({ + expect(wrapperIdentity.data).toMatchObject({ uri: new Uri("my/something-different"), manifest: { format: "0.0.1-prealpha.9", @@ -324,7 +329,7 @@ describe("resolveUri", () => { {} as Client ); - expect(wrapperIdentity).toMatchObject({ + expect(wrapperIdentity.data).toMatchObject({ uri: new Uri("ipfs/QmHash"), manifest: { format: "0.0.1-prealpha.9", @@ -349,7 +354,7 @@ describe("resolveUri", () => { {} as Client ); - expect(wrapperIdentity).toMatchObject({ + expect(wrapperIdentity.data).toMatchObject({ uri: new Uri("my/something-different"), manifest: { format: "0.0.1-prealpha.9", @@ -414,7 +419,7 @@ describe("resolveUri", () => { { uri: new Uri("some/wrapper"), plugin: { - factory: () => ({} as Plugin), + factory: () => ({} as PluginModule<{}>), manifest: { schema: "", implements: [coreInterfaceUris.uriResolver], @@ -460,7 +465,7 @@ describe("resolveUri", () => { client( { ...wrappers, - "wrap://ens/ipfs": faultyIpfsWrapper as unknown as PluginModule + "wrap://ens/ipfs": faultyIpfsWrapper as unknown as PluginModule<{}> }, plugins, interfaces diff --git a/packages/js/core/src/interfaces/uri-resolver.ts b/packages/js/core/src/interfaces/uri-resolver.ts index 46e30c52a6..fbcc1f3e38 100644 --- a/packages/js/core/src/interfaces/uri-resolver.ts +++ b/packages/js/core/src/interfaces/uri-resolver.ts @@ -1,5 +1,5 @@ // TODO: https://github.com/polywrap/monorepo/issues/101 -import { Uri, InvokeHandler, InvokeResult } from "../"; +import { Uri, Invoker, InvokeResult } from "../"; import { Tracer } from "@polywrap/tracing-js"; @@ -13,11 +13,11 @@ export const Query = { tryResolveUri: Tracer.traceFunc( "core: uri-resolver: tryResolveUri", async ( - invoke: InvokeHandler["invoke"], + invoker: Invoker, wrapper: Uri, uri: Uri ): Promise> => { - return invoke({ + return invoker.invoke({ uri: wrapper.uri, method: `tryResolveUri`, args: { @@ -30,11 +30,11 @@ export const Query = { getFile: Tracer.traceFunc( "core: uri-resolver: getFile", async ( - invoke: InvokeHandler["invoke"], + invoker: Invoker, wrapper: Uri, path: string - ): Promise> => { - return invoke({ + ): Promise> => { + return invoker.invoke({ uri: wrapper.uri, method: "getFile", args: { diff --git a/packages/js/core/src/msgpack/index.ts b/packages/js/core/src/msgpack/index.ts index 8ef3c92a4b..e5e28ee00d 100644 --- a/packages/js/core/src/msgpack/index.ts +++ b/packages/js/core/src/msgpack/index.ts @@ -33,7 +33,7 @@ extensionCodec.register({ }, }); -export function msgpackEncode(object: unknown): ArrayBuffer { +export function msgpackEncode(object: unknown): Uint8Array { const encoder = new Encoder( extensionCodec, undefined, // context @@ -45,7 +45,7 @@ export function msgpackEncode(object: unknown): ArrayBuffer { undefined // forceIntegerToFloat ); - return encoder.encode(object).buffer; + return encoder.encode(object); } export function msgpackDecode( @@ -54,3 +54,11 @@ export function msgpackDecode( const decoder = new Decoder(extensionCodec); return decoder.decode(buffer); } + +export function isBuffer(maybeBuf: unknown): maybeBuf is BufferSource { + if (maybeBuf instanceof ArrayBuffer || ArrayBuffer.isView(maybeBuf)) { + return true; + } else { + return false; + } +} diff --git a/packages/js/core/src/types/Client.ts b/packages/js/core/src/types/Client.ts index 23dfa55b8a..4a523bc468 100644 --- a/packages/js/core/src/types/Client.ts +++ b/packages/js/core/src/types/Client.ts @@ -1,6 +1,6 @@ import { QueryHandler, - InvokeHandler, + Invoker, SubscriptionHandler, UriRedirect, Uri, @@ -53,9 +53,9 @@ export interface GetImplementationsOptions extends Contextualized { } export interface Client - extends QueryHandler, + extends Invoker, + QueryHandler, SubscriptionHandler, - InvokeHandler, WorkflowHandler, UriResolverHandler { getRedirects(options: GetRedirectsOptions): readonly UriRedirect[]; @@ -91,7 +91,7 @@ export interface Client getFile( uri: TUri, options: GetFileOptions - ): Promise; + ): Promise; getImplementations( uri: TUri, diff --git a/packages/js/core/src/types/Invoke.ts b/packages/js/core/src/types/Invoke.ts index 057c23bccd..fb26eefbbe 100644 --- a/packages/js/core/src/types/Invoke.ts +++ b/packages/js/core/src/types/Invoke.ts @@ -15,13 +15,7 @@ export interface InvokeOptions< * Arguments for the method, structured as a map, * removing the chance of incorrectly ordering arguments. */ - args?: Record | ArrayBuffer; - - /** - * If set to true, the invoke function will not decode the msgpack results - * into JavaScript objects, and instead return the raw ArrayBuffer. - */ - noDecode?: boolean; + args?: Record | Uint8Array; /** * Override the client's config for all invokes within this invoke. @@ -52,8 +46,26 @@ export interface InvokeResult { error?: Error; } -export interface InvokeHandler { +export interface InvokerOptions< + TUri extends Uri | string = string, + TClientConfig extends ClientConfig = ClientConfig +> extends InvokeOptions { + encodeResult?: boolean; +} + +export interface Invoker { invoke( - options: InvokeOptions + options: InvokerOptions ): Promise>; } + +export interface InvocableResult extends InvokeResult { + encoded?: boolean; +} + +export interface Invocable { + invoke( + options: InvokeOptions, + invoker: Invoker + ): Promise>; +} diff --git a/packages/js/core/src/types/Wrapper.ts b/packages/js/core/src/types/Wrapper.ts index b9308c81b9..d940fe56b2 100644 --- a/packages/js/core/src/types/Wrapper.ts +++ b/packages/js/core/src/types/Wrapper.ts @@ -4,7 +4,9 @@ import { GetFileOptions, GetManifestOptions, InvokeOptions, - InvokeResult, + Invocable, + Invoker, + InvocableResult, } from "."; import { AnyManifestArtifact, ManifestArtifactType } from "../manifest"; @@ -14,7 +16,7 @@ import { AnyManifestArtifact, ManifestArtifactType } from "../manifest"; * this class may do things like caching WASM bytecode, spawning * worker threads, or indexing into resolvers to find the requested method. */ -export abstract class Wrapper { +export abstract class Wrapper implements Invocable { /** * Invoke the Wrapper based on the provided [[InvokeOptions]] * @@ -24,8 +26,8 @@ export abstract class Wrapper { */ public abstract invoke( options: InvokeOptions, - client: Client - ): Promise>; + invoker: Invoker + ): Promise>; /** * Get the Wrapper's schema @@ -57,7 +59,7 @@ export abstract class Wrapper { public abstract getFile( options: GetFileOptions, client: Client - ): Promise; + ): Promise; } /** Cache of Wrapper definitions, mapping the Wrapper's URI to its definition */ diff --git a/packages/js/core/src/uri-resolution/resolvers/extendable/UriResolverWrapper.ts b/packages/js/core/src/uri-resolution/resolvers/extendable/UriResolverWrapper.ts index a8cdef56d0..1c7a4910de 100644 --- a/packages/js/core/src/uri-resolution/resolvers/extendable/UriResolverWrapper.ts +++ b/packages/js/core/src/uri-resolution/resolvers/extendable/UriResolverWrapper.ts @@ -3,7 +3,7 @@ import { DeserializeManifestOptions, deserializePolywrapManifest, } from "../../../manifest"; -import { Uri, WrapperCache, Client, InvokeHandler } from "../../../types"; +import { Uri, WrapperCache, Client, Invoker } from "../../../types"; import { UriResolver, UriResolutionStack, @@ -34,7 +34,7 @@ export class UriResolverWrapper implements UriResolver { const result = await tryResolveUriWithImplementation( uri, this.implementationUri, - client.invoke.bind(client) + client ); if (!result) { @@ -82,10 +82,10 @@ export class UriResolverWrapper implements UriResolver { const tryResolveUriWithImplementation = async ( uri: Uri, implementationUri: Uri, - invoke: InvokeHandler["invoke"] + invoker: Invoker ): Promise => { const { data } = await UriResolverInterface.Query.tryResolveUri( - invoke, + invoker, implementationUri, uri ); diff --git a/packages/js/plugins/file-system/src/index.ts b/packages/js/plugins/file-system/src/index.ts index 96bb84ad92..66b46ef9f7 100644 --- a/packages/js/plugins/file-system/src/index.ts +++ b/packages/js/plugins/file-system/src/index.ts @@ -18,8 +18,12 @@ import { PluginFactory } from "@polywrap/core-js"; type NoConfig = Record; export class FileSystemPlugin extends Module { - async readFile(args: Args_readFile, _client: Client): Promise { - return fs.promises.readFile(args.path); + async readFile(args: Args_readFile, _client: Client): Promise { + return fs.promises + .readFile(args.path) + .then((buffer) => + ArrayBuffer.isView(buffer) ? buffer : new Uint8Array(buffer) + ); } async readFileAsString( diff --git a/packages/js/react/src/invoke.tsx b/packages/js/react/src/invoke.tsx index d01b2f4c00..3b11f1509c 100644 --- a/packages/js/react/src/invoke.tsx +++ b/packages/js/react/src/invoke.tsx @@ -1,7 +1,11 @@ import { usePolywrapClient } from "./client"; import { useStateReducer } from "./state"; -import { InvokeOptions, InvokeResult } from "@polywrap/core-js"; +import { + InvokeOptions, + InvokeResult, + isBuffer +} from "@polywrap/core-js"; export interface UsePolywrapInvokeState< TData = unknown @@ -21,13 +25,13 @@ export interface UsePolywrapInvokeProps extends InvokeOptions { /* Note that the initial values passed into the usePolywrapInvoke hook will be -ignored when an ArrayBuffer is passed into execute(...). +ignored when an Uint8Array is passed into execute(...). */ export interface UsePolywrapInvoke< TData = unknown > extends UsePolywrapInvokeState { execute: ( - args?: Record | ArrayBuffer + args?: Record | Uint8Array ) => Promise>; } @@ -42,11 +46,11 @@ export function usePolywrapInvoke< INITIAL_QUERY_STATE as UsePolywrapInvokeState ); - const execute = async (args?: Record | ArrayBuffer) => { + const execute = async (args?: Record | Uint8Array) => { dispatch({ loading: true }); const { data, error } = await client.invoke({ ...props, - args: args instanceof ArrayBuffer ? args : { + args: isBuffer(args) ? args : { ...props.args, ...args, }, diff --git a/packages/schema/bind/src/bindings/typescript/app-ts/templates/types-ts.mustache b/packages/schema/bind/src/bindings/typescript/app-ts/templates/types-ts.mustache index 78a23f1067..9e7a07ac49 100644 --- a/packages/schema/bind/src/bindings/typescript/app-ts/templates/types-ts.mustache +++ b/packages/schema/bind/src/bindings/typescript/app-ts/templates/types-ts.mustache @@ -15,7 +15,7 @@ export type Int = number; export type Int8 = number; export type Int16 = number; export type Int32 = number; -export type Bytes = ArrayBuffer; +export type Bytes = Uint8Array; export type BigInt = string; export type BigNumber = string; export type Json = string; diff --git a/packages/schema/bind/src/bindings/typescript/plugin-ts/templates/types-ts.mustache b/packages/schema/bind/src/bindings/typescript/plugin-ts/templates/types-ts.mustache index 771baaab3c..e88be3796a 100644 --- a/packages/schema/bind/src/bindings/typescript/plugin-ts/templates/types-ts.mustache +++ b/packages/schema/bind/src/bindings/typescript/plugin-ts/templates/types-ts.mustache @@ -18,7 +18,7 @@ export type Int = number; export type Int8 = number; export type Int16 = number; export type Int32 = number; -export type Bytes = ArrayBuffer; +export type Bytes = Uint8Array; export type BigInt = string; export type BigNumber = string; export type Json = string; diff --git a/packages/test-cases/cases/bind/sanity/output/app-ts/types.ts b/packages/test-cases/cases/bind/sanity/output/app-ts/types.ts index 71e9a4a833..8dcf50fa3f 100644 --- a/packages/test-cases/cases/bind/sanity/output/app-ts/types.ts +++ b/packages/test-cases/cases/bind/sanity/output/app-ts/types.ts @@ -15,7 +15,7 @@ export type Int = number; export type Int8 = number; export type Int16 = number; export type Int32 = number; -export type Bytes = ArrayBuffer; +export type Bytes = Uint8Array; export type BigInt = string; export type BigNumber = string; export type Json = string; diff --git a/packages/test-cases/cases/bind/sanity/output/plugin-ts/types.ts b/packages/test-cases/cases/bind/sanity/output/plugin-ts/types.ts index 8aed41456a..fae3517706 100644 --- a/packages/test-cases/cases/bind/sanity/output/plugin-ts/types.ts +++ b/packages/test-cases/cases/bind/sanity/output/plugin-ts/types.ts @@ -18,7 +18,7 @@ export type Int = number; export type Int8 = number; export type Int16 = number; export type Int32 = number; -export type Bytes = ArrayBuffer; +export type Bytes = Uint8Array; export type BigInt = string; export type BigNumber = string; export type Json = string; diff --git a/packages/test-cases/cases/cli/plugin/codegen/001-sanity/expected/src/wrap/types.ts b/packages/test-cases/cases/cli/plugin/codegen/001-sanity/expected/src/wrap/types.ts index 796131ef49..bb9273518b 100644 --- a/packages/test-cases/cases/cli/plugin/codegen/001-sanity/expected/src/wrap/types.ts +++ b/packages/test-cases/cases/cli/plugin/codegen/001-sanity/expected/src/wrap/types.ts @@ -18,7 +18,7 @@ export type Int = number; export type Int8 = number; export type Int16 = number; export type Int32 = number; -export type Bytes = ArrayBuffer; +export type Bytes = Uint8Array; export type BigInt = string; export type BigNumber = string; export type Json = string; diff --git a/packages/test-cases/cases/cli/plugin/codegen/002-single-module/expected/src/wrap/types.ts b/packages/test-cases/cases/cli/plugin/codegen/002-single-module/expected/src/wrap/types.ts index 796131ef49..bb9273518b 100644 --- a/packages/test-cases/cases/cli/plugin/codegen/002-single-module/expected/src/wrap/types.ts +++ b/packages/test-cases/cases/cli/plugin/codegen/002-single-module/expected/src/wrap/types.ts @@ -18,7 +18,7 @@ export type Int = number; export type Int8 = number; export type Int16 = number; export type Int32 = number; -export type Bytes = ArrayBuffer; +export type Bytes = Uint8Array; export type BigInt = string; export type BigNumber = string; export type Json = string; diff --git a/packages/test-cases/cases/cli/plugin/codegen/003-env/expected/src/wrap/types.ts b/packages/test-cases/cases/cli/plugin/codegen/003-env/expected/src/wrap/types.ts index 450bfe2967..a02c59511e 100644 --- a/packages/test-cases/cases/cli/plugin/codegen/003-env/expected/src/wrap/types.ts +++ b/packages/test-cases/cases/cli/plugin/codegen/003-env/expected/src/wrap/types.ts @@ -18,7 +18,7 @@ export type Int = number; export type Int8 = number; export type Int16 = number; export type Int32 = number; -export type Bytes = ArrayBuffer; +export type Bytes = Uint8Array; export type BigInt = string; export type BigNumber = string; export type Json = string; diff --git a/packages/test-cases/cases/cli/plugin/codegen/004-env-sanitization/expected/src/wrap/types.ts b/packages/test-cases/cases/cli/plugin/codegen/004-env-sanitization/expected/src/wrap/types.ts index 813097c637..d6e697ee03 100644 --- a/packages/test-cases/cases/cli/plugin/codegen/004-env-sanitization/expected/src/wrap/types.ts +++ b/packages/test-cases/cases/cli/plugin/codegen/004-env-sanitization/expected/src/wrap/types.ts @@ -18,7 +18,7 @@ export type Int = number; export type Int8 = number; export type Int16 = number; export type Int32 = number; -export type Bytes = ArrayBuffer; +export type Bytes = Uint8Array; export type BigInt = string; export type BigNumber = string; export type Json = string; diff --git a/packages/wasm/as/assembly/__tests__/msgpack_write.spec.ts b/packages/wasm/as/assembly/__tests__/msgpack_write.spec.ts new file mode 100644 index 0000000000..a24900c449 --- /dev/null +++ b/packages/wasm/as/assembly/__tests__/msgpack_write.spec.ts @@ -0,0 +1,454 @@ +import { Write } from "../msgpack/Write"; +import { WriteSizer } from "../msgpack/WriteSizer"; +import { WriteEncoder } from "../msgpack/WriteEncoder"; +import { BigInt, BigNumber } from "../math"; +import { JSON } from "../json"; + +function fill(arr: Array): ArrayBuffer { + const buffer = new ArrayBuffer(arr.length); + const offset = changetype(buffer); + for (let i: i32 = 0; i < arr.length; ++i) { + store(offset + i, arr[i]); + } + return buffer; +} + +class Case { + name: string; + input: T; + want: Array; + + constructor(name: string, input: T, want: Array) { + this.name = name; + this.input = input; + this.want = want; + } +} + +describe("WriteEncoder", () => { + it("TestWriteNil", () => { + const sizer = new WriteSizer(); + sizer.writeNil(); + const buffer = new ArrayBuffer(sizer.length); + const encoder = new WriteEncoder(buffer, sizer); + encoder.writeNil(); + + const actual = encoder._view.buffer; + const expected = fill([192]); + expect(actual).toStrictEqual(expected); + }); + + it("TestWriteBool", () => { + const cases = [ + new Case("false", false, [194]), + new Case("true", true, [195]), + ]; + + for (let i: i32 = 0; i < cases.length; ++i) { + const testcase = cases[i]; + const sizer = new WriteSizer(); + sizer.writeBool(testcase.input); + const buffer = new ArrayBuffer(sizer.length); + const encoder = new WriteEncoder(buffer, sizer); + encoder.writeBool(testcase.input); + + const actual = encoder._view.buffer; + const expected = fill(testcase.want); + expect(actual).toStrictEqual(expected); + } + }); + + it("TestWriteInt8", () => { + const cases = [ + new Case("zero", 0, [0]), + new Case("negative fixed int", -1, [255]), + new Case("negative fixed int", -31, [225]), + new Case("negative fixed int", -32, [224]), + new Case("positive fixed int", 1, [1]), + new Case("positive fixed int", 127, [127]), + new Case("8-bit signed int", -128, [208, 128]), + new Case("8-bit signed int", -100, [208, 156]), + new Case("8-bit signed int", -33, [208, 223]), + ]; + + for (let i: i32 = 0; i < cases.length; ++i) { + const testcase = cases[i]; + const sizer = new WriteSizer(); + sizer.writeInt8(testcase.input); + const buffer = new ArrayBuffer(sizer.length); + const encoder = new WriteEncoder(buffer, sizer); + encoder.writeInt8(testcase.input); + + const actual = encoder._view.buffer; + const expected = fill(testcase.want); + expect(actual).toStrictEqual(expected); + } + }); + + it("TestWriteInt16", () => { + const cases = [ + new Case("16-bit signed int (negative)", -32768, [209, 128, 0]), + new Case("16-bit signed int (negative)", -32767, [209, 128, 1]), + new Case("16-bit signed int (negative)", -129, [209, 255, 127]), + new Case("16-bit signed int (positive)", 128, [209, 0, 128]), + new Case("16-bit signed int (positive)", 32767, [209, 127, 255]), + ]; + + for (let i: i32 = 0; i < cases.length; ++i) { + const testcase = cases[i]; + const sizer = new WriteSizer(); + sizer.writeInt16(testcase.input); + const buffer = new ArrayBuffer(sizer.length); + const encoder = new WriteEncoder(buffer, sizer); + encoder.writeInt16(testcase.input); + + const actual = encoder._view.buffer; + const expected = fill(testcase.want); + expect(actual).toStrictEqual(expected); + } + }); + + it("TestWriteInt32", () => { + const cases = [ + new Case("32-bit signed int (negative)", -32769, [210, 255, 255, 127, 255]), + new Case("32-bit signed int (negative)", -2147483648,[210, 128, 0, 0, 0]), + new Case("32-bit signed int (negative)", -2147483647,[210, 128, 0, 0, 1]), + new Case("32-bit signed int (positive)", 32768, [210, 0, 0, 128, 0]), + new Case("32-bit signed int (positive)", 123456, [210, 0, 1, 226, 64]), + new Case("32-bit signed int (positive)", 2147483647, [210, 127, 255, 255, 255]), + ] + + for (let i: i32 = 0; i < cases.length; ++i) { + const testcase = cases[i]; + const sizer = new WriteSizer(); + sizer.writeInt32(testcase.input); + const buffer = new ArrayBuffer(sizer.length); + const encoder = new WriteEncoder(buffer, sizer); + encoder.writeInt32(testcase.input); + + const actual = encoder._view.buffer; + const expected = fill(testcase.want); + expect(actual).toStrictEqual(expected); + } + }); + + it("TestWriteUint8", () => { + const cases = [ + new Case("zero", 0, [0]), + new Case("positive fixed int", 1, [1]), + new Case("positive fixed int", 127, [127]), + new Case("8-bit unsigned int", 200, [204, 200]), + new Case("8-bit unsigned int", 255, [204, 255]), + ]; + + for (let i: i32 = 0; i < cases.length; ++i) { + const testcase = cases[i]; + const sizer = new WriteSizer(); + sizer.writeUInt8(testcase.input); + const buffer = new ArrayBuffer(sizer.length); + const encoder = new WriteEncoder(buffer, sizer); + encoder.writeUInt8(testcase.input); + + const actual = encoder._view.buffer; + const expected = fill(testcase.want); + expect(actual).toStrictEqual(expected); + } + }); + + it("TestWriteUint16", () => { + const cases = [ + new Case("16-bit unsigned int", 256, [205, 1, 0]), + new Case("16-bit unsigned int", 32767, [205, 127, 255]), + new Case("16-bit unsigned int", 32768, [205, 128, 0]), + new Case("16-bit unsigned int", 65535, [205, 255, 255]), + ]; + + for (let i: i32 = 0; i < cases.length; ++i) { + const testcase = cases[i]; + const sizer = new WriteSizer(); + sizer.writeUInt16(testcase.input); + const buffer = new ArrayBuffer(sizer.length); + const encoder = new WriteEncoder(buffer, sizer); + encoder.writeUInt16(testcase.input); + + const actual = encoder._view.buffer; + const expected = fill(testcase.want); + expect(actual).toStrictEqual(expected); + } + }); + + it("TestWriteUint32", () => { + const cases = [ + new Case("32-bit unsigned int", 65536, [206, 0, 1, 0, 0]), + new Case("32-bit unsigned int", 123456, [206, 0, 1, 226, 64]), + new Case("32-bit unsigned int", 2147483648, [206, 128, 0, 0, 0]), + new Case("32-bit unsigned int", 4294967295, [206, 255, 255, 255, 255]), + ]; + + for (let i: i32 = 0; i < cases.length; ++i) { + const testcase = cases[i]; + const sizer = new WriteSizer(); + sizer.writeUInt32(testcase.input); + const buffer = new ArrayBuffer(sizer.length); + const encoder = new WriteEncoder(buffer, sizer); + encoder.writeUInt32(testcase.input); + + const actual = encoder._view.buffer; + const expected = fill(testcase.want); + expect(actual).toStrictEqual(expected); + } + }); + + it("TestWriteFloat32", () => { + const cases = [new Case("32-bit float", 0.5, [202, 63, 0, 0, 0])]; + + for (let i: i32 = 0; i < cases.length; ++i) { + const testcase = cases[i]; + const sizer = new WriteSizer(); + sizer.writeFloat32(testcase.input); + const buffer = new ArrayBuffer(sizer.length); + const encoder = new WriteEncoder(buffer, sizer); + encoder.writeFloat32(testcase.input); + + const actual = encoder._view.buffer; + const expected = fill(testcase.want); + expect(actual).toStrictEqual(expected); + } + }); + + it("TestWriteFloat64", () => { + const cases = [ + new Case("64-bit float", 3.141592653589793, [203, 64, 9, 33, 251, 84, 68, 45, 24]), + ]; + + for (let i: i32 = 0; i < cases.length; ++i) { + const testcase = cases[i]; + const sizer = new WriteSizer(); + sizer.writeFloat64(testcase.input); + const buffer = new ArrayBuffer(sizer.length); + const encoder = new WriteEncoder(buffer, sizer); + encoder.writeFloat64(testcase.input); + + const actual = encoder._view.buffer; + const expected = fill(testcase.want); + expect(actual).toStrictEqual(expected); + } + }); + + it("TestWriteBytes", () => { + const arr1 = new Uint8Array(1); + arr1.fill(1, 0, 1); + + const cases = [new Case("Bytes", arr1.buffer, [196, 1, 1])]; + + for (let i: i32 = 0; i < cases.length; ++i) { + const testcase = cases[i]; + const sizer = new WriteSizer(); + sizer.writeBytes(testcase.input); + const buffer = new ArrayBuffer(sizer.length); + const encoder = new WriteEncoder(buffer, sizer); + encoder.writeBytes(testcase.input); + + const actual = encoder._view.buffer; + const expected = fill(testcase.want); + expect(actual).toStrictEqual(expected); + } + }); + + it("TestWriteString", () => { + const cases = [ + new Case("Empty String", "", [160]), + new Case("5-char String", "hello", [165, 104, 101, 108, 108, 111]), + new Case("11-char String", "hello world", [171, 104, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100]), + ]; + + for (let i: i32 = 0; i < cases.length; ++i) { + const testcase = cases[i]; + const sizer = new WriteSizer(); + sizer.writeString(testcase.input); + const buffer = new ArrayBuffer(sizer.length); + const encoder = new WriteEncoder(buffer, sizer); + encoder.writeString(testcase.input); + + const actual = encoder._view.buffer; + const expected = fill(testcase.want); + expect(actual).toStrictEqual(expected); + } + }); + + it("TestWriteBigInt", () => { + const cases = [ + new Case("BigInt", BigInt.fromString("3124124512598273468017578125"), + [188, 51, 49, 50, 52, 49, 50, 52, 53, 49, 50, 53, 57, 56, + 50, 55, 51, 52, 54, 56, 48, 49, 55, 53, 55, 56, 49, 50, 53]), + ]; + + for (let i: i32 = 0; i < cases.length; ++i) { + const testcase = cases[i]; + const sizer = new WriteSizer(); + sizer.writeBigInt(testcase.input); + const buffer = new ArrayBuffer(sizer.length); + const encoder = new WriteEncoder(buffer, sizer); + encoder.writeBigInt(testcase.input); + + const actual = encoder._view.buffer; + const expected = fill(testcase.want); + expect(actual).toStrictEqual(expected); + } + }); + + it("TestWriteBigNumber", () => { + const cases = [ + new Case("BigNumber", BigNumber.fromString("3124124512.598273468017578125"), + [189, 51, 49, 50, 52, 49, 50, 52, 53, 49, 50, 46, 53, 57, 56, + 50, 55, 51, 52, 54, 56, 48, 49, 55, 53, 55, 56, 49, 50, 53]), + ]; + + for (let i: i32 = 0; i < cases.length; ++i) { + const testcase = cases[i]; + const sizer = new WriteSizer(); + sizer.writeBigNumber(testcase.input); + const buffer = new ArrayBuffer(sizer.length); + const encoder = new WriteEncoder(buffer, sizer); + encoder.writeBigNumber(testcase.input); + + const actual = encoder._view.buffer; + const expected = fill(testcase.want); + expect(actual).toStrictEqual(expected); + } + }); + + it("TestWriteJSON", () => { + const cases = [ + new Case("JSON", JSON.parse(`{"foo": "bar"}`), [173, 123, 34, 102, 111, 111, 34, 58, 34, 98, 97, 114, 34, 125]), + ]; + + for (let i: i32 = 0; i < cases.length; ++i) { + const testcase = cases[i]; + const sizer = new WriteSizer(); + sizer.writeJSON(testcase.input); + const buffer = new ArrayBuffer(sizer.length); + const encoder = new WriteEncoder(buffer, sizer); + encoder.writeJSON(testcase.input); + + const actual = encoder._view.buffer; + const expected = fill(testcase.want); + expect(actual).toStrictEqual(expected); + } + }); + + it("TestWriteArray", () => { + const cases = [ + new Case>("Array", [10, 20, 30], [147, 10, 20, 30]), + ]; + + for (let i: i32 = 0; i < cases.length; ++i) { + const testcase = cases[i]; + const sizer = new WriteSizer(); + sizer.writeArray(testcase.input, (writer: Write, item: u8) => { + writer.writeUInt8(item); + }); + const buffer = new ArrayBuffer(sizer.length); + const encoder = new WriteEncoder(buffer, sizer); + encoder.writeArray(testcase.input, (writer: Write, item: u8) => { + writer.writeUInt8(item); + }); + + const actual = encoder._view.buffer; + const expected = fill(testcase.want); + expect(actual).toStrictEqual(expected); + } + }); + + it("TestWriteMap", () => { + const map1 = new Map>(); + map1.set("foo", [1, -1, 42]); + map1.set("baz", [12412, -98987]); + + const cases = [ + new Case>>( + "Map", map1, + [130, 163, 102, 111, 111, 147, 1, 255, 42, 163, 98, + 97, 122, 146, 209, 48, 124, 210, 255, 254, 125, 85]), + ]; + + for (let i: i32 = 0; i < cases.length; ++i) { + const testcase = cases[i]; + const sizer = new WriteSizer(); + sizer.writeMap>( + testcase.input, + (writer: Write, key: string): void => { + writer.writeString(key); + }, + (writer: Write, value: Array) => { + writer.writeArray(value, (writer: Write, item: i32) => { + writer.writeInt32(item); + }); + } + ); + const buffer = new ArrayBuffer(sizer.length); + const encoder = new WriteEncoder(buffer, sizer); + encoder.writeMap>( + testcase.input, + (writer: Write, key: string): void => { + writer.writeString(key); + }, + (writer: Write, value: Array) => { + writer.writeArray(value, (writer: Write, item: i32) => { + writer.writeInt32(item); + }); + } + ); + + const actual = encoder._view.buffer; + const expected = fill(testcase.want); + expect(actual).toStrictEqual(expected); + } + }); + + it("TestWriteExtGenericMap", () => { + const map1 = new Map>(); + map1.set("foo", [1, -1, 42]); + map1.set("baz", [12412, -98987]); + + const cases = [ + new Case>>( + "Map", map1, + [199, 22, 1, 130, 163, 102, 111, 111, 147, 1, 255, 42, 163, 98, + 97, 122, 146, 209, 48, 124, 210, 255, 254, 125, 85]), + ]; + + for (let i: i32 = 0; i < cases.length; ++i) { + const testcase = cases[i]; + const sizer = new WriteSizer(); + sizer.writeExtGenericMap>( + testcase.input, + (writer: Write, key: string): void => { + writer.writeString(key); + }, + (writer: Write, value: Array) => { + writer.writeArray(value, (writer: Write, item: i32) => { + writer.writeInt32(item); + }); + } + ); + const buffer = new ArrayBuffer(sizer.length); + const encoder = new WriteEncoder(buffer, sizer); + encoder.writeExtGenericMap>( + testcase.input, + (writer: Write, key: string): void => { + writer.writeString(key); + }, + (writer: Write, value: Array) => { + writer.writeArray(value, (writer: Write, item: i32) => { + writer.writeInt32(item); + }); + } + ); + + const actual = encoder._view.buffer; + const expected = fill(testcase.want); + expect(actual).toStrictEqual(expected); + } + }); +}); diff --git a/packages/wasm/as/assembly/__tests__/msgpack_write_option.spec.ts b/packages/wasm/as/assembly/__tests__/msgpack_write_option.spec.ts new file mode 100644 index 0000000000..58ac494560 --- /dev/null +++ b/packages/wasm/as/assembly/__tests__/msgpack_write_option.spec.ts @@ -0,0 +1,490 @@ +import { Write } from "../msgpack/Write"; +import { WriteSizer } from "../msgpack/WriteSizer"; +import { WriteEncoder } from "../msgpack/WriteEncoder"; +import { BigInt, BigNumber } from "../math"; +import { JSON } from "../json"; +import { Option } from "../"; + +function fill(arr: Array): ArrayBuffer { + const buffer = new ArrayBuffer(arr.length); + const offset = changetype(buffer); + for (let i: i32 = 0; i < arr.length; ++i) { + store(offset + i, arr[i]); + } + return buffer; +} + +class Case { + name: string; + input: T; + want: Array; + + constructor(name: string, input: T, want: Array) { + this.name = name; + this.input = input; + this.want = want; + } +} + +describe("WriteEncoder Option types", () => { + it("TestWriteOptionalBool", () => { + const cases = [ + new Case>("nil", Option.None(), [192]), + new Case>("nil", Option.Some(false), [194]), + new Case>("nil", Option.Some(true), [195]), + ]; + + for (let i: i32 = 0; i < cases.length; ++i) { + const testcase = cases[i]; + const sizer = new WriteSizer(); + sizer.writeOptionalBool(testcase.input); + const buffer = new ArrayBuffer(sizer.length); + const encoder = new WriteEncoder(buffer, sizer); + encoder.writeOptionalBool(testcase.input); + + const actual = encoder._view.buffer; + const expected = fill(testcase.want); + expect(actual).toStrictEqual(expected); + } + }); + + it("TestWriteOptionalInt8", () => { + const cases = [ + new Case>("none", Option.None(), [192]), + new Case>( + "positive fixed int", + Option.Some(-128), + [208,128] + ), + ]; + + for (let i: i32 = 0; i < cases.length; ++i) { + const testcase = cases[i]; + const sizer = new WriteSizer(); + sizer.writeOptionalInt8(testcase.input); + const buffer = new ArrayBuffer(sizer.length); + const encoder = new WriteEncoder(buffer, sizer); + encoder.writeOptionalInt8(testcase.input); + + const actual = encoder._view.buffer; + const expected = fill(testcase.want); + expect(actual).toStrictEqual(expected); + } + }); + + it("TestWriteOptionalInt16", () => { + const cases = [ + new Case>("none", Option.None(), [192]), + new Case>( + "16-bit signed int (negative)", + Option.Some(-32768), + [209, 128, 0] + ), + ]; + + for (let i: i32 = 0; i < cases.length; ++i) { + const testcase = cases[i]; + const sizer = new WriteSizer(); + sizer.writeOptionalInt16(testcase.input); + const buffer = new ArrayBuffer(sizer.length); + const encoder = new WriteEncoder(buffer, sizer); + encoder.writeOptionalInt16(testcase.input); + + const actual = encoder._view.buffer; + const expected = fill(testcase.want); + expect(actual).toStrictEqual(expected); + } + }); + + it("TestWriteOptionalInt32", () => { + const cases = [ + new Case>("none", Option.None(), [192]), + new Case>( + "32-bit signed int (negative)", + Option.Some(-32769), + [210, 255, 255, 127, 255] + ), + ]; + + for (let i: i32 = 0; i < cases.length; ++i) { + const testcase = cases[i]; + const sizer = new WriteSizer(); + sizer.writeOptionalInt32(testcase.input); + const buffer = new ArrayBuffer(sizer.length); + const encoder = new WriteEncoder(buffer, sizer); + encoder.writeOptionalInt32(testcase.input); + + const actual = encoder._view.buffer; + const expected = fill(testcase.want); + expect(actual).toStrictEqual(expected); + } + }); + + it("TestWriteOptionalUint8", () => { + const cases = [ + new Case>("none", Option.None(), [192]), + new Case>( + "8-bit unsigned int", + Option.Some(200), + [204,200] + ), + ]; + + for (let i: i32 = 0; i < cases.length; ++i) { + const testcase = cases[i]; + const sizer = new WriteSizer(); + sizer.writeOptionalUInt8(testcase.input); + const buffer = new ArrayBuffer(sizer.length); + const encoder = new WriteEncoder(buffer, sizer); + encoder.writeOptionalUInt8(testcase.input); + + const actual = encoder._view.buffer; + const expected = fill(testcase.want); + expect(actual).toStrictEqual(expected); + } + }); + + it("TestWriteOptionalUint16", () => { + const cases = [ + new Case>("none", Option.None(), [192]), + new Case>( + "16-bit unsigned int", + Option.Some(256), + [205,1,0] + ), + ]; + + for (let i: i32 = 0; i < cases.length; ++i) { + const testcase = cases[i]; + const sizer = new WriteSizer(); + sizer.writeOptionalUInt16(testcase.input); + const buffer = new ArrayBuffer(sizer.length); + const encoder = new WriteEncoder(buffer, sizer); + encoder.writeOptionalUInt16(testcase.input); + + const actual = encoder._view.buffer; + const expected = fill(testcase.want); + expect(actual).toStrictEqual(expected); + } + }); + + it("TestWriteOptionalUint32", () => { + const cases = [ + new Case>("none", Option.None(), [192]), + new Case>( + "32-bit unsigned int", + Option.Some(65536), + [206,0,1,0,0] + ), + ]; + + for (let i: i32 = 0; i < cases.length; ++i) { + const testcase = cases[i]; + const sizer = new WriteSizer(); + sizer.writeOptionalUInt32(testcase.input); + const buffer = new ArrayBuffer(sizer.length); + const encoder = new WriteEncoder(buffer, sizer); + encoder.writeOptionalUInt32(testcase.input); + + const actual = encoder._view.buffer; + const expected = fill(testcase.want); + expect(actual).toStrictEqual(expected); + } + }); + + it("TestWriteOptionalFloat32", () => { + const cases = [ + new Case>("none", Option.None(), [192]), + new Case>( + "32-bit float", + Option.Some(0.5), + [202,63,0,0,0] + ), + ]; + + for (let i: i32 = 0; i < cases.length; ++i) { + const testcase = cases[i]; + const sizer = new WriteSizer(); + sizer.writeOptionalFloat32(testcase.input); + const buffer = new ArrayBuffer(sizer.length); + const encoder = new WriteEncoder(buffer, sizer); + encoder.writeOptionalFloat32(testcase.input); + + const actual = encoder._view.buffer; + const expected = fill(testcase.want); + expect(actual).toStrictEqual(expected); + } + }); + + it("TestWriteOptionalFloat64", () => { + const cases = [ + new Case>("none", Option.None(), [192]), + new Case>( + "64-bit float", + Option.Some(3.141592653589793), + [203, 64, 9, 33, 251, 84, 68, 45, 24] + ), + ]; + + for (let i: i32 = 0; i < cases.length; ++i) { + const testcase = cases[i]; + const sizer = new WriteSizer(); + sizer.writeOptionalFloat64(testcase.input); + const buffer = new ArrayBuffer(sizer.length); + const encoder = new WriteEncoder(buffer, sizer); + encoder.writeOptionalFloat64(testcase.input); + + const actual = encoder._view.buffer; + const expected = fill(testcase.want); + expect(actual).toStrictEqual(expected); + } + }); + + it("TestWriteOptionalBytes", () => { + const arr1 = new Uint8Array(1); + arr1.fill(1, 0, 1); + + const cases = [ + new Case("none", null, [192]), + new Case("Bytes", arr1.buffer, [196, 1, 1]), + ]; + + for (let i: i32 = 0; i < cases.length; ++i) { + const testcase = cases[i]; + const sizer = new WriteSizer(); + sizer.writeOptionalBytes(testcase.input); + const buffer = new ArrayBuffer(sizer.length); + const encoder = new WriteEncoder(buffer, sizer); + encoder.writeOptionalBytes(testcase.input); + + const actual = encoder._view.buffer; + const expected = fill(testcase.want); + expect(actual).toStrictEqual(expected); + } + }); + + it("TestWriteOptionalString", () => { + const cases = [ + new Case("none", null, [192]), + new Case( + "5-char String", + "hello", + [165,104,101,108,108,111]), + ]; + + for (let i: i32 = 0; i < cases.length; ++i) { + const testcase = cases[i]; + const sizer = new WriteSizer(); + sizer.writeOptionalString(testcase.input); + const buffer = new ArrayBuffer(sizer.length); + const encoder = new WriteEncoder(buffer, sizer); + encoder.writeOptionalString(testcase.input); + + const actual = encoder._view.buffer; + const expected = fill(testcase.want); + expect(actual).toStrictEqual(expected); + } + }); + + it("TestWriteOptionalBigInt", () => { + const cases = [ + new Case("none", null, [192]), + new Case( + "BigInt", + BigInt.fromString("3124124512598273468017578125"), + [188,51,49,50,52,49,50,52,53,49,50,53,57,56, + 50,55,51,52,54,56,48,49,55,53,55,56,49,50,53] + ), + ]; + + for (let i: i32 = 0; i < cases.length; ++i) { + const testcase = cases[i]; + const sizer = new WriteSizer(); + sizer.writeOptionalBigInt(testcase.input); + const buffer = new ArrayBuffer(sizer.length); + const encoder = new WriteEncoder(buffer, sizer); + encoder.writeOptionalBigInt(testcase.input); + + const actual = encoder._view.buffer; + const expected = fill(testcase.want); + expect(actual).toStrictEqual(expected); + } + }); + + it("TestWriteOptionalBigNumber", () => { + const cases = [ + new Case("none", null, [192]), + new Case( + "BigNumber", + BigNumber.fromString("3124124512.598273468017578125"), + [189,51,49,50,52,49,50,52,53,49,50,46,53,57,56, + 50,55,51,52,54,56,48,49,55,53,55,56,49,50,53] + ), + ]; + + for (let i: i32 = 0; i < cases.length; ++i) { + const testcase = cases[i]; + const sizer = new WriteSizer(); + sizer.writeOptionalBigNumber(testcase.input); + const buffer = new ArrayBuffer(sizer.length); + const encoder = new WriteEncoder(buffer, sizer); + encoder.writeOptionalBigNumber(testcase.input); + + const actual = encoder._view.buffer; + const expected = fill(testcase.want); + expect(actual).toStrictEqual(expected); + } + }); + + it("TestWriteOptionalJSON", () => { + const cases = [ + new Case("none", null, [192]), + new Case( + "JSON", + JSON.parse(`{"foo": "bar"}`), + [173,123,34,102,111,111,34,58,34,98,97,114,34,125] + ), + ]; + + for (let i: i32 = 0; i < cases.length; ++i) { + const testcase = cases[i]; + const sizer = new WriteSizer(); + sizer.writeOptionalJSON(testcase.input); + const buffer = new ArrayBuffer(sizer.length); + const encoder = new WriteEncoder(buffer, sizer); + encoder.writeOptionalJSON(testcase.input); + + const actual = encoder._view.buffer; + const expected = fill(testcase.want); + expect(actual).toStrictEqual(expected); + } + }); + + it("TestWriteOptionalArray", () => { + const cases = [ + new Case | null>("none", null, [192]), + new Case | null>("Array", [10, 20, 30], [147, 10, 20, 30]), + ]; + + for (let i: i32 = 0; i < cases.length; ++i) { + const testcase = cases[i]; + const sizer = new WriteSizer(); + sizer.writeOptionalArray( + testcase.input, + (writer: Write, item: u8) => { + writer.writeUInt8(item); + } + ); + const buffer = new ArrayBuffer(sizer.length); + const encoder = new WriteEncoder(buffer, sizer); + encoder.writeOptionalArray( + testcase.input, + (writer: Write, item: u8) => { + writer.writeUInt8(item); + } + ); + + const actual = encoder._view.buffer; + const expected = fill(testcase.want); + expect(actual).toStrictEqual(expected); + } + }); + + it("TestWriteOptionalMap", () => { + const map1 = new Map>(); + map1.set("foo", [1, -1, 42]); + map1.set("baz", [12412, -98987]); + + const cases = [ + new Case> | null>("none", null, [192]), + new Case> | null>( + "Map", + map1, + [130,163,102,111,111,147,1,255,42,163,98, + 97,122,146,209,48,124,210,255,254,125,85] + ), + ]; + + for (let i: i32 = 0; i < cases.length; ++i) { + const testcase = cases[i]; + const sizer = new WriteSizer(); + sizer.writeOptionalMap>( + testcase.input, + (writer: Write, key: string): void => { + writer.writeString(key); + }, + (writer: Write, value: Array) => { + writer.writeArray(value, (writer: Write, item: i32) => { + writer.writeInt32(item); + }); + } + ); + const buffer = new ArrayBuffer(sizer.length); + const encoder = new WriteEncoder(buffer, sizer); + encoder.writeOptionalMap>( + testcase.input, + (writer: Write, key: string): void => { + writer.writeString(key); + }, + (writer: Write, value: Array) => { + writer.writeArray(value, (writer: Write, item: i32) => { + writer.writeInt32(item); + }); + } + ); + + const actual = encoder._view.buffer; + const expected = fill(testcase.want); + expect(actual).toStrictEqual(expected); + } + }); + + it("TestWriteOptionalExtGenericMap", () => { + const map1 = new Map>(); + map1.set("foo", [1, -1, 42]); + map1.set("baz", [12412, -98987]); + + const cases = [ + new Case> | null>("none", null, [192]), + new Case> | null>( + "Map", + map1, + [199,22,1,130,163,102,111,111,147,1,255,42,163, + 98,97,122,146,209,48,124,210,255,254,125,85] + ), + ]; + + for (let i: i32 = 0; i < cases.length; ++i) { + const testcase = cases[i]; + const sizer = new WriteSizer(); + sizer.writeOptionalExtGenericMap>( + testcase.input, + (writer: Write, key: string): void => { + writer.writeString(key); + }, + (writer: Write, value: Array) => { + writer.writeArray(value, (writer: Write, item: i32) => { + writer.writeInt32(item); + }); + } + ); + const buffer = new ArrayBuffer(sizer.length); + const encoder = new WriteEncoder(buffer, sizer); + encoder.writeOptionalExtGenericMap>( + testcase.input, + (writer: Write, key: string): void => { + writer.writeString(key); + }, + (writer: Write, value: Array) => { + writer.writeArray(value, (writer: Write, item: i32) => { + writer.writeInt32(item); + }); + } + ); + + const actual = encoder._view.buffer; + const expected = fill(testcase.want); + expect(actual).toStrictEqual(expected); + } + }); +}); diff --git a/packages/wasm/as/assembly/msgpack/WriteEncoder.ts b/packages/wasm/as/assembly/msgpack/WriteEncoder.ts index 06a561ae00..36b4ff085c 100644 --- a/packages/wasm/as/assembly/msgpack/WriteEncoder.ts +++ b/packages/wasm/as/assembly/msgpack/WriteEncoder.ts @@ -343,7 +343,7 @@ export class WriteEncoder extends Write { this.writeBigInt(value); } - writeOptionalBigNumber(value: BigNumber): void { + writeOptionalBigNumber(value: BigNumber | null): void { if (value === null) { this.writeNil(); return; diff --git a/packages/wasm/as/assembly/msgpack/WriteSizer.ts b/packages/wasm/as/assembly/msgpack/WriteSizer.ts index 05d789c3a5..23cc096090 100644 --- a/packages/wasm/as/assembly/msgpack/WriteSizer.ts +++ b/packages/wasm/as/assembly/msgpack/WriteSizer.ts @@ -303,7 +303,7 @@ export class WriteSizer extends Write { this.writeBigInt(value); } - writeOptionalBigNumber(value: BigNumber): void { + writeOptionalBigNumber(value: BigNumber | null): void { if (value === null) { this.writeNil(); return;