From e0148007f10df56378d526416c46129c3b791e8a Mon Sep 17 00:00:00 2001 From: Victor Adossi Date: Tue, 21 Apr 2026 02:07:44 +0900 Subject: [PATCH 1/5] feat(jco): add future lower tests, refactor existing --- .../test-components/src/bin/future_lower.rs | 152 +++++ .../src/bin/{stream_rx.rs => stream_lower.rs} | 24 +- crates/test-components/wit/all.wit | 104 +++- packages/jco/test/p3/future-lower.js | 532 ++++++++++++++++++ packages/jco/test/p3/stream-lifts.js | 2 +- packages/jco/test/p3/stream-lowers.js | 256 +++------ 6 files changed, 870 insertions(+), 200 deletions(-) create mode 100644 crates/test-components/src/bin/future_lower.rs rename crates/test-components/src/bin/{stream_rx.rs => stream_lower.rs} (87%) create mode 100644 packages/jco/test/p3/future-lower.js diff --git a/crates/test-components/src/bin/future_lower.rs b/crates/test-components/src/bin/future_lower.rs new file mode 100644 index 000000000..57546b105 --- /dev/null +++ b/crates/test-components/src/bin/future_lower.rs @@ -0,0 +1,152 @@ +mod bindings { + use super::Component; + wit_bindgen::generate!({ + world: "future-lower", + }); + export!(Component); +} + +use wit_bindgen::{FutureReader, StreamReader}; + +use bindings::exports::jco::test_components::future_lower_async; +use bindings::exports::jco::test_components::future_lower_sync; +use bindings::jco::test_components::resources; + +use bindings::exports::jco::test_components::future_lower_async::{ + ExampleEnum, ExampleFlags, ExampleRecord, ExampleVariant, +}; + +struct Component; + +impl future_lower_sync::Guest for Component { + fn future_passthrough(rx: FutureReader) -> FutureReader { + rx + } +} + +impl future_lower_async::Guest for Component { + async fn future_passthrough(rx: FutureReader) -> FutureReader { + rx + } + + async fn read_future_value_bool(rx: FutureReader) -> bool { + rx.await + } + + async fn read_future_value_u8(rx: FutureReader) -> u8 { + rx.await + } + + async fn read_future_value_s8(rx: FutureReader) -> i8 { + rx.await + } + + async fn read_future_value_u16(rx: FutureReader) -> u16 { + rx.await + } + + async fn read_future_value_s16(rx: FutureReader) -> i16 { + rx.await + } + + async fn read_future_value_u32(rx: FutureReader) -> u32 { + rx.await + } + + async fn read_future_value_s32(rx: FutureReader) -> i32 { + rx.await + } + + async fn read_future_value_u64(rx: FutureReader) -> u64 { + rx.await + } + + async fn read_future_value_s64(rx: FutureReader) -> i64 { + rx.await + } + + async fn read_future_value_f32(rx: FutureReader) -> f32 { + rx.await + } + + async fn read_future_value_f64(rx: FutureReader) -> f64 { + rx.await + } + + async fn read_future_value_string(rx: FutureReader) -> String { + rx.await + } + + async fn read_future_value_record(rx: FutureReader) -> ExampleRecord { + rx.await + } + + async fn read_future_value_variant(rx: FutureReader) -> ExampleVariant { + rx.await + } + + async fn read_future_value_option_string(rx: FutureReader>) -> Option { + rx.await + } + + async fn read_future_value_result_string( + rx: FutureReader>, + ) -> Result { + rx.await + } + + async fn read_future_value_tuple(rx: FutureReader<(u32, i32, String)>) -> (u32, i32, String) { + rx.await + } + + async fn read_future_value_flags(rx: FutureReader) -> ExampleFlags { + rx.await + } + + async fn read_future_value_enum(rx: FutureReader) -> ExampleEnum { + rx.await + } + + async fn read_future_value_list_u8(rx: FutureReader>) -> Vec { + rx.await + } + + async fn read_future_value_list_string(rx: FutureReader>) -> Vec { + rx.await + } + + async fn read_future_value_fixed_list_u32(rx: FutureReader<[u32; 5]>) -> [u32; 5] { + rx.await + } + + async fn read_future_value_list_record( + rx: FutureReader>, + ) -> Vec { + rx.await + } + + async fn read_future_value_example_resource_own(rx: FutureReader) { + let _ = rx.await; + // All vals dropped at the end of this function + } + + async fn read_future_value_example_resource_own_attr( + rx: FutureReader, + ) -> u32 { + rx.await.get_id() + } + + async fn read_future_value_future_string(rx: FutureReader>) -> String { + rx.await.await + } + + async fn read_future_value_stream_string( + rx: FutureReader>, + ) -> Vec { + let s = rx.await; + s.collect().await + } +} + +// Stub only to ensure this works as a binary +fn main() {} diff --git a/crates/test-components/src/bin/stream_rx.rs b/crates/test-components/src/bin/stream_lower.rs similarity index 87% rename from crates/test-components/src/bin/stream_rx.rs rename to crates/test-components/src/bin/stream_lower.rs index 6187487e0..625c30a63 100644 --- a/crates/test-components/src/bin/stream_rx.rs +++ b/crates/test-components/src/bin/stream_lower.rs @@ -1,30 +1,30 @@ mod bindings { use super::Component; wit_bindgen::generate!({ - world: "stream-rx", + world: "stream-lower", }); export!(Component); } -use wit_bindgen::StreamReader; +use wit_bindgen::{FutureReader, StreamReader}; -use bindings::exports::jco::test_components::use_stream_async; -use bindings::exports::jco::test_components::use_stream_sync; +use bindings::exports::jco::test_components::stream_lower_async; +use bindings::exports::jco::test_components::stream_lower_sync; use bindings::jco::test_components::resources; -use bindings::exports::jco::test_components::use_stream_async::{ +use bindings::exports::jco::test_components::stream_lower_async::{ ExampleEnum, ExampleFlags, ExampleRecord, ExampleVariant, }; struct Component; -impl use_stream_sync::Guest for Component { +impl stream_lower_sync::Guest for Component { fn stream_passthrough(rx: StreamReader) -> StreamReader { rx } } -impl use_stream_async::Guest for Component { +impl stream_lower_async::Guest for Component { async fn stream_passthrough(rx: StreamReader) -> StreamReader { rx } @@ -153,6 +153,16 @@ impl use_stream_async::Guest for Component { } vals } + + async fn read_stream_values_future_string( + mut stream_rx: StreamReader>, + ) -> Vec { + let mut vals = Vec::new(); + while let Some(fut_rx) = stream_rx.next().await { + vals.push(fut_rx.await); + } + vals + } } async fn read_async_values(mut rx: StreamReader) -> Vec { diff --git a/crates/test-components/wit/all.wit b/crates/test-components/wit/all.wit index 8de4908d3..431936cb1 100644 --- a/crates/test-components/wit/all.wit +++ b/crates/test-components/wit/all.wit @@ -119,7 +119,6 @@ interface get-stream-async { get-stream-future-string: async func(vals: list>) -> stream>; } - interface get-future-async { use resources.{example-resource}; use example-types.{example-variant, example-enum, example-record, example-flags}; @@ -189,7 +188,7 @@ interface get-future-async { // The unspooled version should take advantage of host->host optimizations get-future-stream-string: async func(vals: stream) -> future>; - // The spooled version returns a *new* stream that forces transit through the component + // The spooled version returns a *new* stream that forces transit through the component get-future-stream-string-spool: async func(vals: list) -> future>; } @@ -257,16 +256,16 @@ world basic-run-string { export local-run-string; } -////////////////// -// Stream Usage // -////////////////// +////////////////////// +// Stream lowerings // +////////////////////// -interface use-stream-sync { - // TODO(fix): optimize host-only stream usage -- detect when the read & write are held by the host +interface stream-lower-sync { + // TODO(fix): optimize host-only stream usage -- detect when the read & write are held by the host stream-passthrough: func(s: stream) -> stream; } -interface use-stream-async { +interface stream-lower-async { use resources.{example-resource}; use example-types.{example-variant, example-enum, example-record, example-flags}; @@ -317,12 +316,91 @@ interface use-stream-async { read-stream-values-stream-string: async func(s: stream>) -> list>; - //read-stream-values-future-string: async func(vals: list>) -> result>, string>; + read-stream-values-future-string: async func(vals: stream>) -> list; } -world stream-rx { +world stream-lower { import resources; - export use-stream-sync; - export use-stream-async; -} \ No newline at end of file + export stream-lower-sync; + export stream-lower-async; +} + +////////////////////// +// Future lowerings // +////////////////////// + +interface future-lower-sync { + // TODO(fix): optimize host-only stream usage -- detect when the read & write are held by the host + future-passthrough: func(s: future) -> future; +} + +interface future-lower-async { + use resources.{example-resource}; + use example-types.{example-variant, example-enum, example-record, example-flags}; + + future-passthrough: async func(s: future) -> future; + + read-future-value-bool: async func(f: future) -> bool; + + read-future-value-u32: async func(f: future) -> u32; + read-future-value-s32: async func(f: future) -> s32; + + read-future-value-u8: async func(f: future) -> u8; + read-future-value-s8: async func(f: future) -> s8; + + read-future-value-u16: async func(f: future) -> u16; + read-future-value-s16: async func(f: future) -> s16; + + read-future-value-u64: async func(f: future) -> u64; + read-future-value-s64: async func(f: future) -> s64; + + read-future-value-f32: async func(f: future) -> f32; + + read-future-value-f64: async func(f: future) -> f64; + + // NOTE: future is not yet supported upfuture (future can be used instead) + // + // get-future-char: async func(vals: list) -> result, string>; + + read-future-value-string: async func(f: future) -> string; + + read-future-value-record: async func(f: future) -> example-record; + + read-future-value-variant: async func(f: future) -> example-variant; + + read-future-value-list-u8: async func(f: future>) -> list; + read-future-value-list-string: async func(f: future>) -> list; + read-future-value-list-record: async func(f: future>) -> list; + read-future-value-fixed-list-u32: async func(f: future>) -> list; + + read-future-value-tuple: async func(f: future>) -> tuple; + + read-future-value-flags: async func(f: future) -> example-flags; + + read-future-value-enum: async func(f: future) -> example-enum; + + read-future-value-option-string: async func(f: future>) -> option; + + read-future-value-result-string: async func(f: future>) -> result; + + // NOTE: future> is not supoprted + // + // get-future-example-resource-borrow: async func( + // vals: list> + // ) -> result>, string>; + + read-future-value-example-resource-own: async func(s: future); + + read-future-value-example-resource-own-attr: async func(s: future) -> u32; + + read-future-value-future-string: async func(f: future>) -> string; + + read-future-value-stream-string: async func(vals: future>) -> list; +} + +world future-lower { + import resources; + export future-lower-sync; + export future-lower-async; +} diff --git a/packages/jco/test/p3/future-lower.js b/packages/jco/test/p3/future-lower.js new file mode 100644 index 000000000..d037d19ce --- /dev/null +++ b/packages/jco/test/p3/future-lower.js @@ -0,0 +1,532 @@ +import { join } from "node:path"; +import { ReadableStream } from "node:stream/web"; + +import { suite, test, assert, beforeAll, beforeEach, afterAll } from "vitest"; + +import { setupAsyncTest } from "../helpers.js"; +import { AsyncFunction, LOCAL_TEST_COMPONENTS_DIR, createReadableStreamFromValues } from "../common.js"; +import { WASIShim } from "@bytecodealliance/preview2-shim/instantiation"; + +suite("future lowers", () => { + let esModule, cleanup, instance; + + class ExampleResource { + #id; + + dropped = false; + + constructor(id) { + this.#id = id; + } + + getId() { + return this.#id; + } + } + + beforeAll(async () => { + const name = "future-lower"; + const setupRes = await setupAsyncTest({ + asyncMode: "jspi", + component: { + name, + path: join(LOCAL_TEST_COMPONENTS_DIR, `${name}.wasm`), + skipInstantiation: true, + }, + jco: { + transpile: { + extraArgs: { + minify: false, + }, + }, + }, + }); + + esModule = setupRes.esModule; + cleanup = setupRes.cleanup; + }); + + afterAll(async () => { + await cleanup(); + }); + + beforeEach(async () => { + instance = await esModule.instantiate(undefined, { + ...new WASIShim().getImportObject(), + "jco:test-components/resources": { + ExampleResource, + }, + }); + }); + + test.concurrent("sync passthrough", async () => { + assert.notInstanceOf(instance["jco:test-components/future-lower-sync"].futurePassthrough, AsyncFunction); + + let vals = [0, 5, 10]; + for (const [idx, v] of vals.entries()) { + assert.strictEqual( + await instance["jco:test-components/future-lower-sync"].futurePassthrough(Promise.resolve(v)), + vals[idx] + ); + } + }); + + // Test late writer -- component should block until a value is written, + // and we should handle a final value + done from an iterator properly + test.concurrent("sync passthrough (slow writer)", async () => { + assert.notInstanceOf(instance["jco:test-components/future-lower-sync"].futurePassthrough, AsyncFunction); + + const delayed = new Promise((resolve) => setTimeout(() => resolve(42), 300)); + assert.strictEqual( + await instance["jco:test-components/future-lower-sync"].futurePassthrough(delayed), + 42, + ); + }); + + test.concurrent("async passthrough", async () => { + assert.instanceOf(instance["jco:test-components/future-lower-async"].futurePassthrough, AsyncFunction); + + let vals = [10, 5, 0]; + for (const [idx, v] of vals.entries()) { + assert.strictEqual( + await instance["jco:test-components/future-lower-async"].futurePassthrough(Promise.resolve(v)), + vals[idx] + ); + } + }); + + test.concurrent("async passthrough (slow writer)", async () => { + assert.instanceOf(instance["jco:test-components/future-lower-async"].futurePassthrough, AsyncFunction); + + const delayed = new Promise((resolve) => setTimeout(() => resolve(42), 300)); + assert.strictEqual( + await instance["jco:test-components/future-lower-async"].futurePassthrough(delayed), + 42, + ); + + }); + + test.concurrent("bool", async () => { + assert.instanceOf(instance["jco:test-components/future-lower-async"].readFutureValueBool, AsyncFunction); + + let vals = [true, false]; + for (const [idx, v] of vals.entries()) { + assert.strictEqual( + await instance["jco:test-components/future-lower-async"].readFutureValueBool( + Promise.resolve(v), + ), + vals[idx], + ); + } + }); + + test.concurrent("u8/s8", async () => { + assert.instanceOf(instance["jco:test-components/future-lower-async"].readFutureValueU8, AsyncFunction); + assert.instanceOf(instance["jco:test-components/future-lower-async"].readFutureValueS8, AsyncFunction); + + let vals = [0, 1, 255]; + for (const [idx, v] of vals.entries()) { + assert.strictEqual( + await instance["jco:test-components/future-lower-async"].readFutureValueU8( + Promise.resolve(v), + ), + vals[idx], + ); + } + + vals = [-128, 0, 1, 127]; + for (const [idx, v] of vals.entries()) { + assert.strictEqual( + await instance["jco:test-components/future-lower-async"].readFutureValueS8( + Promise.resolve(v), + ), + vals[idx], + ); + } + }); + + test.concurrent("u16/s16", async () => { + assert.instanceOf(instance["jco:test-components/future-lower-async"].readFutureValueU16, AsyncFunction); + assert.instanceOf(instance["jco:test-components/future-lower-async"].readFutureValueS16, AsyncFunction); + + let vals = [0, 100, 65535]; + for (const [idx, v] of vals.entries()) { + assert.strictEqual( + await instance["jco:test-components/future-lower-async"].readFutureValueU16( + Promise.resolve(v), + ), + vals[idx], + ); + } + + vals = [-32_768, 0, 32_767]; + for (const [idx, v] of vals.entries()) { + assert.strictEqual( + await instance["jco:test-components/future-lower-async"].readFutureValueS16( + Promise.resolve(v), + ), + vals[idx], + ); + } + }); + + test.concurrent("u32/s32", async () => { + assert.instanceOf(instance["jco:test-components/future-lower-async"].readFutureValueU32, AsyncFunction); + assert.instanceOf(instance["jco:test-components/future-lower-async"].readFutureValueS32, AsyncFunction); + + let vals = [10, 5, 0]; + for (const [idx, v] of vals.entries()) { + assert.strictEqual( + await instance["jco:test-components/future-lower-async"].readFutureValueU32( + Promise.resolve(v), + ), + vals[idx], + ); + } + + vals = [-32, 90001, 3200000]; + for (const [idx, v] of vals.entries()) { + assert.strictEqual( + await instance["jco:test-components/future-lower-async"].readFutureValueS32( + Promise.resolve(v), + ), + vals[idx], + ); + } + }); + + test.concurrent("u64/s64", async () => { + assert.instanceOf(instance["jco:test-components/future-lower-async"].readFutureValueU64, AsyncFunction); + assert.instanceOf(instance["jco:test-components/future-lower-async"].readFutureValueS64, AsyncFunction); + + let vals = [0n, 100n, 65535n]; + for (const [idx, v] of vals.entries()) { + assert.strictEqual( + await instance["jco:test-components/future-lower-async"].readFutureValueU64( + Promise.resolve(v), + ), + vals[idx], + ); + } + + vals = [-32_768n, 0n, 32_767n]; + for (const [idx, v] of vals.entries()) { + assert.strictEqual( + await instance["jco:test-components/future-lower-async"].readFutureValueS64( + Promise.resolve(v), + ), + vals[idx], + ); + } + }); + + test.concurrent("f32/f64", async () => { + assert.instanceOf(instance["jco:test-components/future-lower-async"].readFutureValueF32, AsyncFunction); + assert.instanceOf(instance["jco:test-components/future-lower-async"].readFutureValueF64, AsyncFunction); + + let vals = [-300.01235, -1.5, -0.0, 0.0, 1.5, 300.01235]; + for (const [idx, v] of vals.entries()) { + assert.closeTo( + await instance["jco:test-components/future-lower-async"].readFutureValueF32( + Promise.resolve(v), + ), + vals[idx], + 0.01, + ); + assert.closeTo( + await instance["jco:test-components/future-lower-async"].readFutureValueF64( + Promise.resolve(v), + ), + vals[idx], + 0.01, + ); + } + + vals = [-60000.01235, -1.5, -0.0, 0.0, 1.5, -60000.01235]; + for (const [idx, v] of vals.entries()) { + assert.closeTo( + await instance["jco:test-components/future-lower-async"].readFutureValueF32( + Promise.resolve(v), + ), + vals[idx], + 0.01, + ); + assert.closeTo( + await instance["jco:test-components/future-lower-async"].readFutureValueF64( + Promise.resolve(v), + ), + vals[idx], + 0.01, + ); + } + }); + + test.concurrent("string", async () => { + assert.instanceOf(instance["jco:test-components/future-lower-async"].readFutureValueString, AsyncFunction); + + let vals = ["hello", "world", "!"]; + for (const [idx, v] of vals.entries()) { + assert.strictEqual( + await instance["jco:test-components/future-lower-async"].readFutureValueString( + Promise.resolve(v), + ), + vals[idx], + ); + } + }); + + test.only("record", async () => { + assert.instanceOf(instance["jco:test-components/future-lower-async"].readFutureValueRecord, AsyncFunction); + + let vals = [ + { id: 3, idStr: "three" }, + { id: 2, idStr: "two" }, + { id: 1, idStr: "one" }, + ]; + for (const [idx, v] of vals.entries()) { + assert.deepEqual( + await instance["jco:test-components/future-lower-async"].readFutureValueRecord( + Promise.resolve(v), + ), + vals[idx], + ); + } + }); + + test.skip("variant", async () => { + assert.instanceOf(instance["jco:test-components/future-lower-async"].readFutureValueVariant, AsyncFunction); + + let vals = [ + { tag: "maybe-u32", val: 123 }, + { tag: "maybe-u32", val: null }, + { tag: "str", val: "string-value" }, + { tag: "num", val: 1 }, + ]; + let expected = [ + // TODO: wit type representation smoothing mismatch + { tag: "maybe-u32", val: { tag: "some", val: 123 } }, + { tag: "maybe-u32", val: { tag: "none" } }, + { tag: "str", val: "string-value" }, + { tag: "num", val: 1 }, + ]; + for (const [idx, v] of vals.entries()) { + assert.deepEqual( + await instance["jco:test-components/future-lower-async"].readFutureValueVariant( + Promise.resolve(v), + ), + expected[idx], + 0.01, + ); + } + + vals = [{ tag: "float", val: 123.1 }]; + for (const [idx, v] of vals.entries()) { + const returned = await instance["jco:test-components/future-lower-async"].readFutureValueVariant( + Promise.resolve(v), + ); + assert.closeTo( + returned.val, + vals[idx].val, + 0.01, + ); + } + }); + + test.skip("tuple", async () => { + assert.instanceOf(instance["jco:test-components/future-lower-async"].readFutureValueTuple, AsyncFunction); + + let vals = [ + [1, -1, "one"], + [2, -2, "two"], + [3, -3, "two"], + ]; + for (const [idx, v] of vals.entries()) { + assert.deepEqual( + await instance["jco:test-components/future-lower-async"].readFutureValueTuple( + Promise.resolve(v), + ), + vals[idx], + ); + } + }); + + test.concurrent("flags", async () => { + assert.instanceOf(instance["jco:test-components/future-lower-async"].readFutureValueFlags, AsyncFunction); + + let vals = [ + { first: true, second: false, third: false }, + { first: false, second: true, third: false }, + { first: false, second: false, third: true }, + ]; + for (const [idx, v] of vals.entries()) { + assert.deepEqual( + await instance["jco:test-components/future-lower-async"].readFutureValueFlags( + Promise.resolve(v), + ), + vals[idx], + ); + } + }); + + test.concurrent("enum", async () => { + assert.instanceOf(instance["jco:test-components/future-lower-async"].readFutureValueEnum, AsyncFunction); + + let vals = ["first", "second", "third"]; + for (const [idx, v] of vals.entries()) { + assert.deepEqual( + await instance["jco:test-components/future-lower-async"].readFutureValueEnum( + Promise.resolve(v), + ), + vals[idx], + ); + } + }); + + // test.concurrent("option", async () => { + // assert.instanceOf(instance["jco:test-components/future-lower-async"].readFutureValueOptionString, AsyncFunction); + + // let vals = ["present string", null]; + // let returnedVals = await instance["jco:test-components/future-lower-async"].readFutureValueOptionString( + // Promise.resolve(vals), + // ); + // assert.deepEqual(returnedVals, [ + // // TODO: wit type representation smoothing mismatch + // { tag: "some", val: "present string" }, + // { tag: "none" }, + // ]); + // }); + + // test.concurrent("result", async () => { + // assert.instanceOf(instance["jco:test-components/future-lower-async"].readFutureValueResultString, AsyncFunction); + + // let vals = [{ tag: "ok", val: "present string" }, { tag: "err", val: "nope" }, "bare string (ok)"]; + // let returnedVals = await instance["jco:test-components/future-lower-async"].readFutureValueResultString( + // Promise.resolve(vals), + // ); + // assert.deepEqual(returnedVals, [ + // // TODO: wit type representation smoothing mismatch + // { tag: "ok", val: "present string" }, + // { tag: "err", val: "nope" }, + // { tag: "ok", val: "bare string (ok)" }, + // ]); + // }); + + // test.concurrent("list", async () => { + // assert.instanceOf(instance["jco:test-components/future-lower-async"].readFutureValueListU8, AsyncFunction); + + // let vals = [[0x01, 0x02, 0x03, 0x04, 0x05], new Uint8Array([0x05, 0x04, 0x03, 0x02, 0x01]), []]; + // let returnedVals = await instance["jco:test-components/future-lower-async"].readFutureValueListU8( + // Promise.resolve(vals), + // ); + // assert.deepEqual(returnedVals, [ + // // TODO: wit type representation smoothing mismatch + // vals[0], + // [...vals[1]], + // [], + // ]); + // }); + + // test.concurrent("list", async () => { + // assert.instanceOf(instance["jco:test-components/future-lower-async"].readFutureValueListString, AsyncFunction); + + // let vals = [["first", "second", "third"], []]; + // let returnedVals = await instance["jco:test-components/future-lower-async"].readFutureValueListString( + // Promise.resolve(vals), + // ); + // assert.deepEqual(returnedVals, vals); + // }); + + // test.concurrent("list>", async () => { + // assert.instanceOf(instance["jco:test-components/future-lower-async"].readFutureValueFixedListU32, AsyncFunction); + + // let vals = [ + // [ + // [1, 2, 3, 4, 5], + // [0, 0, 0, 0, 0], + // ], + // [[0, 0, 0, 0, 0], new Uint32Array([0x05, 0x04, 0x03, 0x02, 0x01])], + // ]; + // let returnedVals = await instance["jco:test-components/future-lower-async"].readFutureValueFixedListU32( + // Promise.resolve(vals), + // ); + // assert.deepEqual(returnedVals, [ + // // TODO(fix): wit type representation smoothing mismatch + // [ + // [1, 2, 3, 4, 5], + // [0, 0, 0, 0, 0], + // ], + // [ + // [0, 0, 0, 0, 0], + // [0x05, 0x04, 0x03, 0x02, 0x01], + // ], + // ]); + // }); + + // test.concurrent("list", async () => { + // assert.instanceOf(instance["jco:test-components/future-lower-async"].readFutureValueListRecord, AsyncFunction); + + // let vals = [ + // [ + // { id: 3, idStr: "three" }, + // { id: 2, idStr: "two" }, + // { id: 1, idStr: "one" }, + // ], + // [ + // { id: 1, idStr: "one-one" }, + // { id: 2, idStr: "two-two" }, + // { id: 3, idStr: "three-three" }, + // ], + // ]; + // let returnedVals = await instance["jco:test-components/future-lower-async"].readFutureValueListRecord( + // Promise.resolve(vals), + // ); + // assert.deepEqual(returnedVals, vals); + // }); + + // test.concurrent("example-resource", async () => { + // assert.instanceOf( + // instance["jco:test-components/future-lower-async"].readFutureValueExampleResourceOwn, + // AsyncFunction, + // ); + + // let vals = [new ExampleResource(0), new ExampleResource(1), new ExampleResource(2)]; + // await instance["jco:test-components/future-lower-async"].readFutureValueExampleResourceOwn( + // Promise.resolve(vals), + // ); + // // TODO(fix): we shoudl be able to ensure destructor call + // // see: https://github.com/bytecodealliance/jco/issues/989 + // // assert(vals.every(r => r.dropped)); + // }); + + // test.concurrent("example-resource#get-id", async () => { + // assert.instanceOf( + // instance["jco:test-components/future-lower-async"].readFutureValueExampleResourceOwnAttr, + // AsyncFunction, + // ); + + // let vals = [new ExampleResource(2), new ExampleResource(1), new ExampleResource(0)]; + // const returnedVals = await instance[ + // "jco:test-components/future-lower-async" + // ].readFutureValueExampleResourceOwnAttr(Promise.resolve(vals)); + // assert.deepEqual(returnedVals, [2, 1, 0]); + // }); + + // test.concurrent("future", async () => { + // assert.instanceOf(instance["jco:test-components/future-lower-async"].readFutureValueFutureString, AsyncFunction); + + // let vals = [ + // Promise.resolve(["first", "future", "values"]), + // Promise.resolve(["second", "future", "here"]), + // Promise.resolve(["third", "values", "in future"]), + // ]; + // const returnedVals = await instance["jco:test-components/future-lower-async"].readFutureValueFutureString( + // Promise.resolve(vals), + // ); + // assert.deepEqual(returnedVals, [ + // ["first", "future", "values"], + // ["second", "future", "here"], + // ["third", "values", "in future"], + // ]); + // }); + +}); diff --git a/packages/jco/test/p3/stream-lifts.js b/packages/jco/test/p3/stream-lifts.js index 39b50ad44..e4d5c5796 100644 --- a/packages/jco/test/p3/stream-lifts.js +++ b/packages/jco/test/p3/stream-lifts.js @@ -50,7 +50,7 @@ suite("stream lifts", () => { }); getInstance = () => Promise.resolve(instance); - // NOTE: To use an explicitly new instance per-test (more stable), uncomment the lines below + // NOTE: To use an explicitly new instance per-test, uncomment the lines below // // getInstance = async () => esModule.instantiate(undefined, { // ...new WASIShim().getImportObject(), diff --git a/packages/jco/test/p3/stream-lowers.js b/packages/jco/test/p3/stream-lowers.js index 60d163171..0ac5c64de 100644 --- a/packages/jco/test/p3/stream-lowers.js +++ b/packages/jco/test/p3/stream-lowers.js @@ -25,7 +25,7 @@ suite("stream lowers", () => { } beforeAll(async () => { - const name = "stream-rx"; + const name = "stream-lower"; const setupRes = await setupAsyncTest({ asyncMode: "jspi", component: { @@ -55,7 +55,7 @@ suite("stream lowers", () => { }); getInstance = () => Promise.resolve(instance); - // NOTE: To use an explicitly new instance per-test (more stable), uncomment the lines below + // NOTE: To use an explicitly new instance per-test, uncomment the lines below // // getInstance = async () => esModule.instantiate(undefined, { // ...new WASIShim().getImportObject(), @@ -72,7 +72,7 @@ suite("stream lowers", () => { describe("sync", () => { test("sync passthrough", async () => { const instance = await getInstance(); - assert.notInstanceOf(instance["jco:test-components/use-stream-sync"].streamPassthrough, AsyncFunction); + assert.notInstanceOf(instance["jco:test-components/stream-lower-sync"].streamPassthrough, AsyncFunction); let vals = [0, 5, 10]; const readerStream = new ReadableStream({ @@ -82,7 +82,7 @@ suite("stream lowers", () => { }, }); - let returnedStream = instance["jco:test-components/use-stream-sync"].streamPassthrough(readerStream); + let returnedStream = instance["jco:test-components/stream-lower-sync"].streamPassthrough(readerStream); // NOTE: Returned streams conform to the async iterator protocol -- they *do not* confirm to // any other interface, though an object that is a ReadableStream may have been passed in. @@ -110,7 +110,7 @@ suite("stream lowers", () => { }; }, }; - returnedStream = instance["jco:test-components/use-stream-sync"].streamPassthrough(lateStream); + returnedStream = instance["jco:test-components/stream-lower-sync"].streamPassthrough(lateStream); returnedVals = []; for await (const v of returnedStream) { @@ -122,8 +122,20 @@ suite("stream lowers", () => { describe("async", () => { test.concurrent("async passthrough", async () => { + + + // test.concurrent("async passthrough", async () => { + // assert.instanceOf(instance["jco:test-components/stream-lower-async"].streamPassthrough, AsyncFunction); + // let stream = await instance["jco:test-components/stream-lower-async"].streamPassthrough(readerStream); + // let returnedVals = []; + // for await (const v of stream) { + // returnedVals.push(v); + // } + // assert.deepEqual(vals, returnedVals); + // }); + const instance = await getInstance(); - assert.instanceOf(instance["jco:test-components/use-stream-async"].streamPassthrough, AsyncFunction); + assert.instanceOf(instance["jco:test-components/stream-lower-async"].streamPassthrough, AsyncFunction); let vals = [10, 5, 0]; const readerStream = new ReadableStream({ @@ -133,7 +145,7 @@ suite("stream lowers", () => { }, }); - let stream = await instance["jco:test-components/use-stream-async"].streamPassthrough(readerStream); + let stream = await instance["jco:test-components/stream-lower-async"].streamPassthrough(readerStream); let returnedVals = []; for await (const v of stream) { returnedVals.push(v); @@ -143,10 +155,10 @@ suite("stream lowers", () => { test.concurrent("bool", async () => { const instance = await getInstance(); - assert.instanceOf(instance["jco:test-components/use-stream-async"].readStreamValuesBool, AsyncFunction); + assert.instanceOf(instance["jco:test-components/stream-lower-async"].readStreamValuesBool, AsyncFunction); let vals = [true, false]; - let returnedVals = await instance["jco:test-components/use-stream-async"].readStreamValuesBool( + let returnedVals = await instance["jco:test-components/stream-lower-async"].readStreamValuesBool( createReadableStreamFromValues(vals), ); assert.deepEqual(returnedVals, vals); @@ -154,17 +166,17 @@ suite("stream lowers", () => { test.concurrent("u8/s8", async () => { const instance = await getInstance(); - assert.instanceOf(instance["jco:test-components/use-stream-async"].readStreamValuesU8, AsyncFunction); - assert.instanceOf(instance["jco:test-components/use-stream-async"].readStreamValuesS8, AsyncFunction); + assert.instanceOf(instance["jco:test-components/stream-lower-async"].readStreamValuesU8, AsyncFunction); + assert.instanceOf(instance["jco:test-components/stream-lower-async"].readStreamValuesS8, AsyncFunction); let vals = [0, 1, 255]; - let returnedVals = await instance["jco:test-components/use-stream-async"].readStreamValuesU8( + let returnedVals = await instance["jco:test-components/stream-lower-async"].readStreamValuesU8( createReadableStreamFromValues(vals), ); assert.deepEqual(returnedVals, vals); vals = [-128, 0, 1, 127]; - returnedVals = await instance["jco:test-components/use-stream-async"].readStreamValuesS8( + returnedVals = await instance["jco:test-components/stream-lower-async"].readStreamValuesS8( createReadableStreamFromValues(vals), ); assert.deepEqual(returnedVals, vals); @@ -172,17 +184,17 @@ suite("stream lowers", () => { test.concurrent("u16/s16", async () => { const instance = await getInstance(); - assert.instanceOf(instance["jco:test-components/use-stream-async"].readStreamValuesU16, AsyncFunction); - assert.instanceOf(instance["jco:test-components/use-stream-async"].readStreamValuesS16, AsyncFunction); + assert.instanceOf(instance["jco:test-components/stream-lower-async"].readStreamValuesU16, AsyncFunction); + assert.instanceOf(instance["jco:test-components/stream-lower-async"].readStreamValuesS16, AsyncFunction); let vals = [0, 100, 65535]; - let returnedVals = await instance["jco:test-components/use-stream-async"].readStreamValuesU16( + let returnedVals = await instance["jco:test-components/stream-lower-async"].readStreamValuesU16( createReadableStreamFromValues(vals), ); assert.deepEqual(returnedVals, vals); vals = [-32_768, 0, 32_767]; - returnedVals = await instance["jco:test-components/use-stream-async"].readStreamValuesS16( + returnedVals = await instance["jco:test-components/stream-lower-async"].readStreamValuesS16( createReadableStreamFromValues(vals), ); assert.deepEqual(returnedVals, vals); @@ -190,17 +202,17 @@ suite("stream lowers", () => { test.concurrent("u32/s32", async () => { const instance = await getInstance(); - assert.instanceOf(instance["jco:test-components/use-stream-async"].readStreamValuesU32, AsyncFunction); - assert.instanceOf(instance["jco:test-components/use-stream-async"].readStreamValuesS32, AsyncFunction); + assert.instanceOf(instance["jco:test-components/stream-lower-async"].readStreamValuesU32, AsyncFunction); + assert.instanceOf(instance["jco:test-components/stream-lower-async"].readStreamValuesS32, AsyncFunction); let vals = [10, 5, 0]; - let returnedVals = await instance["jco:test-components/use-stream-async"].readStreamValuesU32( + let returnedVals = await instance["jco:test-components/stream-lower-async"].readStreamValuesU32( createReadableStreamFromValues(vals), ); assert.deepEqual(returnedVals, vals); vals = [-32, 90001, 3200000]; - returnedVals = await instance["jco:test-components/use-stream-async"].readStreamValuesS32( + returnedVals = await instance["jco:test-components/stream-lower-async"].readStreamValuesS32( createReadableStreamFromValues(vals), ); assert.deepEqual(returnedVals, vals); @@ -208,17 +220,17 @@ suite("stream lowers", () => { test.concurrent("u64/s64", async () => { const instance = await getInstance(); - assert.instanceOf(instance["jco:test-components/use-stream-async"].readStreamValuesU64, AsyncFunction); - assert.instanceOf(instance["jco:test-components/use-stream-async"].readStreamValuesS64, AsyncFunction); + assert.instanceOf(instance["jco:test-components/stream-lower-async"].readStreamValuesU64, AsyncFunction); + assert.instanceOf(instance["jco:test-components/stream-lower-async"].readStreamValuesS64, AsyncFunction); let vals = [0n, 100n, 65535n]; - let returnedVals = await instance["jco:test-components/use-stream-async"].readStreamValuesU64( + let returnedVals = await instance["jco:test-components/stream-lower-async"].readStreamValuesU64( createReadableStreamFromValues(vals), ); assert.deepEqual(returnedVals, vals); vals = [-32_768n, 0n, 32_767n]; - returnedVals = await instance["jco:test-components/use-stream-async"].readStreamValuesS64( + returnedVals = await instance["jco:test-components/stream-lower-async"].readStreamValuesS64( createReadableStreamFromValues(vals), ); assert.deepEqual(returnedVals, vals); @@ -226,17 +238,17 @@ suite("stream lowers", () => { test.concurrent("f32/f64", async () => { const instance = await getInstance(); - assert.instanceOf(instance["jco:test-components/use-stream-async"].readStreamValuesF32, AsyncFunction); - assert.instanceOf(instance["jco:test-components/use-stream-async"].readStreamValuesF64, AsyncFunction); + assert.instanceOf(instance["jco:test-components/stream-lower-async"].readStreamValuesF32, AsyncFunction); + assert.instanceOf(instance["jco:test-components/stream-lower-async"].readStreamValuesF64, AsyncFunction); let vals = [-300.01235, -1.5, -0.0, 0.0, 1.5, 300.01235]; - let returnedVals = await instance["jco:test-components/use-stream-async"].readStreamValuesF32( + let returnedVals = await instance["jco:test-components/stream-lower-async"].readStreamValuesF32( createReadableStreamFromValues(vals), ); vals.entries().forEach(([idx, v]) => assert.closeTo(v, returnedVals[idx], 0.01)); vals = [-60000.01235, -1.5, -0.0, 0.0, 1.5, -60000.01235]; - returnedVals = await instance["jco:test-components/use-stream-async"].readStreamValuesF32( + returnedVals = await instance["jco:test-components/stream-lower-async"].readStreamValuesF32( createReadableStreamFromValues(vals), ); vals.entries().forEach(([idx, v]) => assert.closeTo(v, returnedVals[idx], 0.01)); @@ -244,10 +256,10 @@ suite("stream lowers", () => { test.concurrent("string", async () => { const instance = await getInstance(); - assert.instanceOf(instance["jco:test-components/use-stream-async"].readStreamValuesString, AsyncFunction); + assert.instanceOf(instance["jco:test-components/stream-lower-async"].readStreamValuesString, AsyncFunction); let vals = ["hello", "world", "!"]; - let returnedVals = await instance["jco:test-components/use-stream-async"].readStreamValuesString( + let returnedVals = await instance["jco:test-components/stream-lower-async"].readStreamValuesString( createReadableStreamFromValues(vals), ); assert.deepEqual(returnedVals, vals); @@ -255,14 +267,14 @@ suite("stream lowers", () => { test.concurrent("record", async () => { const instance = await getInstance(); - assert.instanceOf(instance["jco:test-components/use-stream-async"].readStreamValuesRecord, AsyncFunction); + assert.instanceOf(instance["jco:test-components/stream-lower-async"].readStreamValuesRecord, AsyncFunction); let vals = [ { id: 3, idStr: "three" }, { id: 2, idStr: "two" }, { id: 1, idStr: "one" }, ]; - let returnedVals = await instance["jco:test-components/use-stream-async"].readStreamValuesRecord( + let returnedVals = await instance["jco:test-components/stream-lower-async"].readStreamValuesRecord( createReadableStreamFromValues(vals), ); assert.deepEqual(returnedVals, vals); @@ -270,7 +282,7 @@ suite("stream lowers", () => { test.concurrent("variant", async () => { const instance = await getInstance(); - assert.instanceOf(instance["jco:test-components/use-stream-async"].readStreamValuesVariant, AsyncFunction); + assert.instanceOf(instance["jco:test-components/stream-lower-async"].readStreamValuesVariant, AsyncFunction); let vals = [ { tag: "maybe-u32", val: 123 }, @@ -278,7 +290,7 @@ suite("stream lowers", () => { { tag: "str", val: "string-value" }, { tag: "num", val: 1 }, ]; - let returnedVals = await instance["jco:test-components/use-stream-async"].readStreamValuesVariant( + let returnedVals = await instance["jco:test-components/stream-lower-async"].readStreamValuesVariant( createReadableStreamFromValues(vals), ); assert.deepEqual(returnedVals, [ @@ -290,7 +302,7 @@ suite("stream lowers", () => { ]); vals = [{ tag: "float", val: 123.1 }]; - returnedVals = await instance["jco:test-components/use-stream-async"].readStreamValuesVariant( + returnedVals = await instance["jco:test-components/stream-lower-async"].readStreamValuesVariant( createReadableStreamFromValues(vals), ); assert.closeTo(returnedVals[0].val, 123.1, 0.01); @@ -298,14 +310,14 @@ suite("stream lowers", () => { test.concurrent("tuple", async () => { const instance = await getInstance(); - assert.instanceOf(instance["jco:test-components/use-stream-async"].readStreamValuesTuple, AsyncFunction); + assert.instanceOf(instance["jco:test-components/stream-lower-async"].readStreamValuesTuple, AsyncFunction); let vals = [ [1, -1, "one"], [2, -2, "two"], [3, -3, "two"], ]; - let returnedVals = await instance["jco:test-components/use-stream-async"].readStreamValuesTuple( + let returnedVals = await instance["jco:test-components/stream-lower-async"].readStreamValuesTuple( createReadableStreamFromValues(vals), ); assert.deepEqual(returnedVals, vals); @@ -313,14 +325,14 @@ suite("stream lowers", () => { test.concurrent("flags", async () => { const instance = await getInstance(); - assert.instanceOf(instance["jco:test-components/use-stream-async"].readStreamValuesFlags, AsyncFunction); + assert.instanceOf(instance["jco:test-components/stream-lower-async"].readStreamValuesFlags, AsyncFunction); let vals = [ { first: true, second: false, third: false }, { first: false, second: true, third: false }, { first: false, second: false, third: true }, ]; - let returnedVals = await instance["jco:test-components/use-stream-async"].readStreamValuesFlags( + let returnedVals = await instance["jco:test-components/stream-lower-async"].readStreamValuesFlags( createReadableStreamFromValues(vals), ); assert.deepEqual(returnedVals, vals); @@ -328,10 +340,10 @@ suite("stream lowers", () => { test.concurrent("enum", async () => { const instance = await getInstance(); - assert.instanceOf(instance["jco:test-components/use-stream-async"].readStreamValuesEnum, AsyncFunction); + assert.instanceOf(instance["jco:test-components/stream-lower-async"].readStreamValuesEnum, AsyncFunction); let vals = ["first", "second", "third"]; - let returnedVals = await instance["jco:test-components/use-stream-async"].readStreamValuesEnum( + let returnedVals = await instance["jco:test-components/stream-lower-async"].readStreamValuesEnum( createReadableStreamFromValues(vals), ); assert.deepEqual(returnedVals, ["first", "second", "third"]); @@ -339,13 +351,10 @@ suite("stream lowers", () => { test.concurrent("option", async () => { const instance = await getInstance(); - assert.instanceOf( - instance["jco:test-components/use-stream-async"].readStreamValuesOptionString, - AsyncFunction, - ); + assert.instanceOf(instance["jco:test-components/stream-lower-async"].readStreamValuesOptionString, AsyncFunction); let vals = ["present string", null]; - let returnedVals = await instance["jco:test-components/use-stream-async"].readStreamValuesOptionString( + let returnedVals = await instance["jco:test-components/stream-lower-async"].readStreamValuesOptionString( createReadableStreamFromValues(vals), ); assert.deepEqual(returnedVals, [ @@ -357,13 +366,10 @@ suite("stream lowers", () => { test.concurrent("result", async () => { const instance = await getInstance(); - assert.instanceOf( - instance["jco:test-components/use-stream-async"].readStreamValuesResultString, - AsyncFunction, - ); + assert.instanceOf(instance["jco:test-components/stream-lower-async"].readStreamValuesResultString, AsyncFunction); let vals = [{ tag: "ok", val: "present string" }, { tag: "err", val: "nope" }, "bare string (ok)"]; - let returnedVals = await instance["jco:test-components/use-stream-async"].readStreamValuesResultString( + let returnedVals = await instance["jco:test-components/stream-lower-async"].readStreamValuesResultString( createReadableStreamFromValues(vals), ); assert.deepEqual(returnedVals, [ @@ -376,10 +382,10 @@ suite("stream lowers", () => { test.concurrent("list", async () => { const instance = await getInstance(); - assert.instanceOf(instance["jco:test-components/use-stream-async"].readStreamValuesListU8, AsyncFunction); + assert.instanceOf(instance["jco:test-components/stream-lower-async"].readStreamValuesListU8, AsyncFunction); let vals = [[0x01, 0x02, 0x03, 0x04, 0x05], new Uint8Array([0x05, 0x04, 0x03, 0x02, 0x01]), []]; - let returnedVals = await instance["jco:test-components/use-stream-async"].readStreamValuesListU8( + let returnedVals = await instance["jco:test-components/stream-lower-async"].readStreamValuesListU8( createReadableStreamFromValues(vals), ); assert.deepEqual(returnedVals, [ @@ -392,13 +398,10 @@ suite("stream lowers", () => { test.concurrent("list", async () => { const instance = await getInstance(); - assert.instanceOf( - instance["jco:test-components/use-stream-async"].readStreamValuesListString, - AsyncFunction, - ); + assert.instanceOf(instance["jco:test-components/stream-lower-async"].readStreamValuesListString, AsyncFunction); let vals = [["first", "second", "third"], []]; - let returnedVals = await instance["jco:test-components/use-stream-async"].readStreamValuesListString( + let returnedVals = await instance["jco:test-components/stream-lower-async"].readStreamValuesListString( createReadableStreamFromValues(vals), ); assert.deepEqual(returnedVals, vals); @@ -406,10 +409,7 @@ suite("stream lowers", () => { test.concurrent("list>", async () => { const instance = await getInstance(); - assert.instanceOf( - instance["jco:test-components/use-stream-async"].readStreamValuesFixedListU32, - AsyncFunction, - ); + assert.instanceOf(instance["jco:test-components/stream-lower-async"].readStreamValuesFixedListU32, AsyncFunction); let vals = [ [ @@ -418,7 +418,7 @@ suite("stream lowers", () => { ], [[0, 0, 0, 0, 0], new Uint32Array([0x05, 0x04, 0x03, 0x02, 0x01])], ]; - let returnedVals = await instance["jco:test-components/use-stream-async"].readStreamValuesFixedListU32( + let returnedVals = await instance["jco:test-components/stream-lower-async"].readStreamValuesFixedListU32( createReadableStreamFromValues(vals), ); assert.deepEqual(returnedVals, [ @@ -436,10 +436,7 @@ suite("stream lowers", () => { test.concurrent("list", async () => { const instance = await getInstance(); - assert.instanceOf( - instance["jco:test-components/use-stream-async"].readStreamValuesListRecord, - AsyncFunction, - ); + assert.instanceOf(instance["jco:test-components/stream-lower-async"].readStreamValuesListRecord, AsyncFunction); let vals = [ [ @@ -453,7 +450,7 @@ suite("stream lowers", () => { { id: 3, idStr: "three-three" }, ], ]; - let returnedVals = await instance["jco:test-components/use-stream-async"].readStreamValuesListRecord( + let returnedVals = await instance["jco:test-components/stream-lower-async"].readStreamValuesListRecord( createReadableStreamFromValues(vals), ); assert.deepEqual(returnedVals, vals); @@ -462,12 +459,12 @@ suite("stream lowers", () => { test.concurrent("example-resource", async () => { const instance = await getInstance(); assert.instanceOf( - instance["jco:test-components/use-stream-async"].readStreamValuesExampleResourceOwn, + instance["jco:test-components/stream-lower-async"].readStreamValuesExampleResourceOwn, AsyncFunction, ); let vals = [new ExampleResource(0), new ExampleResource(1), new ExampleResource(2)]; - await instance["jco:test-components/use-stream-async"].readStreamValuesExampleResourceOwn( + await instance["jco:test-components/stream-lower-async"].readStreamValuesExampleResourceOwn( createReadableStreamFromValues(vals), ); // TODO(fix): we shoudl be able to ensure destructor call @@ -478,30 +475,27 @@ suite("stream lowers", () => { test.concurrent("example-resource#get-id", async () => { const instance = await getInstance(); assert.instanceOf( - instance["jco:test-components/use-stream-async"].readStreamValuesExampleResourceOwnAttr, + instance["jco:test-components/stream-lower-async"].readStreamValuesExampleResourceOwnAttr, AsyncFunction, ); let vals = [new ExampleResource(2), new ExampleResource(1), new ExampleResource(0)]; const returnedVals = await instance[ - "jco:test-components/use-stream-async" + "jco:test-components/stream-lower-async" ].readStreamValuesExampleResourceOwnAttr(createReadableStreamFromValues(vals)); assert.deepEqual(returnedVals, [2, 1, 0]); }); test.concurrent("stream", async () => { const instance = await getInstance(); - assert.instanceOf( - instance["jco:test-components/use-stream-async"].readStreamValuesStreamString, - AsyncFunction, - ); + assert.instanceOf(instance["jco:test-components/stream-lower-async"].readStreamValuesStreamString, AsyncFunction); let vals = [ createReadableStreamFromValues(["first", "stream", "values"]), createReadableStreamFromValues(["second", "stream", "here"]), createReadableStreamFromValues(["third", "values", "in stream"]), ]; - const returnedVals = await instance["jco:test-components/use-stream-async"].readStreamValuesStreamString( + const returnedVals = await instance["jco:test-components/stream-lower-async"].readStreamValuesStreamString( createReadableStreamFromValues(vals), ); assert.deepEqual(returnedVals, [ @@ -510,106 +504,10 @@ suite("stream lowers", () => { ["third", "values", "in stream"], ]); }); + + test.concurrent("stream>", async () => { + const instance = await getInstance(); + throw new Error("NOT YET IMPLEMENTED"); + }); }); }); - -// // NOTE: this suite of tests should *not* be run concurrently, as they performs sync operations -// // which cannot be mediated/scheduled in the same way as async functions (avoiding -// // which does *not* get mediated in the same way -// suite("stream lowers (sync)", () => { -// let esModule, cleanup, getInstance, instance; - -// beforeAll(async () => { -// const name = "stream-rx"; -// const setupRes = await setupAsyncTest({ -// asyncMode: "jspi", -// component: { -// name, -// path: join(LOCAL_TEST_COMPONENTS_DIR, `${name}.wasm`), -// skipInstantiation: true, -// }, -// jco: { -// transpile: { -// extraArgs: { -// minify: false, -// }, -// }, -// }, -// }); - -// esModule = setupRes.esModule; -// cleanup = setupRes.cleanup; - -// // We use a completely shared instance because sibling re-entrance -// // is mediated by code in task.enter() -// instance = await esModule.instantiate(undefined, { -// ...new WASIShim().getImportObject(), -// "jco:test-components/resources": { -// ExampleResource, -// }, -// }); -// getInstance = () => Promise.resolve(instance); - -// // NOTE: To use an explicitly new instance per-test (more stable), uncomment the lines below -// // -// // getInstance = async () => esModule.instantiate(undefined, { -// // ...new WASIShim().getImportObject(), -// // "jco:test-components/resources": { -// // ExampleResource, -// // }, -// // }); -// }); - -// afterAll(async () => { -// await cleanup(); -// }); - -// test("sync passthrough", async () => { -// const instance = await getInstance(); -// assert.notInstanceOf(instance["jco:test-components/use-stream-sync"].streamPassthrough, AsyncFunction); - -// let vals = [0, 5, 10]; -// const readerStream = new ReadableStream({ -// start(ctrl) { -// vals.forEach((v) => ctrl.enqueue(v)); -// ctrl.close(); -// }, -// }); - -// let returnedStream = instance["jco:test-components/use-stream-sync"].streamPassthrough(readerStream); - -// // NOTE: Returned streams conform to the async iterator protocol -- they *do not* confirm to -// // any other interface, though an object that is a ReadableStream may have been passed in. -// // -// let returnedVals = []; -// for await (const v of returnedStream) { -// returnedVals.push(v); -// } -// assert.deepEqual(vals, returnedVals); - -// // Test late writer -- component should block until a value is written, -// // and we should handle a final value + done from an iterator properly -// const lateStream = { -// [Symbol.asyncIterator]() { -// let returned = 0; -// return { -// async next() { -// await new Promise((resolve) => setTimeout(resolve, 300)); -// if (returned === 2) { -// return { value: 42, done: true }; -// } -// returned += 1; -// return { value: 42, done: false }; -// }, -// }; -// }, -// }; -// returnedStream = instance["jco:test-components/use-stream-sync"].streamPassthrough(lateStream); - -// returnedVals = []; -// for await (const v of returnedStream) { -// returnedVals.push(v); -// } -// assert.deepEqual([42, 42, 42], returnedVals); -// }); -// }); From bbfb5ed512ebd58d359f298fb6fb02dd8c33b2ad Mon Sep 17 00:00:00 2001 From: Victor Adossi Date: Mon, 27 Apr 2026 17:33:18 +0900 Subject: [PATCH 2/5] feat(bindgen): fill out future lower impl --- .../src/function_bindgen.rs | 197 ++++++++++++++++-- .../src/intrinsics/lift.rs | 20 +- .../src/intrinsics/lower.rs | 4 +- .../src/intrinsics/mod.rs | 29 ++- .../src/intrinsics/p3/async_future.rs | 137 ++++++++++-- .../src/intrinsics/p3/async_stream.rs | 15 +- .../src/intrinsics/p3/async_task.rs | 6 +- .../src/transpile_bindgen.rs | 8 +- 8 files changed, 357 insertions(+), 59 deletions(-) diff --git a/crates/js-component-bindgen/src/function_bindgen.rs b/crates/js-component-bindgen/src/function_bindgen.rs index 09e5e8ab7..6d3a6689e 100644 --- a/crates/js-component-bindgen/src/function_bindgen.rs +++ b/crates/js-component-bindgen/src/function_bindgen.rs @@ -4,8 +4,9 @@ use std::mem; use heck::{ToLowerCamelCase, ToUpperCamelCase}; use wasmtime_environ::component::{ - CanonicalOptions, InterfaceType, ResourceIndex, TypeComponentLocalErrorContextTableIndex, - TypeFutureTableIndex, TypeResourceTableIndex, TypeStreamTableIndex, + CanonicalOptions, CanonicalOptionsDataModel, InterfaceType, LinearMemoryOptions, ResourceIndex, + TypeComponentLocalErrorContextTableIndex, TypeFutureTableIndex, TypeResourceTableIndex, + TypeStreamTableIndex, }; use wit_bindgen_core::abi::{Bindgen, Bitcast, Instruction}; use wit_component::StringEncoding; @@ -2685,19 +2686,172 @@ impl Bindgen for FunctionBindgen<'_> { results.push(item.clone()); } - Instruction::FutureLower { .. } => { - // TODO: convert this return of the lifted Future: - // - // ``` - // return BigInt(writeEndWaitableIdx) << 32n | BigInt(readEndWaitableIdx); - // ``` - // - // Into a component-local Future instance - // + Instruction::FutureLower { ty, .. } => { + let debug_log_fn = self.intrinsic(Intrinsic::DebugLog); + let get_or_create_async_state_fn = self.intrinsic(Intrinsic::Component( + ComponentIntrinsic::GetOrCreateAsyncState, + )); + let gen_future_host_inject_fn = self.intrinsic(Intrinsic::AsyncFuture( + AsyncFutureIntrinsic::GenFutureHostInjectFn, + )); + let is_future_lowerable_object_fn = self.intrinsic(Intrinsic::AsyncFuture( + AsyncFutureIntrinsic::IsFutureLowerableObject, + )); + + let component_idx = self.canon_opts.instance.as_u32(); + let future_arg = operands .first() .expect("unexpectedly missing ErrorContextLower arg"); - results.push(future_arg.clone()); + + // Build the lowering function for the type produced by the future + let type_id = &crate::dealias(self.resolve, *ty); + let ResourceTable { + imported: true, + data: + ResourceData::Guest { + extra: + Some(ResourceExtraData::Future { + table_idx: future_table_idx_ty, + elem_ty, + }), + .. + }, + } = self + .resource_map + .get(type_id) + .expect("missing resource mapping for future lower") + else { + unreachable!("invalid resource table observed during future lower"); + }; + let future_table_idx = future_table_idx_ty.as_u32(); + + // Generate payload metadata ('elemMeta') + let ( + payload_type_name_js, + lift_fn_js, + lower_fn_js, + payload_is_none, + payload_is_numeric, + payload_is_borrow, + payload_is_async_value, + payload_size32_js, + payload_align32_js, + payload_flat_count_js, + ) = match elem_ty { + Some(PayloadTypeMetadata { + ty: _, + iface_ty, + lift_js_expr, + lower_js_expr, + size32, + align32, + flat_count, + }) => ( + format!("'{iface_ty:?}'"), + lift_js_expr.as_str(), + lower_js_expr.as_str(), + "false", + format!( + "{}", + matches!( + iface_ty, + InterfaceType::U8 + | InterfaceType::U16 + | InterfaceType::U32 + | InterfaceType::U64 + | InterfaceType::S8 + | InterfaceType::S16 + | InterfaceType::S32 + | InterfaceType::S64 + | InterfaceType::Float32 + | InterfaceType::Float64 + ) + ), + format!("{}", matches!(iface_ty, InterfaceType::Borrow(_))), + format!( + "{}", + matches!( + iface_ty, + InterfaceType::Stream(_) | InterfaceType::Future(_) + ) + ), + size32.to_string(), + align32.to_string(), + flat_count.unwrap_or(0).to_string(), + ), + None => ( + "null".into(), + "() => {{ throw new Error('no lift fn'); }}", + "() => {{ throw new Error('no lower fn'); }}", + "true", + "false".into(), + "false".into(), + "false".into(), + "null".into(), + "null".into(), + "0".into(), + ), + }; + + // Retrieve the realloc fn if present, in case lowering fns need to allocate + // + // The realloc fn is saved on the element metadata which is passed through to + // stream end and underlying buffer + let get_realloc_fn_js = match self.canon_opts.data_model { + CanonicalOptionsDataModel::LinearMemory(LinearMemoryOptions { + realloc: Some(realloc_idx), + .. + }) => format!("() => realloc{}", realloc_idx.as_u32()), + _ => "undefined".into(), + }; + + let tmp = self.tmp(); + let lowered_future_waitable_idx = format!("futureWaitableIdx{tmp}"); + uwriteln!( + self.src, + r#" + if (!{is_future_lowerable_object_fn}({future_arg})) {{ + {debug_log_fn}('[Instruction::FutureLower] object is not a Promise/Thenable', {{ {future_arg} }}); + throw new Error('unrecognized future object (not Promise/Thenable)'); + }} + + const cstate{tmp} = {get_or_create_async_state_fn}({component_idx}); + if (!cstate{tmp}) {{ throw new Error(`missing component state for component [{component_idx}]`); }} + + // TODO(feat): facilitate non utf8 string encoding for lowered futures + const stringEncoding = 'utf8'; + + const {{ writeEnd: hostWriteEnd{tmp}, readEnd: readEnd{tmp} }} = cstate{tmp}.createFuture({{ + tableIdx: {future_table_idx}, + elemMeta: {{ + liftFn: {lift_fn_js}, + lowerFn: {lower_fn_js}, + payloadTypeName: {payload_type_name_js}, + isNone: {payload_is_none}, + isNumeric: {payload_is_numeric}, + isBorrowed: {payload_is_borrow}, + isAsyncValue: {payload_is_async_value}, + flatCount: {payload_flat_count_js}, + align32: {payload_align32_js}, + size32: {payload_size32_js}, + stringEncoding, + getReallocFn: {get_realloc_fn_js}, + }}, + }}); + + const hostInjectFn = {gen_future_host_inject_fn}({{ + promise: {future_arg}, + stringEncoding, + hostWriteEnd: hostWriteEnd{tmp}, + }}); + readEnd{tmp}.setHostInjectFn(hostInjectFn); + + const {lowered_future_waitable_idx} = readEnd{tmp}.waitableIdx(); + "# + ); + + results.push(lowered_future_waitable_idx); } Instruction::FutureLift { payload, ty } => { @@ -2802,9 +2956,12 @@ impl Bindgen for FunctionBindgen<'_> { ComponentIntrinsic::GetOrCreateAsyncState, )); let gen_host_inject_fn = self.intrinsic(Intrinsic::AsyncStream( - AsyncStreamIntrinsic::GenHostInjectFn, + AsyncStreamIntrinsic::GenStreamHostInjectFn, )); + // TODO(???): A component could end up receiving a stream that it outputted, + // and the below would fail (imported: false)? + // Build the lowering function for the type produced by the stream let type_id = &crate::dealias(self.resolve, *ty); let ResourceTable { @@ -2896,6 +3053,18 @@ impl Bindgen for FunctionBindgen<'_> { ), }; + // Retrieve the realloc fn if present, in case lowering fns need to allocate + // + // The realloc fn is saved on the element metadata which is passed through to + // stream end and underlying buffer + let get_realloc_fn_js = match self.canon_opts.data_model { + CanonicalOptionsDataModel::LinearMemory(LinearMemoryOptions { + realloc: Some(realloc_idx), + .. + }) => format!("() => realloc{}", realloc_idx.as_u32()), + _ => "undefined".into(), + }; + let tmp = self.tmp(); let lowered_stream_waitable_idx = format!("streamWaitableIdx{tmp}"); uwriteln!( @@ -2926,6 +3095,7 @@ impl Bindgen for FunctionBindgen<'_> { size32: {payload_size32_js}, // TODO(feat): facilitate non utf8 string encoding for lowered streams stringEncoding: 'utf8', + geReallocFn: {get_realloc_fn_js}, }}, }}); @@ -2952,6 +3122,7 @@ impl Bindgen for FunctionBindgen<'_> { const {lowered_stream_waitable_idx} = readEnd{tmp}.waitableIdx(); "# ); + results.push(lowered_stream_waitable_idx); } diff --git a/crates/js-component-bindgen/src/intrinsics/lift.rs b/crates/js-component-bindgen/src/intrinsics/lift.rs index 932cedfb3..0dfd8db07 100644 --- a/crates/js-component-bindgen/src/intrinsics/lift.rs +++ b/crates/js-component-bindgen/src/intrinsics/lift.rs @@ -393,7 +393,7 @@ impl LiftIntrinsic { let val; if (ctx.useDirectParams) {{ - if (params.length === 0) {{ throw new Error('expected at least a single i32 argument'); }} + if (ctx.params.length === 0) {{ throw new Error('expected at least a single i32 argument'); }} val = ctx.params[0]; ctx.params = ctx.params.slice(1); return [val, ctx]; @@ -734,11 +734,6 @@ impl LiftIntrinsic { return function {lift_flat_record_fn}Inner(ctx) {{ {debug_log_fn}('[{lift_flat_record_fn}()] args', {{ ctx }}); - if (ctx.useDirectParams) {{ - ctx.storagePtr = ctx.params[0]; - ctx.params = ctx.params.slice(1); - }} - const res = {{}}; for (const [key, liftFn, _size32, _align32] of keysAndLiftFns) {{ let [val, newCtx] = liftFn(ctx); @@ -1233,28 +1228,27 @@ impl LiftIntrinsic { output.push_str(&format!(r#" function {lift_flat_error_fn}(errCtxTableIdx, ctx) {{ {debug_log_fn}('[{lift_flat_error_fn}()] ctx', ctx); - const {{ useDirectParams, params, componentIdx }} = ctx; let val; let table; - if (useDirectParams) {{ - if (params.length === 0) {{ throw new Error('expected at least one single i32 argument'); }} + if (ctx.useDirectParams) {{ + if (ctx.params.length === 0) {{ throw new Error('expected at least one single i32 argument'); }} val = ctx.params[0]; ctx.params = ctx.params.slice(1); - table = {get_err_ctx_local_table_fn}(componentIdx, errCtxTableIdx); + table = {get_err_ctx_local_table_fn}(ctx.componentIdx, errCtxTableIdx); }} else {{ throw new Error('indirect flat lift for error-contexts not yet implemented!'); }} let handle = table.get(val); if (handle === undefined) {{ - throw new Error(`missing error ctx (handle [${{val}}], component [${{componentIdx}}], error context table [${{errCtxTableIdx}}])`); + throw new Error(`missing error ctx (handle [${{val}}], component [${{ctx.componentIdx}}], error context table [${{errCtxTableIdx}}])`); }} - const cstate = {get_or_create_async_state_fn}(componentIdx); + const cstate = {get_or_create_async_state_fn}(ctx.componentIdx); const errCtx = cstate.handles.get(handle); if (!errCtx || errCtx.globalRep === undefined || errCtx.refCount === undefined) {{ - throw new Error(`malformed error context (handle [${{handle}}], component [${{componentIdx}}])`); + throw new Error(`malformed error context (handle [${{handle}}], component [${{ctx.componentIdx}}])`); }} errCtx.refCount -= 1; diff --git a/crates/js-component-bindgen/src/intrinsics/lower.rs b/crates/js-component-bindgen/src/intrinsics/lower.rs index 78e7aefda..b5f3d82a7 100644 --- a/crates/js-component-bindgen/src/intrinsics/lower.rs +++ b/crates/js-component-bindgen/src/intrinsics/lower.rs @@ -498,7 +498,7 @@ impl LowerIntrinsic { function {lower_flat_f64_fn}(ctx) {{ {debug_log_fn}('[{lower_flat_f64_fn}()] args', {{ ctx }}); - if (vals.length !== 1) {{ throw new Error('unexpected number of vals'); }} + if (ctx.vals.length !== 1) {{ throw new Error('unexpected number of vals'); }} const rem = ctx.storagePtr % 8; if (rem !== 0) {{ ctx.storagePtr += (8 - rem); }} @@ -994,7 +994,7 @@ impl LowerIntrinsic { Intrinsic::AsyncStream(AsyncStreamIntrinsic::GenReadFnFromLowerableStream) .name(); let gen_host_inject_fn = - Intrinsic::AsyncStream(AsyncStreamIntrinsic::GenHostInjectFn).name(); + Intrinsic::AsyncStream(AsyncStreamIntrinsic::GenStreamHostInjectFn).name(); let lower_u32_fn = Self::LowerFlatU32.name(); output.push_str(&format!( diff --git a/crates/js-component-bindgen/src/intrinsics/mod.rs b/crates/js-component-bindgen/src/intrinsics/mod.rs index 519bac969..4c094bd5c 100644 --- a/crates/js-component-bindgen/src/intrinsics/mod.rs +++ b/crates/js-component-bindgen/src/intrinsics/mod.rs @@ -644,7 +644,8 @@ impl Intrinsic { storagePtr: startPtr, componentIdx: this.#componentIdx, stringEncoding: this.#elemMeta.stringEncoding, - realloc: this.#elemMeta.reallocFn, + realloc: this.#elemMeta.getReallocFn?.(), + getReallocFn: this.#elemMeta.getReallocFn, }} for (const v of values) {{ lowerCtx.vals = [v]; @@ -1401,7 +1402,22 @@ pub fn render_intrinsics(args: RenderIntrinsicsArgs) -> Source { &Intrinsic::SymbolResourceRep, &Intrinsic::Component(ComponentIntrinsic::GetOrCreateAsyncState), &Intrinsic::AsyncStream(AsyncStreamIntrinsic::GenReadFnFromLowerableStream), - &Intrinsic::AsyncStream(AsyncStreamIntrinsic::GenHostInjectFn), + &Intrinsic::AsyncStream(AsyncStreamIntrinsic::GenStreamHostInjectFn), + &Intrinsic::Lower(LowerIntrinsic::LowerFlatU32), + ]) + } + + if args + .intrinsics + .contains(&Intrinsic::Lower(LowerIntrinsic::LowerFlatFuture)) + { + args.intrinsics.extend([ + &Intrinsic::AsyncFuture(AsyncFutureIntrinsic::GlobalFutureMap), + &Intrinsic::AsyncFuture(AsyncFutureIntrinsic::InternalFutureClass), + &Intrinsic::AsyncFuture(AsyncFutureIntrinsic::IsFutureLowerableObject), + &Intrinsic::SymbolResourceRep, + &Intrinsic::Component(ComponentIntrinsic::GetOrCreateAsyncState), + &Intrinsic::AsyncFuture(AsyncFutureIntrinsic::GenFutureHostInjectFn), &Intrinsic::Lower(LowerIntrinsic::LowerFlatU32), ]) } @@ -1535,6 +1551,15 @@ pub fn render_intrinsics(args: RenderIntrinsicsArgs) -> Source { ]); } + if args.intrinsics.contains(&Intrinsic::AsyncFuture( + AsyncFutureIntrinsic::FutureNewFromLift, + )) { + args.intrinsics.extend([ + &Intrinsic::AsyncFuture(AsyncFutureIntrinsic::GlobalFutureMap), + &Intrinsic::AsyncFuture(AsyncFutureIntrinsic::HostFutureClass), + ]); + } + if args.intrinsics.contains(&Intrinsic::AsyncFuture( AsyncFutureIntrinsic::FutureWritableEndClass, )) || args.intrinsics.contains(&Intrinsic::AsyncFuture( diff --git a/crates/js-component-bindgen/src/intrinsics/p3/async_future.rs b/crates/js-component-bindgen/src/intrinsics/p3/async_future.rs index 8c348ff69..49067a2eb 100644 --- a/crates/js-component-bindgen/src/intrinsics/p3/async_future.rs +++ b/crates/js-component-bindgen/src/intrinsics/p3/async_future.rs @@ -200,6 +200,18 @@ pub enum AsyncFutureIntrinsic { /// /// See [`Trampoline::FutureTransfer`] FutureTransfer, + + /// Function that generates a host injection function for external futures + /// + /// This is usually used when lowering external `Promise`s into components, creating + /// readable ends as necessary. + /// + /// The generated host injection function is generally called right when a component + /// attempts to read (in doing so, "injecting" a write before the component read). + GenFutureHostInjectFn, + + /// Function to check whether a JS object can be used as a stream + IsFutureLowerableObject, } impl AsyncFutureIntrinsic { @@ -226,6 +238,8 @@ impl AsyncFutureIntrinsic { Self::GlobalFutureMap.name(), Self::GlobalFutureTableMap.name(), Self::InternalFutureClass.name(), + Self::GenFutureHostInjectFn.name(), + Self::IsFutureLowerableObject.name(), ] } @@ -248,6 +262,8 @@ impl AsyncFutureIntrinsic { Self::GlobalFutureTableMap => "FUTURE_TABLES", Self::HostFutureClass => "HostFuture", Self::InternalFutureClass => "InternalFuture", + Self::GenFutureHostInjectFn => "_genFutureHostInjectFn", + Self::IsFutureLowerableObject => "_isFutureLowerableObject", } } @@ -289,7 +305,6 @@ impl AsyncFutureIntrinsic { let host_future_class_name = self.name(); let get_or_create_async_state_fn = Intrinsic::Component(ComponentIntrinsic::GetOrCreateAsyncState).name(); - let promise_with_resolvers_fn = Intrinsic::PromiseWithResolversPonyfill.name(); output.push_str(&format!( r#" @@ -340,11 +355,7 @@ impl AsyncFutureIntrinsic { throw new Error(`missing future [${{this.#futureEndWaitableIdx}}] (table [${{this.#futureTableIdx}}], component [${{this.#componentIdx}}]`); }} - this.#userFuture = {promise_with_resolvers_fn}(); - - this.#userFuture.reject(new Error("TODO")); - - return this.#userFuture.promise; + return futureEnd.promise(); }} }} "# @@ -519,7 +530,7 @@ impl AsyncFutureIntrinsic { return; }} - if (componentIdx === meta.componentIdx && !this.#elemMeta.isNoneOrNumberType) {{ + if (componentIdx === meta.componentIdx && componentIdx !== -1 && !this.#elemMeta.isNoneOrNumberType) {{ throw new Error('same-component future reads not allowed for non-numeric types'); }} @@ -558,7 +569,7 @@ impl AsyncFutureIntrinsic { return; }} - if (componentIdx === meta.componentIdx && !this.#elemMeta.isNoneOrNumberType) {{ + if (componentIdx === meta.componentIdx && componentIdx !== -1 && !this.#elemMeta.isNoneOrNumberType) {{ throw new Error('same-component future writes not allowed for non-numeric types'); }} @@ -594,6 +605,10 @@ impl AsyncFutureIntrinsic { this.#elemMeta.stringEncoding = stringEncoding; }} + if (args.getReallocFn && this.#elemMeta.getReallocFn === undefined) {{ + this.#elemMeta.getReallocFn = args.getReallocFn; + }} + const elemMeta = this.#elemMeta; if (this.#elemMeta.isBorrowed) {{ @@ -643,12 +658,26 @@ impl AsyncFutureIntrinsic { this.setPendingEvent(() => futureEvent(res)); }}; + + // TODO: before performing this read, if we're dealing with a host-controlled + // future, then we should inject a write, but we can't wait for it to complete + // as we must do the rendesvous read below for the write to complete. + let injectedWritePromise; + if (this.#hostInjectFn) {{ + injectedWritePromise = this.#hostInjectFn({{ count: 1 }}); + }} + await this._read({{ buffer, onCopyDoneFn, componentIdx, }}); + if (injectedWritePromise) {{ + const cleanupFn = await injectedWritePromise; + cleanupFn(); + }} + return {{ buffer }}; }} "# @@ -663,6 +692,7 @@ impl AsyncFutureIntrinsic { const {{ componentIdx, stringEncoding, + getReallocFn, isAsync, memory, realloc, @@ -674,6 +704,10 @@ impl AsyncFutureIntrinsic { this.#elemMeta.stringEncoding = stringEncoding; }} + if (args.getReallocFn && this.#elemMeta.getReallocFn === undefined) {{ + this.#elemMeta.getReallocFn = getReallocFn; + }} + const elemMeta = this.#elemMeta; if (this.#elemMeta.isBorrowed) {{ @@ -792,17 +826,19 @@ impl AsyncFutureIntrinsic { format!( r#" async hostWrite(args) {{ - const {{ stringEncoding, value }} = args; + const {{ stringEncoding, value, getReallocFn }} = args; const {{ buffer }} = await this.guestWrite({{ stringEncoding, + getReallocFn, + // TODO: support sync host writes isAsync: true, data: [value], componentIdx: -1, + componentIdx: -1, }}); if (!this.hasPendingEvent()) {{ - if (!isAsync) {{ throw new Error("all host writes are async"); }} this.setCopyState({future_end_class}.CopyState.ASYNC_COPYING); // Wait for the write to complete @@ -874,6 +910,7 @@ impl AsyncFutureIntrinsic { #hostInjectFn; #elemMeta; #handle; + #promise; target; @@ -889,8 +926,6 @@ impl AsyncFutureIntrinsic { this.#hostInjectFn = args.hostInjectFn; this.#isHostOwned = args.hostOwned; - - this.#hostInjectFn = args.hostInjectFn; }} {type_getters} @@ -919,13 +954,22 @@ impl AsyncFutureIntrinsic { }} promise() {{ + if (this.#promise) {{ return this.#promise; }} // NOTE: we return a "thenable" here to ensure that simply lifting the future does // not trigger a host read. - return {{ + + let readPromise = null; + this.#promise = {{ then: (resolve, reject) => {{ - this.hostRead({{ stringEncoding: 'utf8' }}).then(resolve, reject); + if (readPromise) {{ + readPromise.then(resolve, reject); + return; + }} + readPromise = this.hostRead({{ stringEncoding: 'utf8' }}); + readPromise.then(resolve, reject); }} }}; + return this.#promise; }} cancel() {{ @@ -1174,7 +1218,8 @@ impl AsyncFutureIntrinsic { componentIdx, stringEncoding, memory: getMemoryFn(), - realloc: getReallocFn(), + realloc: getReallocFn?.(), + getReallocFn, ptr, }}); @@ -1268,7 +1313,6 @@ impl AsyncFutureIntrinsic { "#)); } - // TODO: fill in future class impl (check for matching element types) Self::FutureDropReadable | Self::FutureDropWritable => { let debug_log_fn = Intrinsic::DebugLog.name(); let future_drop_fn = self.name(); @@ -1316,6 +1360,67 @@ impl AsyncFutureIntrinsic { "# )); } + + Self::GenFutureHostInjectFn => { + let debug_log_fn = Intrinsic::DebugLog.name(); + let gen_host_inject_fn = self.name(); + + uwriteln!( + output, + r#" + function {gen_host_inject_fn}(genArgs) {{ + const {{ promise, hostWriteEnd, stringEncoding, getReallocFn }} = genArgs; + + let done; + + return async function generateFutureHostInject(args) {{ + let {{ count }} = args; + if (count !== 1) {{ throw new Error('invalid count'); }} + + // Futures should only be completed once + if (done) {{ + return () => {{ throw new Error('cannot inject write: future already completed'); }} + }} + + // The host *must* write something to this channel before closing it + if (hostWriteEnd.isDoneState()) {{ + return () => {{ throw new Error('cannot inject write: host must write to future before closing'); }} + }} + + try {{ + const value = await promise; + await hostWriteEnd.hostWrite({{ stringEncoding, value, getReallocFn }}); + }} catch (err) {{ + {debug_log_fn}("failed to inject host write", err); + throw new Error("cannot inject write: promise failed"); + }} + + hostWriteEnd.getPendingEvent(); + hostWriteEnd.drop(); + + return () => {{ + // After the write is finished, we consume the event that was generated + // by the just-in-time write (and the subsequent read), if one was generated + if (hostWriteEnd.hasPendingEvent()) {{ hostWriteEnd.getPendingEvent(); }} + }}; + }}; + }} + "# + ); + } + + Self::IsFutureLowerableObject => { + let is_future_lowerable_object = self.name(); + output.push_str(&format!( + r#" + function {is_future_lowerable_object}(obj) {{ + if (typeof obj !== 'object') {{ return false; }} + return obj instanceof Promise + || 'then' in obj && typeof obj.then === 'function'; + }} + "# + )); + } } } } diff --git a/crates/js-component-bindgen/src/intrinsics/p3/async_stream.rs b/crates/js-component-bindgen/src/intrinsics/p3/async_stream.rs index 5e9ad21d6..d00150165 100644 --- a/crates/js-component-bindgen/src/intrinsics/p3/async_stream.rs +++ b/crates/js-component-bindgen/src/intrinsics/p3/async_stream.rs @@ -222,7 +222,7 @@ pub enum AsyncStreamIntrinsic { /// This is usually used when lowering external streams' readable ends into a component, /// and the generated function is generally called right when a component attempts to read /// (in doing so, "injecting" a write before the component read). - GenHostInjectFn, + GenStreamHostInjectFn, /// Function that generates a function (the "read function") lowerable stream object GenReadFnFromLowerableStream, @@ -260,7 +260,7 @@ impl AsyncStreamIntrinsic { Self::StreamCancelRead => "streamCancelRead", Self::StreamCancelWrite => "streamCancelWrite", Self::IsStreamLowerableObject => "_isStreamLowerableObject", - Self::GenHostInjectFn => "_genHostInjectFn", + Self::GenStreamHostInjectFn => "_genStreamHostInjectFn", Self::GenReadFnFromLowerableStream => "_genReadFnFromLowerableStream", } } @@ -698,8 +698,8 @@ impl AsyncStreamIntrinsic { throw new Error(`inconsistent string encoding (previously [${{this.#elemMeta.stringEncoding}}], now [${{stringEncoding}}])`); }} - if (this.#elemMeta.reallocFn === undefined && reallocFn) {{ - this.#elemMeta.reallocFn = reallocFn; + if (args.getReallocFn && this.#elemMeta.getReallocFn === undefined) {{ + this.#elemMeta.getReallocFn = args.getReallocFn; }} if (this.isDropped()) {{ @@ -1608,7 +1608,8 @@ impl AsyncStreamIntrinsic { eventCode: {event_code}, componentIdx, stringEncoding, - reallocFn: getReallocFn(), + realloc: getReallocFn?.(), + getReallocFn, }}); return result; @@ -1853,7 +1854,7 @@ impl AsyncStreamIntrinsic { )); } - Self::GenHostInjectFn => { + Self::GenStreamHostInjectFn => { let gen_host_inject_fn = self.name(); output.push_str(&format!( @@ -1869,7 +1870,7 @@ impl AsyncStreamIntrinsic { let done = false; - return async (args) => {{ + return async function generatedStreamHostInject(args) {{ let {{ count }} = args; if (count < 0) {{ throw new Error('invalid count'); }} if (count === 0) {{ return doNothingFn; }} diff --git a/crates/js-component-bindgen/src/intrinsics/p3/async_task.rs b/crates/js-component-bindgen/src/intrinsics/p3/async_task.rs index aa201fa88..6fc6a84af 100644 --- a/crates/js-component-bindgen/src/intrinsics/p3/async_task.rs +++ b/crates/js-component-bindgen/src/intrinsics/p3/async_task.rs @@ -2114,7 +2114,8 @@ impl AsyncTaskIntrinsic { callMetadata: {{ memoryIdx, memory, - realloc: getReallocFn(), + realloc: getReallocFn?.(), + getReallocFn, resultPtr: params[0], lowers: resultLowerFns, stringEncoding, @@ -2340,7 +2341,8 @@ impl AsyncTaskIntrinsic { callMetadata: {{ memoryIdx, memory, - realloc: getReallocFn(), + realloc: getReallocFn?.(), + getReallocFn, resultPtr: params[0], lowers: resultLowerFns, stringEncoding, diff --git a/crates/js-component-bindgen/src/transpile_bindgen.rs b/crates/js-component-bindgen/src/transpile_bindgen.rs index 145f9170f..e2430e958 100644 --- a/crates/js-component-bindgen/src/transpile_bindgen.rs +++ b/crates/js-component-bindgen/src/transpile_bindgen.rs @@ -1439,7 +1439,7 @@ impl<'a> Instantiator<'a, '_> { let v = v.as_u32().to_string(); (v.to_string(), format!("() => realloc{v}")) } - None => ("null".into(), "() => null".into()), + None => ("undefined".into(), "undefined".into()), }; let component_instance_id = instance.as_u32(); @@ -1519,7 +1519,7 @@ impl<'a> Instantiator<'a, '_> { let v = v.as_u32().to_string(); (v.to_string(), format!("() => realloc{v}")) } - None => ("null".into(), "() => null".into()), + None => ("undefined".into(), "undefined".into()), }; let string_encoding = string_encoding_js_literal(string_encoding); @@ -1778,7 +1778,7 @@ impl<'a> Instantiator<'a, '_> { idx.as_u32().to_string(), format!("() => realloc{}", idx.as_u32()), ), - None => ("null".into(), "() => null".to_string()), + None => ("undefined".into(), "undefined".to_string()), }; let string_encoding = string_encoding_js_literal(string_encoding); @@ -2183,7 +2183,7 @@ impl<'a> Instantiator<'a, '_> { }; let (memory_idx_js, memory_expr_js) = memory_exprs.unwrap_or_else(|| ("null".into(), "() => null".into())); - let realloc_expr_js = realloc_expr_js.unwrap_or_else(|| "() => null".into()); + let realloc_expr_js = realloc_expr_js.unwrap_or_else(|| "undefined".into()); let string_encoding_js = string_encoding_js_literal(&canon_opts.string_encoding); // Build the lower import call that will wrap the actual trampoline From 68ab1f533b4f1a20d6e4429cf9a4c4062b5aa716 Mon Sep 17 00:00:00 2001 From: Victor Adossi Date: Mon, 27 Apr 2026 17:51:09 +0900 Subject: [PATCH 3/5] test(jco): fill in nested future stream lower test --- packages/jco/test/p3/stream-lowers.js | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/packages/jco/test/p3/stream-lowers.js b/packages/jco/test/p3/stream-lowers.js index 0ac5c64de..bddb51a52 100644 --- a/packages/jco/test/p3/stream-lowers.js +++ b/packages/jco/test/p3/stream-lowers.js @@ -505,9 +505,24 @@ suite("stream lowers", () => { ]); }); - test.concurrent("stream>", async () => { + // TODO: needs flat lower for futures + test.only("stream>", async () => { const instance = await getInstance(); - throw new Error("NOT YET IMPLEMENTED"); + assert.instanceOf(instance["jco:test-components/stream-lower-async"].readStreamValuesFutureString, AsyncFunction); + + let vals = [ + "hello", + "from", + "nested", + "future", + ]; + const input = createReadableStreamFromValues(vals.map(v => Promise.resolve(v))); + const stream = await instance["jco:test-components/stream-lower-async"].readStreamValuesFutureString(input); + let returnedVals = []; + for await (const v of stream) { + returnedVals.push(v); + } + assert.deepEqual(returnedVals, vals); }); }); }); From cb2f11e2098df2e4b0bb6c64c1ecde8fecedbe9d Mon Sep 17 00:00:00 2001 From: Victor Adossi Date: Mon, 27 Apr 2026 19:31:21 +0900 Subject: [PATCH 4/5] fix(bindgen): fix task.return param spill check --- .../src/intrinsics/lift.rs | 54 ++++++++++++------- .../src/intrinsics/p3/async_task.rs | 8 ++- .../src/transpile_bindgen.rs | 10 ++-- 3 files changed, 45 insertions(+), 27 deletions(-) diff --git a/crates/js-component-bindgen/src/intrinsics/lift.rs b/crates/js-component-bindgen/src/intrinsics/lift.rs index 0dfd8db07..ffd46edae 100644 --- a/crates/js-component-bindgen/src/intrinsics/lift.rs +++ b/crates/js-component-bindgen/src/intrinsics/lift.rs @@ -842,25 +842,41 @@ impl LiftIntrinsic { let liftResults; if (knownLen !== undefined) {{ // list with known length - if (ctx.useDirectParams) {{ - // list with known length w/ direct params - const dataPtr = ctx.params[0]; - ctx.params = ctx.params.slice(1); - - // TODO(???): is it possible for all values to come in from params? + if (ctx.memory === null) {{ + // If this lift should be using direct params, + // and the memory is missing, we are in the case where + // a fixed length list (or other value) is being passed only + // via parameters to the function. + // + // Normally, we would expect to use the direct parameters as a + // memory location + size, but in this case, *all* values are being passed directly, + // via params. + // + {debug_log_fn}('memory unexpectedly missing while lifting unknown length list', {{ ctx }}); + liftResults = [ctx.params.slice(0, knownLen), ctx]; + ctx.params = ctx.params.slice(knownLen); + }} else {{ + // in-memory list with unknown length w/ direct params + const dataPtr = ctx.params[0]; + ctx.params = ctx.params.slice(1); + + ctx.useDirectParams = false; + const originalPtr = ctx.storagePtr; + ctx.storageLen = knownLen * elemSize32; + + liftResults = readValuesAndReset(ctx, originalPtr, dataPtr, knownLen); + + ctx.useDirectParams = true; + ctx.storagePtr = undefined; + ctx.storageLen = undefined; + }} + }} else {{ // indirect params + if (ctx.memory === null) {{ + {debug_log_fn}('memory unexpectedly missing while lifting known length list', {{ knownLen, ctx }}); + throw new Error(`memory missing while lifting known length (${{knownLen}}) list`); + }} - ctx.useDirectParams = false; - const originalPtr = ctx.storagePtr; - ctx.storageLen = knownLen * elemSize32; - - liftResults = readValuesAndReset(ctx, originalPtr, dataPtr, knownLen); - - ctx.useDirectParams = true; - ctx.storagePtr = null; - ctx.storageLen = null; - - }} else {{ ctx.storageLen = knownLen * elemSize32; liftResults = readValuesAndReset(ctx, null, ctx.storagePtr, knownLen); }} @@ -880,8 +896,8 @@ impl LiftIntrinsic { liftResults = readValuesAndReset(ctx, originalPtr, dataPtr, len); ctx.useDirectParams = true; - ctx.storagePtr = null; - ctx.storageLen = null; + ctx.storagePtr = undefined; + ctx.storageLen = undefined; }} else {{ // unknown length list ptr w/ in-memory params diff --git a/crates/js-component-bindgen/src/intrinsics/p3/async_task.rs b/crates/js-component-bindgen/src/intrinsics/p3/async_task.rs index 6fc6a84af..2914fb188 100644 --- a/crates/js-component-bindgen/src/intrinsics/p3/async_task.rs +++ b/crates/js-component-bindgen/src/intrinsics/p3/async_task.rs @@ -454,7 +454,6 @@ impl AsyncTaskIntrinsic { function {task_return_fn}(ctx) {{ const {{ componentIdx, - useDirectParams, getMemoryFn, memoryIdx, callbackFnIdx, @@ -464,6 +463,7 @@ impl AsyncTaskIntrinsic { }} = ctx; const params = [...arguments].slice(1); const memory = getMemoryFn(); + let useDirectParams = ctx.useDirectParams; const {{ taskID }} = {get_global_current_task_meta_fn}(componentIdx); @@ -511,6 +511,10 @@ impl AsyncTaskIntrinsic { let liftCtx = {{ memory, useDirectParams, params, componentIdx, stringEncoding }}; if (!useDirectParams) {{ + if (!ctx.memory) {{ + {debug_log_fn}('missing memory despite indirect param usage', {{ useDirectParams, liftCtx, ctx }}); + throw new Error('missing memory despite indirect param usage'); + }} liftCtx.storagePtr = params[0]; liftCtx.storageLen = params[1]; }} @@ -519,7 +523,7 @@ impl AsyncTaskIntrinsic { {debug_log_fn}('[{task_return_fn}()] lifting results out of memory', {{ liftCtx }}); for (const liftFn of liftFns) {{ if (liftCtx.storageLen !== undefined && liftCtx.storageLen <= 0) {{ - {debug_log_fn}("[{task_return_fn}()] ran out of range while writing"); + {debug_log_fn}(`[{task_return_fn}()] ran out of range while writing storageLen = [${{liftCtx.storageLen}}]`); throw new Error('ran out of storage while writing'); }} const [ val, newLiftCtx ] = liftFn(liftCtx); diff --git a/crates/js-component-bindgen/src/transpile_bindgen.rs b/crates/js-component-bindgen/src/transpile_bindgen.rs index e2430e958..b95bf7b02 100644 --- a/crates/js-component-bindgen/src/transpile_bindgen.rs +++ b/crates/js-component-bindgen/src/transpile_bindgen.rs @@ -55,11 +55,9 @@ use crate::{ requires_async_porcelain, source, uwrite, uwriteln, }; -/// Number of flat parameters allowed before spilling over to memory -/// for an async function -/// -/// See [`wit-bindgen-core`] and the Component Model spec -const MAX_ASYNC_FLAT_PARAMS: usize = 4; +/// Size of flat parameters that can be sent, for example via the `task.return` +/// intrinsic, when returning from an async func +const MAX_FLAT_PARAMS: usize = 16; #[derive(Debug, Default, Clone)] pub struct TranspileOpts { @@ -2530,7 +2528,7 @@ impl<'a> Instantiator<'a, '_> { .unwrap_or(0) }) .sum(); - let use_direct_params = result_flat_param_total < MAX_ASYNC_FLAT_PARAMS; + let use_direct_params = result_flat_param_total < MAX_FLAT_PARAMS; // Build up a list of all the lifting functions that will be needed for the types // that are actually being passed through task.return From 02996e0433ae0dea7252e2c4638a7ae25c01b1dd Mon Sep 17 00:00:00 2001 From: Victor Adossi Date: Mon, 27 Apr 2026 19:31:42 +0900 Subject: [PATCH 5/5] test(jco): update tests for future lift/lower, add list --- .../test-components/src/bin/future_lower.rs | 4 +- .../test-components/src/bin/stream_lower.rs | 2 +- packages/jco/test/p3/future-lower.js | 916 +++++++++--------- packages/jco/test/p3/stream-lowers.js | 55 +- 4 files changed, 519 insertions(+), 458 deletions(-) diff --git a/crates/test-components/src/bin/future_lower.rs b/crates/test-components/src/bin/future_lower.rs index 57546b105..80e4ee7d4 100644 --- a/crates/test-components/src/bin/future_lower.rs +++ b/crates/test-components/src/bin/future_lower.rs @@ -126,8 +126,8 @@ impl future_lower_async::Guest for Component { } async fn read_future_value_example_resource_own(rx: FutureReader) { - let _ = rx.await; - // All vals dropped at the end of this function + let v = rx.await; + drop(v); // we do an explicit drop to ensure the drop happens *before* this function returns } async fn read_future_value_example_resource_own_attr( diff --git a/crates/test-components/src/bin/stream_lower.rs b/crates/test-components/src/bin/stream_lower.rs index 625c30a63..7d189eea3 100644 --- a/crates/test-components/src/bin/stream_lower.rs +++ b/crates/test-components/src/bin/stream_lower.rs @@ -155,7 +155,7 @@ impl stream_lower_async::Guest for Component { } async fn read_stream_values_future_string( - mut stream_rx: StreamReader>, + mut stream_rx: StreamReader>, ) -> Vec { let mut vals = Vec::new(); while let Some(fut_rx) = stream_rx.next().await { diff --git a/packages/jco/test/p3/future-lower.js b/packages/jco/test/p3/future-lower.js index d037d19ce..ce3b9dc41 100644 --- a/packages/jco/test/p3/future-lower.js +++ b/packages/jco/test/p3/future-lower.js @@ -1,14 +1,13 @@ import { join } from "node:path"; -import { ReadableStream } from "node:stream/web"; -import { suite, test, assert, beforeAll, beforeEach, afterAll } from "vitest"; +import { suite, test, assert, beforeAll, afterAll, describe, expect } from "vitest"; import { setupAsyncTest } from "../helpers.js"; -import { AsyncFunction, LOCAL_TEST_COMPONENTS_DIR, createReadableStreamFromValues } from "../common.js"; +import { AsyncFunction, LOCAL_TEST_COMPONENTS_DIR } from "../common.js"; import { WASIShim } from "@bytecodealliance/preview2-shim/instantiation"; suite("future lowers", () => { - let esModule, cleanup, instance; + let esModule, cleanup, getInstance, instance; class ExampleResource { #id; @@ -44,489 +43,534 @@ suite("future lowers", () => { esModule = setupRes.esModule; cleanup = setupRes.cleanup; - }); - afterAll(async () => { - await cleanup(); - }); - - beforeEach(async () => { + // We use a completely shared instance because sibling re-entrance + // is mediated by code in task.enter() instance = await esModule.instantiate(undefined, { ...new WASIShim().getImportObject(), "jco:test-components/resources": { ExampleResource, }, }); + getInstance = () => Promise.resolve(instance); + + // NOTE: To use an explicitly new instance per-test, uncomment the lines below + // + // getInstance = async () => esModule.instantiate(undefined, { + // ...new WASIShim().getImportObject(), + // "jco:test-components/resources": { + // ExampleResource, + // }, + // }); }); - test.concurrent("sync passthrough", async () => { - assert.notInstanceOf(instance["jco:test-components/future-lower-sync"].futurePassthrough, AsyncFunction); - - let vals = [0, 5, 10]; - for (const [idx, v] of vals.entries()) { - assert.strictEqual( - await instance["jco:test-components/future-lower-sync"].futurePassthrough(Promise.resolve(v)), - vals[idx] - ); - } + afterAll(async () => { + await cleanup(); }); - // Test late writer -- component should block until a value is written, - // and we should handle a final value + done from an iterator properly - test.concurrent("sync passthrough (slow writer)", async () => { - assert.notInstanceOf(instance["jco:test-components/future-lower-sync"].futurePassthrough, AsyncFunction); + describe("sync", () => { + test.concurrent("sync passthrough", async () => { + const instance = await getInstance(); + assert.notInstanceOf(instance["jco:test-components/future-lower-sync"].futurePassthrough, AsyncFunction); + + let vals = [0, 5, 10]; + for (const [idx, v] of vals.entries()) { + assert.strictEqual( + await instance["jco:test-components/future-lower-sync"].futurePassthrough(Promise.resolve(v)), + vals[idx], + ); + } + }); - const delayed = new Promise((resolve) => setTimeout(() => resolve(42), 300)); - assert.strictEqual( - await instance["jco:test-components/future-lower-sync"].futurePassthrough(delayed), - 42, - ); + // Test late writer -- component should block until a value is written, + // and we should handle a final value + done from an iterator properly + test.concurrent("sync passthrough (slow writer)", async () => { + const instance = await getInstance(); + assert.notInstanceOf(instance["jco:test-components/future-lower-sync"].futurePassthrough, AsyncFunction); + + const delayed = new Promise((resolve) => setTimeout(() => resolve(42), 300)); + assert.strictEqual(await instance["jco:test-components/future-lower-sync"].futurePassthrough(delayed), 42); + }); }); - test.concurrent("async passthrough", async () => { - assert.instanceOf(instance["jco:test-components/future-lower-async"].futurePassthrough, AsyncFunction); + describe("async", () => { + test.concurrent("async passthrough", async () => { + const instance = await getInstance(); + assert.instanceOf(instance["jco:test-components/future-lower-async"].futurePassthrough, AsyncFunction); + + let vals = [10, 5, 0]; + for (const [idx, v] of vals.entries()) { + assert.strictEqual( + await instance["jco:test-components/future-lower-async"].futurePassthrough(Promise.resolve(v)), + vals[idx], + ); + } + }); - let vals = [10, 5, 0]; - for (const [idx, v] of vals.entries()) { - assert.strictEqual( - await instance["jco:test-components/future-lower-async"].futurePassthrough(Promise.resolve(v)), - vals[idx] - ); - } - }); + test.concurrent("async passthrough (slow writer)", async () => { + const instance = await getInstance(); + assert.instanceOf(instance["jco:test-components/future-lower-async"].futurePassthrough, AsyncFunction); - test.concurrent("async passthrough (slow writer)", async () => { - assert.instanceOf(instance["jco:test-components/future-lower-async"].futurePassthrough, AsyncFunction); + const delayed = new Promise((resolve) => setTimeout(() => resolve(42), 300)); + assert.strictEqual(await instance["jco:test-components/future-lower-async"].futurePassthrough(delayed), 42); + }); - const delayed = new Promise((resolve) => setTimeout(() => resolve(42), 300)); - assert.strictEqual( - await instance["jco:test-components/future-lower-async"].futurePassthrough(delayed), - 42, - ); + test.concurrent("bool", async () => { + const instance = await getInstance(); + assert.instanceOf(instance["jco:test-components/future-lower-async"].readFutureValueBool, AsyncFunction); + + let vals = [true, false]; + for (const [idx, v] of vals.entries()) { + assert.strictEqual( + await instance["jco:test-components/future-lower-async"].readFutureValueBool(Promise.resolve(v)), + vals[idx], + ); + } + }); - }); + test.concurrent("u8/s8", async () => { + const instance = await getInstance(); + assert.instanceOf(instance["jco:test-components/future-lower-async"].readFutureValueU8, AsyncFunction); + assert.instanceOf(instance["jco:test-components/future-lower-async"].readFutureValueS8, AsyncFunction); + + let vals = [0, 1, 255]; + for (const [idx, v] of vals.entries()) { + assert.strictEqual( + await instance["jco:test-components/future-lower-async"].readFutureValueU8(Promise.resolve(v)), + vals[idx], + ); + } + + vals = [-128, 0, 1, 127]; + for (const [idx, v] of vals.entries()) { + assert.strictEqual( + await instance["jco:test-components/future-lower-async"].readFutureValueS8(Promise.resolve(v)), + vals[idx], + ); + } + }); - test.concurrent("bool", async () => { - assert.instanceOf(instance["jco:test-components/future-lower-async"].readFutureValueBool, AsyncFunction); + test.concurrent("u16/s16", async () => { + const instance = await getInstance(); + assert.instanceOf(instance["jco:test-components/future-lower-async"].readFutureValueU16, AsyncFunction); + assert.instanceOf(instance["jco:test-components/future-lower-async"].readFutureValueS16, AsyncFunction); + + let vals = [0, 100, 65535]; + for (const [idx, v] of vals.entries()) { + assert.strictEqual( + await instance["jco:test-components/future-lower-async"].readFutureValueU16(Promise.resolve(v)), + vals[idx], + ); + } + + vals = [-32_768, 0, 32_767]; + for (const [idx, v] of vals.entries()) { + assert.strictEqual( + await instance["jco:test-components/future-lower-async"].readFutureValueS16(Promise.resolve(v)), + vals[idx], + ); + } + }); - let vals = [true, false]; - for (const [idx, v] of vals.entries()) { - assert.strictEqual( - await instance["jco:test-components/future-lower-async"].readFutureValueBool( - Promise.resolve(v), - ), - vals[idx], - ); - } - }); + test.concurrent("u32/s32", async () => { + const instance = await getInstance(); + assert.instanceOf(instance["jco:test-components/future-lower-async"].readFutureValueU32, AsyncFunction); + assert.instanceOf(instance["jco:test-components/future-lower-async"].readFutureValueS32, AsyncFunction); + + let vals = [10, 5, 0]; + for (const [idx, v] of vals.entries()) { + assert.strictEqual( + await instance["jco:test-components/future-lower-async"].readFutureValueU32(Promise.resolve(v)), + vals[idx], + ); + } + + vals = [-32, 90001, 3200000]; + for (const [idx, v] of vals.entries()) { + assert.strictEqual( + await instance["jco:test-components/future-lower-async"].readFutureValueS32(Promise.resolve(v)), + vals[idx], + ); + } + }); - test.concurrent("u8/s8", async () => { - assert.instanceOf(instance["jco:test-components/future-lower-async"].readFutureValueU8, AsyncFunction); - assert.instanceOf(instance["jco:test-components/future-lower-async"].readFutureValueS8, AsyncFunction); + test.concurrent("u64/s64", async () => { + const instance = await getInstance(); + assert.instanceOf(instance["jco:test-components/future-lower-async"].readFutureValueU64, AsyncFunction); + assert.instanceOf(instance["jco:test-components/future-lower-async"].readFutureValueS64, AsyncFunction); + + let vals = [0n, 100n, 65535n]; + for (const [idx, v] of vals.entries()) { + assert.strictEqual( + await instance["jco:test-components/future-lower-async"].readFutureValueU64(Promise.resolve(v)), + vals[idx], + ); + } + + vals = [-32_768n, 0n, 32_767n]; + for (const [idx, v] of vals.entries()) { + assert.strictEqual( + await instance["jco:test-components/future-lower-async"].readFutureValueS64(Promise.resolve(v)), + vals[idx], + ); + } + }); - let vals = [0, 1, 255]; - for (const [idx, v] of vals.entries()) { - assert.strictEqual( - await instance["jco:test-components/future-lower-async"].readFutureValueU8( - Promise.resolve(v), - ), - vals[idx], - ); - } + test.concurrent("f32/f64", async () => { + const instance = await getInstance(); + assert.instanceOf(instance["jco:test-components/future-lower-async"].readFutureValueF32, AsyncFunction); + assert.instanceOf(instance["jco:test-components/future-lower-async"].readFutureValueF64, AsyncFunction); + + let vals = [-300.01235, -1.5, -0.0, 0.0, 1.5, 300.01235]; + for (const [idx, v] of vals.entries()) { + assert.closeTo( + await instance["jco:test-components/future-lower-async"].readFutureValueF32(Promise.resolve(v)), + vals[idx], + 0.01, + ); + assert.closeTo( + await instance["jco:test-components/future-lower-async"].readFutureValueF64(Promise.resolve(v)), + vals[idx], + 0.01, + ); + } + + vals = [-60000.01235, -1.5, -0.0, 0.0, 1.5, -60000.01235]; + for (const [idx, v] of vals.entries()) { + assert.closeTo( + await instance["jco:test-components/future-lower-async"].readFutureValueF32(Promise.resolve(v)), + vals[idx], + 0.01, + ); + assert.closeTo( + await instance["jco:test-components/future-lower-async"].readFutureValueF64(Promise.resolve(v)), + vals[idx], + 0.01, + ); + } + }); - vals = [-128, 0, 1, 127]; - for (const [idx, v] of vals.entries()) { - assert.strictEqual( - await instance["jco:test-components/future-lower-async"].readFutureValueS8( - Promise.resolve(v), - ), - vals[idx], - ); - } - }); + test.concurrent("string", async () => { + const instance = await getInstance(); + assert.instanceOf(instance["jco:test-components/future-lower-async"].readFutureValueString, AsyncFunction); + + let vals = ["hello", "world", "!"]; + for (const [idx, v] of vals.entries()) { + assert.strictEqual( + await instance["jco:test-components/future-lower-async"].readFutureValueString(Promise.resolve(v)), + vals[idx], + ); + } + }); - test.concurrent("u16/s16", async () => { - assert.instanceOf(instance["jco:test-components/future-lower-async"].readFutureValueU16, AsyncFunction); - assert.instanceOf(instance["jco:test-components/future-lower-async"].readFutureValueS16, AsyncFunction); + test.concurrent("record", async () => { + assert.instanceOf(instance["jco:test-components/future-lower-async"].readFutureValueRecord, AsyncFunction); + + let vals = [ + { id: 3, idStr: "three" }, + { id: 2, idStr: "two" }, + { id: 1, idStr: "one" }, + ]; + for (const [idx, v] of vals.entries()) { + assert.deepEqual( + await instance["jco:test-components/future-lower-async"].readFutureValueRecord(Promise.resolve(v)), + vals[idx], + ); + } + }); - let vals = [0, 100, 65535]; - for (const [idx, v] of vals.entries()) { - assert.strictEqual( - await instance["jco:test-components/future-lower-async"].readFutureValueU16( + // TODO(FIX): FAILING, BAD + test.skip("variant", async () => { + assert.instanceOf(instance["jco:test-components/future-lower-async"].readFutureValueVariant, AsyncFunction); + + let vals = [ + { tag: "maybe-u32", val: 123 }, + { tag: "maybe-u32", val: null }, + { tag: "str", val: "string-value" }, + { tag: "num", val: 1 }, + ]; + let expected = [ + // TODO: wit type representation smoothing mismatch + { tag: "maybe-u32", val: { tag: "some", val: 123 } }, + { tag: "maybe-u32", val: { tag: "none" } }, + { tag: "str", val: "string-value" }, + { tag: "num", val: 1 }, + ]; + for (const [idx, v] of vals.entries()) { + assert.deepEqual( + await instance["jco:test-components/future-lower-async"].readFutureValueVariant(Promise.resolve(v)), + expected[idx], + 0.01, + ); + } + + vals = [{ tag: "float", val: 123.1 }]; + for (const [idx, v] of vals.entries()) { + const returned = await instance["jco:test-components/future-lower-async"].readFutureValueVariant( Promise.resolve(v), - ), - vals[idx], - ); - } + ); + assert.closeTo(returned.val, vals[idx].val, 0.01); + } + }); - vals = [-32_768, 0, 32_767]; - for (const [idx, v] of vals.entries()) { - assert.strictEqual( - await instance["jco:test-components/future-lower-async"].readFutureValueS16( - Promise.resolve(v), - ), - vals[idx], - ); - } - }); + // TODO(FIX): FAILING, BAD + test.skip("tuple", async () => { + assert.instanceOf(instance["jco:test-components/future-lower-async"].readFutureValueTuple, AsyncFunction); + + let vals = [ + [1, -1, "one"], + [2, -2, "two"], + [3, -3, "two"], + ]; + for (const [idx, v] of vals.entries()) { + assert.deepEqual( + await instance["jco:test-components/future-lower-async"].readFutureValueTuple(Promise.resolve(v)), + vals[idx], + ); + } + }); - test.concurrent("u32/s32", async () => { - assert.instanceOf(instance["jco:test-components/future-lower-async"].readFutureValueU32, AsyncFunction); - assert.instanceOf(instance["jco:test-components/future-lower-async"].readFutureValueS32, AsyncFunction); + test.concurrent("flags", async () => { + const instance = await getInstance(); + assert.instanceOf(instance["jco:test-components/future-lower-async"].readFutureValueFlags, AsyncFunction); + + let vals = [ + { first: true, second: false, third: false }, + { first: false, second: true, third: false }, + { first: false, second: false, third: true }, + ]; + for (const [idx, v] of vals.entries()) { + assert.deepEqual( + await instance["jco:test-components/future-lower-async"].readFutureValueFlags(Promise.resolve(v)), + vals[idx], + ); + } + }); - let vals = [10, 5, 0]; - for (const [idx, v] of vals.entries()) { - assert.strictEqual( - await instance["jco:test-components/future-lower-async"].readFutureValueU32( - Promise.resolve(v), - ), - vals[idx], - ); - } + test.concurrent("enum", async () => { + const instance = await getInstance(); + assert.instanceOf(instance["jco:test-components/future-lower-async"].readFutureValueEnum, AsyncFunction); + + let vals = ["first", "second", "third"]; + for (const [idx, v] of vals.entries()) { + assert.deepEqual( + await instance["jco:test-components/future-lower-async"].readFutureValueEnum(Promise.resolve(v)), + vals[idx], + ); + } + }); - vals = [-32, 90001, 3200000]; - for (const [idx, v] of vals.entries()) { - assert.strictEqual( - await instance["jco:test-components/future-lower-async"].readFutureValueS32( - Promise.resolve(v), - ), - vals[idx], + test.concurrent("option", async () => { + const instance = await getInstance(); + assert.instanceOf( + instance["jco:test-components/future-lower-async"].readFutureValueOptionString, + AsyncFunction, ); - } - }); - test.concurrent("u64/s64", async () => { - assert.instanceOf(instance["jco:test-components/future-lower-async"].readFutureValueU64, AsyncFunction); - assert.instanceOf(instance["jco:test-components/future-lower-async"].readFutureValueS64, AsyncFunction); + let vals = ["present string", null]; + let returnedVals = []; + for (const v of vals) { + returnedVals.push( + await instance["jco:test-components/future-lower-async"].readFutureValueOptionString( + Promise.resolve(v), + ), + ); + } + assert.deepEqual(returnedVals, [ + // TODO: wit type representation smoothing mismatch + { tag: "some", val: "present string" }, + { tag: "none" }, + ]); + }); - let vals = [0n, 100n, 65535n]; - for (const [idx, v] of vals.entries()) { - assert.strictEqual( - await instance["jco:test-components/future-lower-async"].readFutureValueU64( - Promise.resolve(v), - ), - vals[idx], + // TODO(FIX): it's returning the actual nope value if it's an error?? + // A future that resolves to an error actually gets thrown! + test.concurrent("result", async () => { + const instance = await getInstance(); + assert.instanceOf( + instance["jco:test-components/future-lower-async"].readFutureValueResultString, + AsyncFunction, ); - } - vals = [-32_768n, 0n, 32_767n]; - for (const [idx, v] of vals.entries()) { - assert.strictEqual( - await instance["jco:test-components/future-lower-async"].readFutureValueS64( - Promise.resolve(v), - ), - vals[idx], - ); - } - }); - - test.concurrent("f32/f64", async () => { - assert.instanceOf(instance["jco:test-components/future-lower-async"].readFutureValueF32, AsyncFunction); - assert.instanceOf(instance["jco:test-components/future-lower-async"].readFutureValueF64, AsyncFunction); + let vals = [{ tag: "ok", val: "present string" }, "bare string (ok)"]; + let returnedVals = []; + for (const v of vals) { + returnedVals.push( + await instance["jco:test-components/future-lower-async"].readFutureValueResultString( + Promise.resolve(v), + ), + ); + } + assert.deepEqual(returnedVals, ["present string", "bare string (ok)"]); + + // NOTE: Result errors (result) are converted into JS errors, + // and when passed through a Promise, they throw. + vals = [{ tag: "err", val: "nope" }]; + for (const v of vals) { + expect( + instance["jco:test-components/future-lower-async"].readFutureValueResultString(Promise.resolve(v)), + ).rejects.toThrow(v.val); + } + }); - let vals = [-300.01235, -1.5, -0.0, 0.0, 1.5, 300.01235]; - for (const [idx, v] of vals.entries()) { - assert.closeTo( - await instance["jco:test-components/future-lower-async"].readFutureValueF32( - Promise.resolve(v), - ), - vals[idx], - 0.01, - ); - assert.closeTo( - await instance["jco:test-components/future-lower-async"].readFutureValueF64( - Promise.resolve(v), - ), - vals[idx], - 0.01, - ); - } + test.concurrent("list", async () => { + const instance = await getInstance(); + assert.instanceOf(instance["jco:test-components/future-lower-async"].readFutureValueListU8, AsyncFunction); + + let vals = [[0x01, 0x02, 0x03, 0x04, 0x05], new Uint8Array([0x05, 0x04, 0x03, 0x02, 0x01]), []]; + let returnedVals = []; + for (const v of vals) { + returnedVals.push( + await instance["jco:test-components/future-lower-async"].readFutureValueListU8(Promise.resolve(v)), + ); + } + assert.deepEqual(returnedVals, [ + // TODO: wit type representation smoothing mismatch + vals[0], + [...vals[1]], + [], + ]); + }); - vals = [-60000.01235, -1.5, -0.0, 0.0, 1.5, -60000.01235]; - for (const [idx, v] of vals.entries()) { - assert.closeTo( - await instance["jco:test-components/future-lower-async"].readFutureValueF32( - Promise.resolve(v), - ), - vals[idx], - 0.01, + test.concurrent("list", async () => { + const instance = await getInstance(); + assert.instanceOf( + instance["jco:test-components/future-lower-async"].readFutureValueListString, + AsyncFunction, ); - assert.closeTo( - await instance["jco:test-components/future-lower-async"].readFutureValueF64( - Promise.resolve(v), - ), - vals[idx], - 0.01, - ); - } - }); - test.concurrent("string", async () => { - assert.instanceOf(instance["jco:test-components/future-lower-async"].readFutureValueString, AsyncFunction); + let vals = [["first", "second", "third"], []]; + let returnedVals = []; + for (const v of vals) { + returnedVals.push( + await instance["jco:test-components/future-lower-async"].readFutureValueListString( + Promise.resolve(v), + ), + ); + } + assert.deepEqual(returnedVals, vals); + }); - let vals = ["hello", "world", "!"]; - for (const [idx, v] of vals.entries()) { - assert.strictEqual( - await instance["jco:test-components/future-lower-async"].readFutureValueString( - Promise.resolve(v), - ), - vals[idx], + test.concurrent("list>", async () => { + const instance = await getInstance(); + assert.instanceOf( + instance["jco:test-components/future-lower-async"].readFutureValueFixedListU32, + AsyncFunction, ); - } - }); - test.only("record", async () => { - assert.instanceOf(instance["jco:test-components/future-lower-async"].readFutureValueRecord, AsyncFunction); - - let vals = [ - { id: 3, idStr: "three" }, - { id: 2, idStr: "two" }, - { id: 1, idStr: "one" }, - ]; - for (const [idx, v] of vals.entries()) { - assert.deepEqual( - await instance["jco:test-components/future-lower-async"].readFutureValueRecord( - Promise.resolve(v), - ), - vals[idx], - ); - } - }); + let vals = [[1, 2, 3, 4, 5], [0, 0, 0, 0, 0], new Uint32Array([0x05, 0x04, 0x03, 0x02, 0x01])]; + let returnedVals = []; + for (const v of vals) { + returnedVals.push( + await instance["jco:test-components/future-lower-async"].readFutureValueFixedListU32( + Promise.resolve(v), + ), + ); + } + assert.deepEqual(returnedVals, [ + [1, 2, 3, 4, 5], + [0, 0, 0, 0, 0], + [0x05, 0x04, 0x03, 0x02, 0x01], + ]); + }); - test.skip("variant", async () => { - assert.instanceOf(instance["jco:test-components/future-lower-async"].readFutureValueVariant, AsyncFunction); - - let vals = [ - { tag: "maybe-u32", val: 123 }, - { tag: "maybe-u32", val: null }, - { tag: "str", val: "string-value" }, - { tag: "num", val: 1 }, - ]; - let expected = [ - // TODO: wit type representation smoothing mismatch - { tag: "maybe-u32", val: { tag: "some", val: 123 } }, - { tag: "maybe-u32", val: { tag: "none" } }, - { tag: "str", val: "string-value" }, - { tag: "num", val: 1 }, - ]; - for (const [idx, v] of vals.entries()) { - assert.deepEqual( - await instance["jco:test-components/future-lower-async"].readFutureValueVariant( - Promise.resolve(v), - ), - expected[idx], - 0.01, + test.concurrent("list", async () => { + const instance = await getInstance(); + assert.instanceOf( + instance["jco:test-components/future-lower-async"].readFutureValueListRecord, + AsyncFunction, ); - } - vals = [{ tag: "float", val: 123.1 }]; - for (const [idx, v] of vals.entries()) { - const returned = await instance["jco:test-components/future-lower-async"].readFutureValueVariant( - Promise.resolve(v), - ); - assert.closeTo( - returned.val, - vals[idx].val, - 0.01, - ); - } - }); + let vals = [ + [ + { id: 3, idStr: "three" }, + { id: 2, idStr: "two" }, + { id: 1, idStr: "one" }, + ], + [ + { id: 1, idStr: "one-one" }, + { id: 2, idStr: "two-two" }, + { id: 3, idStr: "three-three" }, + ], + ]; + let returnedVals = []; + for (const v of vals) { + returnedVals.push( + await instance["jco:test-components/future-lower-async"].readFutureValueListRecord( + Promise.resolve(v), + ), + ); + } + assert.deepEqual(returnedVals, vals); + }); - test.skip("tuple", async () => { - assert.instanceOf(instance["jco:test-components/future-lower-async"].readFutureValueTuple, AsyncFunction); - - let vals = [ - [1, -1, "one"], - [2, -2, "two"], - [3, -3, "two"], - ]; - for (const [idx, v] of vals.entries()) { - assert.deepEqual( - await instance["jco:test-components/future-lower-async"].readFutureValueTuple( - Promise.resolve(v), - ), - vals[idx], + test.concurrent("example-resource", async () => { + const instance = await getInstance(); + assert.instanceOf( + instance["jco:test-components/future-lower-async"].readFutureValueExampleResourceOwn, + AsyncFunction, ); - } - }); - test.concurrent("flags", async () => { - assert.instanceOf(instance["jco:test-components/future-lower-async"].readFutureValueFlags, AsyncFunction); - - let vals = [ - { first: true, second: false, third: false }, - { first: false, second: true, third: false }, - { first: false, second: false, third: true }, - ]; - for (const [idx, v] of vals.entries()) { - assert.deepEqual( - await instance["jco:test-components/future-lower-async"].readFutureValueFlags( - Promise.resolve(v), - ), - vals[idx], + let vals = [new ExampleResource(0), new ExampleResource(1), new ExampleResource(2)]; + let returnedVals = []; + for (const v of vals) { + returnedVals.push( + await instance["jco:test-components/future-lower-async"].readFutureValueExampleResourceOwn( + Promise.resolve(v), + ), + ); + } + // TODO(fix): we should be able to ensure destructor call, at some point + // see: https://github.com/bytecodealliance/jco/issues/989 + // assert(vals.every(r => r.dropped)); + }); + + test.concurrent("example-resource#get-id", async () => { + const instance = await getInstance(); + assert.instanceOf( + instance["jco:test-components/future-lower-async"].readFutureValueExampleResourceOwnAttr, + AsyncFunction, ); - } - }); - test.concurrent("enum", async () => { - assert.instanceOf(instance["jco:test-components/future-lower-async"].readFutureValueEnum, AsyncFunction); + let vals = [2, 1, 0]; + let returnedVals = []; + for (const v of vals) { + returnedVals.push( + await instance["jco:test-components/future-lower-async"].readFutureValueExampleResourceOwnAttr( + Promise.resolve(new ExampleResource(v)), + ), + ); + } + assert.deepEqual(returnedVals, [2, 1, 0]); + }); - let vals = ["first", "second", "third"]; - for (const [idx, v] of vals.entries()) { - assert.deepEqual( - await instance["jco:test-components/future-lower-async"].readFutureValueEnum( - Promise.resolve(v), - ), - vals[idx], + // NOTE: NodeJS will *collapse* a future> -- Promise> is turned into Promise + // TODO: implement flat lower for (inner) future + test.skip("future", async () => { + const instance = await getInstance(); + assert.instanceOf( + instance["jco:test-components/future-lower-async"].readFutureValueFutureString, + AsyncFunction, ); - } - }); - - // test.concurrent("option", async () => { - // assert.instanceOf(instance["jco:test-components/future-lower-async"].readFutureValueOptionString, AsyncFunction); - - // let vals = ["present string", null]; - // let returnedVals = await instance["jco:test-components/future-lower-async"].readFutureValueOptionString( - // Promise.resolve(vals), - // ); - // assert.deepEqual(returnedVals, [ - // // TODO: wit type representation smoothing mismatch - // { tag: "some", val: "present string" }, - // { tag: "none" }, - // ]); - // }); - - // test.concurrent("result", async () => { - // assert.instanceOf(instance["jco:test-components/future-lower-async"].readFutureValueResultString, AsyncFunction); - - // let vals = [{ tag: "ok", val: "present string" }, { tag: "err", val: "nope" }, "bare string (ok)"]; - // let returnedVals = await instance["jco:test-components/future-lower-async"].readFutureValueResultString( - // Promise.resolve(vals), - // ); - // assert.deepEqual(returnedVals, [ - // // TODO: wit type representation smoothing mismatch - // { tag: "ok", val: "present string" }, - // { tag: "err", val: "nope" }, - // { tag: "ok", val: "bare string (ok)" }, - // ]); - // }); - - // test.concurrent("list", async () => { - // assert.instanceOf(instance["jco:test-components/future-lower-async"].readFutureValueListU8, AsyncFunction); - - // let vals = [[0x01, 0x02, 0x03, 0x04, 0x05], new Uint8Array([0x05, 0x04, 0x03, 0x02, 0x01]), []]; - // let returnedVals = await instance["jco:test-components/future-lower-async"].readFutureValueListU8( - // Promise.resolve(vals), - // ); - // assert.deepEqual(returnedVals, [ - // // TODO: wit type representation smoothing mismatch - // vals[0], - // [...vals[1]], - // [], - // ]); - // }); - - // test.concurrent("list", async () => { - // assert.instanceOf(instance["jco:test-components/future-lower-async"].readFutureValueListString, AsyncFunction); - - // let vals = [["first", "second", "third"], []]; - // let returnedVals = await instance["jco:test-components/future-lower-async"].readFutureValueListString( - // Promise.resolve(vals), - // ); - // assert.deepEqual(returnedVals, vals); - // }); - - // test.concurrent("list>", async () => { - // assert.instanceOf(instance["jco:test-components/future-lower-async"].readFutureValueFixedListU32, AsyncFunction); - - // let vals = [ - // [ - // [1, 2, 3, 4, 5], - // [0, 0, 0, 0, 0], - // ], - // [[0, 0, 0, 0, 0], new Uint32Array([0x05, 0x04, 0x03, 0x02, 0x01])], - // ]; - // let returnedVals = await instance["jco:test-components/future-lower-async"].readFutureValueFixedListU32( - // Promise.resolve(vals), - // ); - // assert.deepEqual(returnedVals, [ - // // TODO(fix): wit type representation smoothing mismatch - // [ - // [1, 2, 3, 4, 5], - // [0, 0, 0, 0, 0], - // ], - // [ - // [0, 0, 0, 0, 0], - // [0x05, 0x04, 0x03, 0x02, 0x01], - // ], - // ]); - // }); - - // test.concurrent("list", async () => { - // assert.instanceOf(instance["jco:test-components/future-lower-async"].readFutureValueListRecord, AsyncFunction); - - // let vals = [ - // [ - // { id: 3, idStr: "three" }, - // { id: 2, idStr: "two" }, - // { id: 1, idStr: "one" }, - // ], - // [ - // { id: 1, idStr: "one-one" }, - // { id: 2, idStr: "two-two" }, - // { id: 3, idStr: "three-three" }, - // ], - // ]; - // let returnedVals = await instance["jco:test-components/future-lower-async"].readFutureValueListRecord( - // Promise.resolve(vals), - // ); - // assert.deepEqual(returnedVals, vals); - // }); - - // test.concurrent("example-resource", async () => { - // assert.instanceOf( - // instance["jco:test-components/future-lower-async"].readFutureValueExampleResourceOwn, - // AsyncFunction, - // ); - - // let vals = [new ExampleResource(0), new ExampleResource(1), new ExampleResource(2)]; - // await instance["jco:test-components/future-lower-async"].readFutureValueExampleResourceOwn( - // Promise.resolve(vals), - // ); - // // TODO(fix): we shoudl be able to ensure destructor call - // // see: https://github.com/bytecodealliance/jco/issues/989 - // // assert(vals.every(r => r.dropped)); - // }); - - // test.concurrent("example-resource#get-id", async () => { - // assert.instanceOf( - // instance["jco:test-components/future-lower-async"].readFutureValueExampleResourceOwnAttr, - // AsyncFunction, - // ); - - // let vals = [new ExampleResource(2), new ExampleResource(1), new ExampleResource(0)]; - // const returnedVals = await instance[ - // "jco:test-components/future-lower-async" - // ].readFutureValueExampleResourceOwnAttr(Promise.resolve(vals)); - // assert.deepEqual(returnedVals, [2, 1, 0]); - // }); - - // test.concurrent("future", async () => { - // assert.instanceOf(instance["jco:test-components/future-lower-async"].readFutureValueFutureString, AsyncFunction); - - // let vals = [ - // Promise.resolve(["first", "future", "values"]), - // Promise.resolve(["second", "future", "here"]), - // Promise.resolve(["third", "values", "in future"]), - // ]; - // const returnedVals = await instance["jco:test-components/future-lower-async"].readFutureValueFutureString( - // Promise.resolve(vals), - // ); - // assert.deepEqual(returnedVals, [ - // ["first", "future", "values"], - // ["second", "future", "here"], - // ["third", "values", "in future"], - // ]); - // }); + let vals = [ + Promise.resolve(["first", "future", "values"]), + Promise.resolve(["second", "future", "here"]), + Promise.resolve(["third", "values", "in future"]), + ]; + let returnedVals = []; + for (const v of vals) { + returnedVals.push( + await instance["jco:test-components/future-lower-async"].readFutureValueFutureString( + Promise.resolve(v), + ), + ); + } + assert.deepEqual(returnedVals, [ + ["first", "future", "values"], + ["second", "future", "here"], + ["third", "values", "in future"], + ]); + }); + }); }); diff --git a/packages/jco/test/p3/stream-lowers.js b/packages/jco/test/p3/stream-lowers.js index bddb51a52..6d3f333a4 100644 --- a/packages/jco/test/p3/stream-lowers.js +++ b/packages/jco/test/p3/stream-lowers.js @@ -122,8 +122,6 @@ suite("stream lowers", () => { describe("async", () => { test.concurrent("async passthrough", async () => { - - // test.concurrent("async passthrough", async () => { // assert.instanceOf(instance["jco:test-components/stream-lower-async"].streamPassthrough, AsyncFunction); // let stream = await instance["jco:test-components/stream-lower-async"].streamPassthrough(readerStream); @@ -282,7 +280,10 @@ suite("stream lowers", () => { test.concurrent("variant", async () => { const instance = await getInstance(); - assert.instanceOf(instance["jco:test-components/stream-lower-async"].readStreamValuesVariant, AsyncFunction); + assert.instanceOf( + instance["jco:test-components/stream-lower-async"].readStreamValuesVariant, + AsyncFunction, + ); let vals = [ { tag: "maybe-u32", val: 123 }, @@ -351,7 +352,10 @@ suite("stream lowers", () => { test.concurrent("option", async () => { const instance = await getInstance(); - assert.instanceOf(instance["jco:test-components/stream-lower-async"].readStreamValuesOptionString, AsyncFunction); + assert.instanceOf( + instance["jco:test-components/stream-lower-async"].readStreamValuesOptionString, + AsyncFunction, + ); let vals = ["present string", null]; let returnedVals = await instance["jco:test-components/stream-lower-async"].readStreamValuesOptionString( @@ -366,7 +370,10 @@ suite("stream lowers", () => { test.concurrent("result", async () => { const instance = await getInstance(); - assert.instanceOf(instance["jco:test-components/stream-lower-async"].readStreamValuesResultString, AsyncFunction); + assert.instanceOf( + instance["jco:test-components/stream-lower-async"].readStreamValuesResultString, + AsyncFunction, + ); let vals = [{ tag: "ok", val: "present string" }, { tag: "err", val: "nope" }, "bare string (ok)"]; let returnedVals = await instance["jco:test-components/stream-lower-async"].readStreamValuesResultString( @@ -398,7 +405,10 @@ suite("stream lowers", () => { test.concurrent("list", async () => { const instance = await getInstance(); - assert.instanceOf(instance["jco:test-components/stream-lower-async"].readStreamValuesListString, AsyncFunction); + assert.instanceOf( + instance["jco:test-components/stream-lower-async"].readStreamValuesListString, + AsyncFunction, + ); let vals = [["first", "second", "third"], []]; let returnedVals = await instance["jco:test-components/stream-lower-async"].readStreamValuesListString( @@ -409,7 +419,10 @@ suite("stream lowers", () => { test.concurrent("list>", async () => { const instance = await getInstance(); - assert.instanceOf(instance["jco:test-components/stream-lower-async"].readStreamValuesFixedListU32, AsyncFunction); + assert.instanceOf( + instance["jco:test-components/stream-lower-async"].readStreamValuesFixedListU32, + AsyncFunction, + ); let vals = [ [ @@ -436,7 +449,10 @@ suite("stream lowers", () => { test.concurrent("list", async () => { const instance = await getInstance(); - assert.instanceOf(instance["jco:test-components/stream-lower-async"].readStreamValuesListRecord, AsyncFunction); + assert.instanceOf( + instance["jco:test-components/stream-lower-async"].readStreamValuesListRecord, + AsyncFunction, + ); let vals = [ [ @@ -488,7 +504,10 @@ suite("stream lowers", () => { test.concurrent("stream", async () => { const instance = await getInstance(); - assert.instanceOf(instance["jco:test-components/stream-lower-async"].readStreamValuesStreamString, AsyncFunction); + assert.instanceOf( + instance["jco:test-components/stream-lower-async"].readStreamValuesStreamString, + AsyncFunction, + ); let vals = [ createReadableStreamFromValues(["first", "stream", "values"]), @@ -505,18 +524,16 @@ suite("stream lowers", () => { ]); }); - // TODO: needs flat lower for futures - test.only("stream>", async () => { + // TODO(fix): needs flat lower for futures + test.skip("stream>", async () => { const instance = await getInstance(); - assert.instanceOf(instance["jco:test-components/stream-lower-async"].readStreamValuesFutureString, AsyncFunction); + assert.instanceOf( + instance["jco:test-components/stream-lower-async"].readStreamValuesFutureString, + AsyncFunction, + ); - let vals = [ - "hello", - "from", - "nested", - "future", - ]; - const input = createReadableStreamFromValues(vals.map(v => Promise.resolve(v))); + let vals = ["hello", "from", "nested", "future"]; + const input = createReadableStreamFromValues(vals.map((v) => Promise.resolve(v))); const stream = await instance["jco:test-components/stream-lower-async"].readStreamValuesFutureString(input); let returnedVals = []; for await (const v of stream) {