diff --git a/crates/js-component-bindgen/src/function_bindgen.rs b/crates/js-component-bindgen/src/function_bindgen.rs index 6b8c1c30c..9b28107a7 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; @@ -396,12 +397,16 @@ impl FunctionBindgen<'_> { )); let component_instance_idx = self.canon_opts.instance.as_u32(); - // If we're within an async function, wait for all top level previous tasks to finish before running + // If we're within a true async function, or one that has simply been lowered as async, + // wait for all top level previous tasks to finish before running // to ensure that guests do not try to run two tasks at the same time. - if is_async && self.requires_async_porcelain { + if is_async || self.requires_async_porcelain { uwriteln!( self.src, r#" + // TODO(fix): this logic should be in the async task.enter() and be backpressure driven + // (note, we may need specific handlers for backpressure changes) + // // All other tasks must finish before we can start this one const taskMetas = {global_task_map}.get({component_instance_idx}); if (taskMetas) {{ @@ -2596,19 +2601,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 } => { @@ -2713,9 +2871,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 { @@ -2807,6 +2968,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!( @@ -2837,6 +3010,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}, }}, }}); @@ -2863,6 +3037,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 1b93aeb26..8d47d809b 100644 --- a/crates/js-component-bindgen/src/intrinsics/mod.rs +++ b/crates/js-component-bindgen/src/intrinsics/mod.rs @@ -614,7 +614,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]; @@ -1367,7 +1368,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), ]) } @@ -1501,6 +1517,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 01a275738..43cf23fc9 100644 --- a/crates/js-component-bindgen/src/intrinsics/p3/async_task.rs +++ b/crates/js-component-bindgen/src/intrinsics/p3/async_task.rs @@ -1146,9 +1146,8 @@ impl AsyncTaskIntrinsic { const ready = readyFn(); if (ready && {global_async_determinism} === 'random') {{ - // const coinFlip = {coin_flip_fn}(); - // if (coinFlip) {{ return true }} - return true; + const coinFlip = {coin_flip_fn}(); + if (coinFlip) {{ return true }} }} const keepGoing = await this.immediateSuspend({{ cancellable, readyFn }}); @@ -2068,7 +2067,8 @@ impl AsyncTaskIntrinsic { callMetadata: {{ memoryIdx, memory, - realloc: getReallocFn(), + realloc: getReallocFn?.(), + getReallocFn, resultPtr: params[0], lowers: resultLowerFns, stringEncoding, @@ -2294,7 +2294,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 216762661..2683431d5 100644 --- a/crates/js-component-bindgen/src/transpile_bindgen.rs +++ b/crates/js-component-bindgen/src/transpile_bindgen.rs @@ -1437,7 +1437,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(); @@ -1517,7 +1517,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); @@ -1776,7 +1776,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); @@ -2181,7 +2181,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 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-lowers.js b/packages/jco/test/p3/stream-lowers.js index e3f63b58f..3dd115f65 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: { @@ -60,7 +60,7 @@ suite("stream lowers", () => { }); test.concurrent("sync passthrough", async () => { - 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({ @@ -70,7 +70,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. @@ -98,7 +98,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) { @@ -108,7 +108,7 @@ suite("stream lowers", () => { }); test.concurrent("async passthrough", async () => { - 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({ @@ -118,7 +118,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); @@ -127,126 +127,126 @@ suite("stream lowers", () => { }); test.concurrent("bool", async () => { - 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); }); test.concurrent("u8/s8", async () => { - 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); }); test.concurrent("u16/s16", async () => { - 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); }); test.concurrent("u32/s32", async () => { - 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); }); test.concurrent("u64/s64", async () => { - 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); }); test.concurrent("f32/f64", async () => { - 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)); }); test.concurrent("string", async () => { - 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); }); test.concurrent("record", async () => { - 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); }); test.concurrent("variant", async () => { - 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 }, @@ -254,7 +254,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, [ @@ -266,55 +266,55 @@ 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); }); test.concurrent("tuple", async () => { - 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); }); test.concurrent("flags", async () => { - 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); }); test.concurrent("enum", async () => { - 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"]); }); test.concurrent("option", async () => { - 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, [ @@ -325,10 +325,10 @@ suite("stream lowers", () => { }); test.concurrent("result", async () => { - 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, [ @@ -340,10 +340,10 @@ suite("stream lowers", () => { }); test.concurrent("list", async () => { - 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, [ @@ -355,17 +355,17 @@ suite("stream lowers", () => { }); test.concurrent("list", async () => { - 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); }); test.concurrent("list>", async () => { - assert.instanceOf(instance["jco:test-components/use-stream-async"].readStreamValuesFixedListU32, AsyncFunction); + assert.instanceOf(instance["jco:test-components/stream-lower-async"].readStreamValuesFixedListU32, AsyncFunction); let vals = [ [ @@ -374,7 +374,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, [ @@ -391,7 +391,7 @@ suite("stream lowers", () => { }); test.concurrent("list", async () => { - assert.instanceOf(instance["jco:test-components/use-stream-async"].readStreamValuesListRecord, AsyncFunction); + assert.instanceOf(instance["jco:test-components/stream-lower-async"].readStreamValuesListRecord, AsyncFunction); let vals = [ [ @@ -405,7 +405,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); @@ -413,12 +413,12 @@ suite("stream lowers", () => { test.concurrent("example-resource", async () => { 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 @@ -428,26 +428,26 @@ suite("stream lowers", () => { test.concurrent("example-resource#get-id", async () => { 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 () => { - 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, [ @@ -456,4 +456,10 @@ suite("stream lowers", () => { ["third", "values", "in stream"], ]); }); + + // TODO + test.skip("stream>", async () => { + throw new Error("NOT YET IMPLEMENTED"); + }); + });