@polyengine/runtime 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/esm/cabi/async_values.js +6 -5
- package/esm/cabi/bulk_lists.js +0 -5
- package/esm/cabi/context.js +13 -3
- package/esm/cabi/flatten.js +41 -9
- package/esm/cabi/handles.js +57 -54
- package/esm/cabi/layout.js +113 -52
- package/esm/cabi/load.js +31 -23
- package/esm/cabi/store.js +33 -26
- package/esm/cabi/trap.js +2 -2
- package/esm/cabi/types.js +138 -25
- package/esm/cabi/values.js +25 -7
- package/esm/cache/core.js +2 -11
- package/esm/digest/digest.js +10 -8
- package/esm/digest/mod.js +1 -1
- package/esm/digest/verify.js +6 -86
- package/esm/embedder/casing.js +24 -9
- package/esm/embedder/copy.js +6 -6
- package/esm/embedder/errors.js +2 -2
- package/esm/embedder/imports.js +3 -3
- package/esm/embedder/instantiate.js +170 -41
- package/esm/embedder/mod.js +9 -8
- package/esm/embedder/resources.js +39 -16
- package/esm/embedder/streams.js +36 -37
- package/esm/embedder/sync.js +242 -0
- package/esm/embedder/values.js +84 -22
- package/esm/embedder/version.js +9 -9
- package/esm/exec/boundary.js +318 -165
- package/esm/exec/executor.js +51 -24
- package/esm/exec/host_streams.js +31 -31
- package/esm/intrinsics/async_builtins.js +19 -8
- package/esm/intrinsics/context.js +1 -1
- package/esm/intrinsics/errors.js +9 -9
- package/esm/intrinsics/fact_calls.js +37 -49
- package/esm/intrinsics/mod.js +54 -117
- package/esm/intrinsics/stream_builtins.js +2 -2
- package/esm/intrinsics/transcode.js +1 -1
- package/esm/jspi/bridge.js +4 -3
- package/esm/jspi/suspending.js +10 -5
- package/esm/plan/loader.js +5 -5
- package/esm/shim/translator.js +2 -2
- package/esm/task/mod.js +45 -182
- package/esm/task/scheduler.js +154 -185
- package/esm/task/streams.js +39 -54
- package/esm/task/subtask.js +2 -2
- package/esm/task/thread.js +20 -41
- package/esm/task/waitable.js +0 -1
- package/package.json +2 -2
- package/types/cabi/async_values.d.ts +3 -2
- package/types/cabi/bulk_lists.d.ts +0 -2
- package/types/cabi/context.d.ts +15 -5
- package/types/cabi/flatten.d.ts +2 -2
- package/types/cabi/handles.d.ts +15 -26
- package/types/cabi/layout.d.ts +22 -1
- package/types/cabi/load.d.ts +10 -2
- package/types/cabi/store.d.ts +4 -2
- package/types/cabi/types.d.ts +22 -3
- package/types/digest/mod.d.ts +1 -1
- package/types/digest/verify.d.ts +3 -19
- package/types/embedder/casing.d.ts +9 -1
- package/types/embedder/copy.d.ts +4 -4
- package/types/embedder/instantiate.d.ts +4 -4
- package/types/embedder/mod.d.ts +3 -2
- package/types/embedder/resources.d.ts +20 -7
- package/types/embedder/streams.d.ts +5 -6
- package/types/embedder/sync.d.ts +81 -0
- package/types/embedder/values.d.ts +2 -2
- package/types/exec/boundary.d.ts +66 -42
- package/types/exec/executor.d.ts +3 -2
- package/types/exec/host_streams.d.ts +8 -8
- package/types/intrinsics/errors.d.ts +3 -3
- package/types/intrinsics/mod.d.ts +1 -1
- package/types/intrinsics/stream_builtins.d.ts +2 -2
- package/types/jspi/bridge.d.ts +6 -5
- package/types/jspi/suspending.d.ts +1 -1
- package/types/plan/format.d.ts +11 -10
- package/types/plan/loader.d.ts +2 -2
- package/types/shim/translator.d.ts +2 -2
- package/types/task/mod.d.ts +26 -97
- package/types/task/scheduler.d.ts +81 -62
- package/types/task/streams.d.ts +23 -38
- package/types/task/subtask.d.ts +2 -2
- package/types/task/waitable.d.ts +0 -1
package/esm/embedder/casing.js
CHANGED
|
@@ -38,6 +38,14 @@ function upperFirst(s) {
|
|
|
38
38
|
const MANGLED = /^\[([a-z-]+)\](.*)$/;
|
|
39
39
|
/**
|
|
40
40
|
* Decode a mangled leaf name; unmangled names come back as `plain`.
|
|
41
|
+
*
|
|
42
|
+
* An unknown bracket form throws rather than falling back to `plain`
|
|
43
|
+
* (contracts/embedder-api.md §"Getters and setters (pre-ruling…)", final
|
|
44
|
+
* paragraph): "the runtime refuses unknown bracket forms in mangled names
|
|
45
|
+
* loudly at instantiation (rather than misbinding them as plain names — a
|
|
46
|
+
* `[get]foo` treated as a function named `[get]foo` would be wrong in both
|
|
47
|
+
* directions)". Getter/setter support (`[get]`/`[set]`, upstream
|
|
48
|
+
* WebAssembly/component-model#701) is tracked in polyengine#254.
|
|
41
49
|
* @internal — leaf-name demangling, performed by the runtime and by
|
|
42
50
|
* bindgen-generated code.
|
|
43
51
|
*/
|
|
@@ -48,20 +56,27 @@ export function parseLeafName(raw) {
|
|
|
48
56
|
const [, tag, rest] = m;
|
|
49
57
|
switch (tag) {
|
|
50
58
|
case "constructor":
|
|
51
|
-
|
|
59
|
+
if (!rest.includes("["))
|
|
60
|
+
return { form: "constructor", resource: rest };
|
|
61
|
+
break;
|
|
52
62
|
case "method":
|
|
53
63
|
case "static": {
|
|
54
64
|
const dot = rest.indexOf(".");
|
|
55
65
|
if (dot < 0)
|
|
56
66
|
break;
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
67
|
+
const resource = rest.slice(0, dot);
|
|
68
|
+
// A resource name carrying a further bracket (`[method][get]r.p`, the
|
|
69
|
+
// exact getter/setter-on-instance spelling the pre-ruling names) is
|
|
70
|
+
// NOT a plain `[method]`/`[static]` leaf — it is one of the still-
|
|
71
|
+
// unimplemented forms, and must be refused the same way, not
|
|
72
|
+
// misparsed as a method whose resource is literally `[get]r`.
|
|
73
|
+
if (resource.includes("["))
|
|
74
|
+
break;
|
|
75
|
+
return { form: tag, resource, member: rest.slice(dot + 1) };
|
|
62
76
|
}
|
|
63
77
|
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
78
|
+
throw new Error(`unrecognized mangled export/import name '${raw}': the bracket form is ` +
|
|
79
|
+
`not one this runtime understands (only [constructor]/[method]/` +
|
|
80
|
+
`[static] are implemented; getter/setter forms like [get]/[set] are ` +
|
|
81
|
+
`not yet implemented — polyengine#254)`);
|
|
67
82
|
}
|
package/esm/embedder/copy.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// This runtime copy's identity (contracts/embedder-api.md §"Module identity
|
|
2
|
-
// and @polyengine/protocol"
|
|
2
|
+
// and @polyengine/protocol"; issue #83).
|
|
3
3
|
//
|
|
4
4
|
// One module owns the copy's URL so every diagnostic composes the same
|
|
5
5
|
// message, and so lowering sites deep in the value adapters can name the copy
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
import { copyCensus } from "@polyengine/protocol";
|
|
13
13
|
/**
|
|
14
14
|
* The URL of this copy of the runtime. Identity of the copy.
|
|
15
|
-
* @internal — copy-identity constant for the
|
|
15
|
+
* @internal — copy-identity constant for the module identity multi-copy diagnostics; not
|
|
16
16
|
* host-facing.
|
|
17
17
|
*/
|
|
18
18
|
export const COPY_URL = import.meta.url;
|
|
@@ -26,15 +26,15 @@ export const COPY_URL = import.meta.url;
|
|
|
26
26
|
*
|
|
27
27
|
* INVARIANT: keep in sync with `version` in runtime/deno.json — pinned by
|
|
28
28
|
* runtime/tests/embedder/cross_copy_test.ts.
|
|
29
|
-
* @internal — copy-identity constant for the
|
|
29
|
+
* @internal — copy-identity constant for the module identity multi-copy diagnostics; not
|
|
30
30
|
* host-facing.
|
|
31
31
|
*/
|
|
32
|
-
export const RUNTIME_VERSION = "0.
|
|
32
|
+
export const RUNTIME_VERSION = "0.6.0";
|
|
33
33
|
/**
|
|
34
34
|
* Compose a cross-copy diagnostic: what was foreign, which copy is speaking,
|
|
35
35
|
* the census of every copy in the graph, and the by-value remediation.
|
|
36
36
|
*
|
|
37
|
-
* Kept to one line but complete — the whole point of
|
|
37
|
+
* Kept to one line but complete — the whole point of module identity's stateful half is
|
|
38
38
|
* that "recognized but foreign" is a NAMED failure, never a silent
|
|
39
39
|
* adaptation (a foreign `Stream` pumped as an async iterable) and never a
|
|
40
40
|
* misleading generic ("handle is not an error-context").
|
|
@@ -43,6 +43,6 @@ export function describeCrossCopy(what, remedy) {
|
|
|
43
43
|
const census = copyCensus();
|
|
44
44
|
return `${what} was minted by a DIFFERENT polyengine runtime copy and cannot ` +
|
|
45
45
|
`be used through this one (this copy: ${COPY_URL}${census === "" ? "" : `; ${census}`}). Handles are stateful — their machinery lives in the copy that minted ` +
|
|
46
|
-
`them (contracts/embedder-api.md
|
|
46
|
+
`them (contracts/embedder-api.md §"Module identity")` +
|
|
47
47
|
`${remedy === undefined ? "" : `. ${remedy}`}`;
|
|
48
48
|
}
|
package/esm/embedder/errors.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
// The embedder-facing error model (contracts/embedder-api.md §"Error model").
|
|
2
2
|
//
|
|
3
|
-
// The canonical definitions live in `@polyengine/protocol` since
|
|
3
|
+
// The canonical definitions live in `@polyengine/protocol` since §"Module identity and @polyengine/protocol" —
|
|
4
4
|
// this module is the unchanged import path for them (every existing
|
|
5
5
|
// `from "./errors.ts"` / `@polyengine/runtime/embedder` import keeps working) plus
|
|
6
6
|
// the runtime-local `NameCollisionError`, which never crosses a copy boundary
|
|
7
7
|
// (it is raised while BUILDING a facade, before any value exists) and
|
|
8
|
-
// therefore carries no brand: the
|
|
8
|
+
// therefore carries no brand: the module identity table's omissions are deliberate.
|
|
9
9
|
//
|
|
10
10
|
// Recognition at the runtime's own boundaries is by BRAND, not class: use the
|
|
11
11
|
// `is*` predicates re-exported below, never `instanceof`, for any value that
|
package/esm/embedder/imports.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
// `requiredImports()` — the supported enumeration of a component's linkable
|
|
2
2
|
// import leaves (contracts/embedder-api.md §"Module wiring and
|
|
3
|
-
// instantiation"
|
|
3
|
+
// instantiation").
|
|
4
4
|
//
|
|
5
|
-
//
|
|
6
|
-
// that needed it hand-
|
|
5
|
+
// `plan.imports` proved the right authority, and every embedder
|
|
6
|
+
// that needed it would otherwise hand-roll the same walk.
|
|
7
7
|
// Blessing it removes that. It is also this layer's own input: the facade
|
|
8
8
|
// builds its import wrappers from exactly this list.
|
|
9
9
|
import { loadPlan } from "../plan/loader.js";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// The conventions facade: `instantiate(artifacts, imports, opts)`.
|
|
2
2
|
//
|
|
3
|
-
// DESIGN
|
|
3
|
+
// DESIGN: the facade is **runtime-driven**. Every
|
|
4
4
|
// camelCase name, every resource class and every import wrapper is built here,
|
|
5
5
|
// at instantiate time, from the loaded plan's type tables — the plan already
|
|
6
6
|
// carries names, kinds and function types. Bindgen emits compile-time *types*
|
|
@@ -11,9 +11,9 @@
|
|
|
11
11
|
// contracts/plan-format.md for the wire shapes read here.
|
|
12
12
|
import { loadEnvelope, loadPlan, PlanError } from "../plan/loader.js";
|
|
13
13
|
import { Trap } from "../cabi/trap.js";
|
|
14
|
-
import {
|
|
14
|
+
import { SYNC_ENTRY, hostResourceType, instantiateComponent, } from "../exec/mod.js";
|
|
15
15
|
import { camelCase, parseLeafName, pascalCase } from "./casing.js";
|
|
16
|
-
import { isSuspending, suspending } from "../jspi/suspending.js";
|
|
16
|
+
import { abortable, deferCancel, isAbortable, isDeferCancel, isSuspending, suspending, } from "../jspi/suspending.js";
|
|
17
17
|
import { Translator } from "../shim/mod.js";
|
|
18
18
|
import { copyCensus, isTrap, isComponentException } from "@polyengine/protocol";
|
|
19
19
|
import { NameCollisionError, ComponentException } from "./errors.js";
|
|
@@ -23,6 +23,28 @@ import { buildGuestResourceClass, HostResourceRegistry, invalidateWrapper, lendW
|
|
|
23
23
|
import { BorrowScope, describe, fromHost, toHost, } from "./values.js";
|
|
24
24
|
import { ImportResolver } from "./version.js";
|
|
25
25
|
import { Future, Stream } from "./streams.js";
|
|
26
|
+
import { markSyncCallable } from "./sync.js";
|
|
27
|
+
/**
|
|
28
|
+
* Relay the per-declaration host-import marks from the embedder's function
|
|
29
|
+
* onto the wrapper the executor will actually receive, and return the
|
|
30
|
+
* wrapper.
|
|
31
|
+
*
|
|
32
|
+
* Every `#dispatcher` arm re-wraps the embedder's function in a closure, so a
|
|
33
|
+
* brand left on the original is INVISIBLE to `buildLoweredImport` — for suspending mark
|
|
34
|
+
* that surfaced as a `NeedsJspi`, for cancellation discard (`deferCancel()`) it would be a
|
|
35
|
+
* silently discarded commit, which is precisely the failure the brand exists
|
|
36
|
+
* to prevent. Both marks are relayed by the same helper so a third one cannot
|
|
37
|
+
* be added to one arm and forgotten in the other three.
|
|
38
|
+
*/
|
|
39
|
+
function relayMarks(from, to) {
|
|
40
|
+
if (isSuspending(from))
|
|
41
|
+
suspending(to);
|
|
42
|
+
if (isDeferCancel(from))
|
|
43
|
+
deferCancel(to);
|
|
44
|
+
if (isAbortable(from))
|
|
45
|
+
abortable(to);
|
|
46
|
+
return to;
|
|
47
|
+
}
|
|
26
48
|
/** Per-element codec for a `future<T>` returned in function-result position. */
|
|
27
49
|
function elementCodec(element, o) {
|
|
28
50
|
return {
|
|
@@ -37,7 +59,7 @@ function elementCodec(element, o) {
|
|
|
37
59
|
* single-file JSON emitted by build-time translation (`tools/translate`,
|
|
38
60
|
* or `Translator.translateRaw`), carrying the plan and the FACT adapter
|
|
39
61
|
* modules. The production deploy set is `component.wasm` + its envelope +
|
|
40
|
-
* the runtime: no translator ships (embedder-api.md
|
|
62
|
+
* the runtime: no translator ships (contracts/embedder-api.md §"Module wiring and instantiation").
|
|
41
63
|
*
|
|
42
64
|
* Pure and fetch-agnostic: acquire the two blobs however the platform
|
|
43
65
|
* likes (HTTP, fs, bundler asset) and hand them over. The envelope embeds
|
|
@@ -55,7 +77,7 @@ export function artifactsFromEnvelope(envelopeJson, componentBytes) {
|
|
|
55
77
|
* guest code runs: generated `instantiate` wrappers call this, verify the
|
|
56
78
|
* plan, and only then delegate to `instantiate` below.
|
|
57
79
|
* @internal — bindgen-generated code only — the digest handshake needs the
|
|
58
|
-
* plan before instantiating (
|
|
80
|
+
* plan before instantiating (§"Module wiring and instantiation").
|
|
59
81
|
*/
|
|
60
82
|
export async function resolveArtifacts(src) {
|
|
61
83
|
if ("plan" in src)
|
|
@@ -105,7 +127,7 @@ export async function instantiate(source, imports = {}, opts = {}) {
|
|
|
105
127
|
return instance;
|
|
106
128
|
}
|
|
107
129
|
/**
|
|
108
|
-
* Alias matching
|
|
130
|
+
* Alias matching bindgen's generated import spelling.
|
|
109
131
|
* @internal — alias kept for bindgen-generated code only; hosts call
|
|
110
132
|
* `instantiate`.
|
|
111
133
|
*/
|
|
@@ -154,7 +176,7 @@ class Facade {
|
|
|
154
176
|
// guest `start` function can resolve resource types normally.
|
|
155
177
|
//
|
|
156
178
|
// One resource TYPE can be reached through several resource TABLES
|
|
157
|
-
// (plan-format.md
|
|
179
|
+
// (plan-format.md "Type exports index into `resourceTables`": a type export's index is a table
|
|
158
180
|
// index, and the executor sets impl/dtor on every table whose `resource`
|
|
159
181
|
// matches), hence index-keyed bindings with tokens as aliases.
|
|
160
182
|
artifacts.plan.resourceTables.forEach((table, i) => {
|
|
@@ -167,8 +189,8 @@ class Facade {
|
|
|
167
189
|
this.leaves = requiredImports(this.loaded);
|
|
168
190
|
// A component that imports a resource TYPE cannot be wired without
|
|
169
191
|
// `plan.importedResources`: that table is the only thing mapping the
|
|
170
|
-
// import back to a `ResourceIndex` (
|
|
171
|
-
//
|
|
192
|
+
// import back to a `ResourceIndex` (the `importedResources` field,
|
|
193
|
+
// contracts/plan-format.md schema). Without it every own/borrow of that type would fail late, deep
|
|
172
194
|
// inside a call, as an unattributable `InvalidHandleError`.
|
|
173
195
|
const resourceLeaves = this.leaves.filter((l) => l.kind === "resource");
|
|
174
196
|
if (resourceLeaves.length > 0 &&
|
|
@@ -238,7 +260,7 @@ class Facade {
|
|
|
238
260
|
#guestClass(b) {
|
|
239
261
|
b.cls ??= buildGuestResourceClass({ name: b.name, ctor: null, ctorParams: null, methods: [], statics: [] },
|
|
240
262
|
// The rt is supplied per wrapper, so an anonymous class needs none here.
|
|
241
|
-
{ impl: null, dtor: null }, () => Promise.reject(new TypeError("no methods")), () => []);
|
|
263
|
+
{ impl: null, dtor: null }, () => () => Promise.reject(new TypeError("no methods")), () => []);
|
|
242
264
|
return b.cls;
|
|
243
265
|
}
|
|
244
266
|
/**
|
|
@@ -249,7 +271,7 @@ class Facade {
|
|
|
249
271
|
* construction — before instantiation, and therefore before a guest `start`
|
|
250
272
|
* function can call an import that carries an `own`/`borrow` of one.
|
|
251
273
|
* Imported resources occupy `ResourceIndex` 0..n-1 in `importedResources`
|
|
252
|
-
* order (plan-format.md
|
|
274
|
+
* order (the `importedResources` field, contracts/plan-format.md schema).
|
|
253
275
|
*/
|
|
254
276
|
#bindHostResources() {
|
|
255
277
|
const importedResources = this.artifacts.plan.importedResources ?? [];
|
|
@@ -328,7 +350,7 @@ class Facade {
|
|
|
328
350
|
return rep;
|
|
329
351
|
},
|
|
330
352
|
dropOwn(rep, t) {
|
|
331
|
-
//
|
|
353
|
+
// resource stream: a lowered `own` the guest will never take (an un-taken
|
|
332
354
|
// stream element). Destroy it exactly as a guest-side drop would:
|
|
333
355
|
// host-implemented R runs the instance's [Symbol.dispose] through
|
|
334
356
|
// the registry; guest-implemented R runs the guest dtor via the
|
|
@@ -433,9 +455,10 @@ class Facade {
|
|
|
433
455
|
}
|
|
434
456
|
return impl(...raw);
|
|
435
457
|
};
|
|
436
|
-
//
|
|
437
|
-
//
|
|
438
|
-
|
|
458
|
+
// suspending mark/cancellation discard brand relay, layer 2 of 2 (see #dispatcher): the executor reads
|
|
459
|
+
// the brands off this wrapper, which is what lands in its hostImports
|
|
460
|
+
// record.
|
|
461
|
+
return relayMarks(dispatch, wrapper);
|
|
439
462
|
}
|
|
440
463
|
/** A host-implemented resource type: register the class, own the mapping. */
|
|
441
464
|
#wrapResourceType(leaf, importIndex, provider) {
|
|
@@ -471,10 +494,11 @@ class Facade {
|
|
|
471
494
|
throw new PlanError(`host import '${label(leaf)}' missing or not a function (got ` +
|
|
472
495
|
`${describe(fn)}); expected '${camelCase(m.name)}'`);
|
|
473
496
|
}
|
|
474
|
-
//
|
|
475
|
-
// can relay
|
|
497
|
+
// suspending mark/cancellation discard: the `suspending()` and `deferCancel()` brands ride the
|
|
498
|
+
// dispatch closure so #wrapLeaf can relay them onto the value the
|
|
499
|
+
// executor actually receives.
|
|
476
500
|
//
|
|
477
|
-
//
|
|
501
|
+
// suspending mark receiver rule: an interface member is invoked with its containing
|
|
478
502
|
// object as receiver (matching the static arm's `apply(cls)`), so a
|
|
479
503
|
// class INSTANCE is a fully supported spelling of an interface
|
|
480
504
|
// provider — methods reading instance state work. A world-level bare
|
|
@@ -485,7 +509,7 @@ class Facade {
|
|
|
485
509
|
// liberal-acceptance failure the contract forbids.)
|
|
486
510
|
const receiver = leaf.path.length === 0 ? undefined : provider;
|
|
487
511
|
const dispatch = (args) => fn.apply(receiver, args);
|
|
488
|
-
return
|
|
512
|
+
return relayMarks(fn, dispatch);
|
|
489
513
|
}
|
|
490
514
|
const clsName = pascalCase(m.resource);
|
|
491
515
|
// World-level member leaves resolved the class itself (`#provider`);
|
|
@@ -499,12 +523,12 @@ class Facade {
|
|
|
499
523
|
switch (m.form) {
|
|
500
524
|
case "constructor":
|
|
501
525
|
// Never markable: guest-driven construction of a host resource is
|
|
502
|
-
// synchronous
|
|
526
|
+
// synchronous (contracts/embedder-api.md §"Functions and async"), and stage-3 reserves no
|
|
503
527
|
// constructor-decorator position.
|
|
504
528
|
// deno-lint-ignore no-explicit-any
|
|
505
529
|
return (args) => new cls(...args);
|
|
506
530
|
case "method": {
|
|
507
|
-
//
|
|
531
|
+
// suspending mark: the brand authority for an instance method is the CLASS
|
|
508
532
|
// PROTOTYPE, read at wrap time — the Suspending-wrap decision is
|
|
509
533
|
// per-declaration and taken at instantiation, before any instance
|
|
510
534
|
// exists. Instance-level method overrides do not change
|
|
@@ -516,7 +540,7 @@ class Facade {
|
|
|
516
540
|
// `URLSearchParams.prototype.size`) brand-checks its receiver, and a
|
|
517
541
|
// raw `prototype[member]` read runs it with `this` = the prototype —
|
|
518
542
|
// an engine TypeError at instantiation, even for guests that never
|
|
519
|
-
// call the member. Only a data-property function can carry the
|
|
543
|
+
// call the member. Only a data-property function can carry the suspending mark
|
|
520
544
|
// mark (stage-3 method decorators install data properties), so an
|
|
521
545
|
// accessor-backed member yields no wrap-time function here and stays
|
|
522
546
|
// a call-time concern for the per-call lookup below.
|
|
@@ -530,7 +554,7 @@ class Facade {
|
|
|
530
554
|
}
|
|
531
555
|
return fn.apply(self, rest);
|
|
532
556
|
};
|
|
533
|
-
return
|
|
557
|
+
return relayMarks(protoFn, dispatch);
|
|
534
558
|
}
|
|
535
559
|
case "static": {
|
|
536
560
|
const fn = cls[camelCase(m.member)];
|
|
@@ -538,11 +562,11 @@ class Facade {
|
|
|
538
562
|
throw new PlanError(`host import '${label(leaf)}': ${clsName} has no static ` +
|
|
539
563
|
`'${camelCase(m.member)}'`);
|
|
540
564
|
}
|
|
541
|
-
//
|
|
565
|
+
// suspending mark: a static's brand sits on the function itself (a stage-3
|
|
542
566
|
// static-method decorator marks the function value), readable here
|
|
543
567
|
// at wrap time.
|
|
544
568
|
const dispatch = (args) => fn.apply(cls, args);
|
|
545
|
-
return
|
|
569
|
+
return relayMarks(fn, dispatch);
|
|
546
570
|
}
|
|
547
571
|
}
|
|
548
572
|
}
|
|
@@ -573,7 +597,7 @@ class Facade {
|
|
|
573
597
|
return fromHost(v, resultType, o);
|
|
574
598
|
};
|
|
575
599
|
const fail = (e, args) => {
|
|
576
|
-
// Brand, not class (
|
|
600
|
+
// Brand, not class (§"Module identity and @polyengine/protocol"): a `ComponentException` thrown by a host module
|
|
577
601
|
// that resolved a DIFFERENT runtime copy — or hand-rolled with the
|
|
578
602
|
// registry symbol — is the same value here (issue #83).
|
|
579
603
|
if (isComponentException(e) && isResult) {
|
|
@@ -599,9 +623,9 @@ class Facade {
|
|
|
599
623
|
`only a fallible import may signal an error value`);
|
|
600
624
|
}
|
|
601
625
|
// The #83 signature: in a graph with several copies, an UNBRANDED throw
|
|
602
|
-
// is usually a pre-
|
|
626
|
+
// is usually a pre-module identity copy's `ComponentException` (its brand rode class identity,
|
|
603
627
|
// which does not survive the copy boundary). Say so rather than leaving
|
|
604
|
-
// the latent puzzle that motivated
|
|
628
|
+
// the latent puzzle that motivated §"Module identity and @polyengine/protocol".
|
|
605
629
|
const census = copyCensus();
|
|
606
630
|
throw new Trap(`${where} threw ${describeThrow(e)}. An unbranded throw from a host ` +
|
|
607
631
|
`import is a host bug and becomes a trap: signal a WIT error with ` +
|
|
@@ -609,12 +633,23 @@ class Facade {
|
|
|
609
633
|
(census === ""
|
|
610
634
|
? ""
|
|
611
635
|
: ` (${census} — an error carrying no polyengine brand in a ` +
|
|
612
|
-
`multi-copy graph usually means a pre-
|
|
636
|
+
`multi-copy graph usually means a pre-module identity runtime copy threw ` +
|
|
613
637
|
`it, issue #83.)`));
|
|
614
638
|
};
|
|
615
639
|
return (...raw) => {
|
|
616
640
|
const scope = new BorrowScope();
|
|
617
641
|
const args = ft.params.map((p, i) => toHost(raw[i], p, o, scope));
|
|
642
|
+
// CONTRACT: anything the executor appended PAST the WIT-declared
|
|
643
|
+
// params is a runtime-minted extra, not a component value — today
|
|
644
|
+
// exactly the `abortable()` signal `createLoweredImport` adds for a
|
|
645
|
+
// marked import. It is forwarded verbatim (no `toHost` conversion: it
|
|
646
|
+
// has no `ValType` and must reach the host as the platform object it
|
|
647
|
+
// is). Without this the facade would silently drop the signal and a
|
|
648
|
+
// marked import's `signal` parameter would be forever `undefined` —
|
|
649
|
+
// the failure the mark exists to prevent. The slice is empty for every
|
|
650
|
+
// unmarked import, so no existing path changes shape.
|
|
651
|
+
for (let i = ft.params.length; i < raw.length; i++)
|
|
652
|
+
args.push(raw[i]);
|
|
618
653
|
let out;
|
|
619
654
|
try {
|
|
620
655
|
out = dispatch(args);
|
|
@@ -624,7 +659,7 @@ class Facade {
|
|
|
624
659
|
return fail(e, args);
|
|
625
660
|
}
|
|
626
661
|
if (isThenable(out)) {
|
|
627
|
-
//
|
|
662
|
+
// When the WIT result type is `future<T>`, a thenable
|
|
628
663
|
// return IS the future source ("for `future<T>`, a `Promise<T>` or
|
|
629
664
|
// `Future<T>`" — §"Streams and futures"), not the call's async
|
|
630
665
|
// completion. The import completes immediately with the lowered
|
|
@@ -719,8 +754,8 @@ class Facade {
|
|
|
719
754
|
continue;
|
|
720
755
|
}
|
|
721
756
|
if (exp.kind === "module") {
|
|
722
|
-
// Not WIT-expressible, digest-excluded (
|
|
723
|
-
//
|
|
757
|
+
// Not WIT-expressible, digest-excluded (the `module` export kind,
|
|
758
|
+
// contracts/plan-format.md schema notes): the WIT-shaped facade skips it, the type-export precedent. The
|
|
724
759
|
// raw executor export surface still carries the compiled module.
|
|
725
760
|
continue;
|
|
726
761
|
}
|
|
@@ -754,8 +789,8 @@ class Facade {
|
|
|
754
789
|
const s = spec(member.resource);
|
|
755
790
|
// Prefer the plain-entered variant in jspi mode: the JS `new`
|
|
756
791
|
// cannot await the Promise a promising-wrapped entry returns
|
|
757
|
-
// (exec/boundary.ts
|
|
758
|
-
s.ctor = (fn[
|
|
792
|
+
// (exec/boundary.ts SYNC_ENTRY).
|
|
793
|
+
s.ctor = (fn[SYNC_ENTRY] ?? fn);
|
|
759
794
|
s.ctorParams = ft.params;
|
|
760
795
|
rtOf(ft.results[0], specRt, member.resource);
|
|
761
796
|
break;
|
|
@@ -766,6 +801,7 @@ class Facade {
|
|
|
766
801
|
raw: fn,
|
|
767
802
|
params: ft.params,
|
|
768
803
|
results: ft.results,
|
|
804
|
+
async: ft.async === true,
|
|
769
805
|
});
|
|
770
806
|
rtOf(ft.params[0], specRt, member.resource);
|
|
771
807
|
break;
|
|
@@ -776,6 +812,7 @@ class Facade {
|
|
|
776
812
|
raw: fn,
|
|
777
813
|
params: ft.params,
|
|
778
814
|
results: ft.results,
|
|
815
|
+
async: ft.async === true,
|
|
779
816
|
});
|
|
780
817
|
break;
|
|
781
818
|
}
|
|
@@ -787,7 +824,7 @@ class Facade {
|
|
|
787
824
|
throw new PlanError(`export '${id}': resource '${name}' has leaves but no own/borrow ` +
|
|
788
825
|
`type to identify it by`);
|
|
789
826
|
}
|
|
790
|
-
const cls = buildGuestResourceClass(s, rt, (
|
|
827
|
+
const cls = buildGuestResourceClass(s, rt, (raw, params, results, async, where) => this.#wrapExportFn(raw, { params, results, async }, where), (args, params, where) => args.map((a, i) => fromHost(a, params[i], this.#opts(where))));
|
|
791
828
|
obj[claim(pascalCase(name), name)] = cls;
|
|
792
829
|
const index = this.#tokenIndex.get(rt);
|
|
793
830
|
if (index !== undefined) {
|
|
@@ -844,13 +881,14 @@ class Facade {
|
|
|
844
881
|
#wrapExportFn(fn, ft, where) {
|
|
845
882
|
const o = this.#opts(where);
|
|
846
883
|
const resultType = ft.results.length === 0 ? null : ft.results[0];
|
|
884
|
+
let wrapper;
|
|
847
885
|
if (resultType !== null && resultType.kind === "future") {
|
|
848
886
|
// See `Future.deferred`: a `future<T>` result cannot be delivered
|
|
849
887
|
// *through* a Promise, because promise resolution adopts thenables and
|
|
850
888
|
// `Future<T>` is one. The handle is returned eagerly instead; it is
|
|
851
889
|
// PromiseLike, so `await` still yields `T`.
|
|
852
890
|
const element = resultType.element;
|
|
853
|
-
|
|
891
|
+
wrapper = (...args) => {
|
|
854
892
|
// Advisory 9: the generic branch checks arity; so must this one.
|
|
855
893
|
if (args.length !== ft.params.length) {
|
|
856
894
|
throw new TypeError(`${where}: expected ${ft.params.length} argument(s), got ` +
|
|
@@ -869,20 +907,111 @@ class Facade {
|
|
|
869
907
|
return Future.deferred(pending, elementCodec(element, o));
|
|
870
908
|
};
|
|
871
909
|
}
|
|
872
|
-
|
|
910
|
+
else {
|
|
911
|
+
wrapper = async (...args) => {
|
|
912
|
+
if (args.length !== ft.params.length) {
|
|
913
|
+
throw new TypeError(`${where}: expected ${ft.params.length} argument(s), got ${args.length}`);
|
|
914
|
+
}
|
|
915
|
+
const { lowered, release } = this.#lowerParams(ft.params, args, o);
|
|
916
|
+
let raw;
|
|
917
|
+
try {
|
|
918
|
+
raw = await fn(...lowered);
|
|
919
|
+
}
|
|
920
|
+
finally {
|
|
921
|
+
// Call-scoped reps minted for `borrow<R>` arguments of a
|
|
922
|
+
// host-implemented resource live exactly as long as the call.
|
|
923
|
+
release();
|
|
924
|
+
}
|
|
925
|
+
if (resultType === null)
|
|
926
|
+
return undefined;
|
|
927
|
+
if (resultType.kind === "result") {
|
|
928
|
+
const v = raw;
|
|
929
|
+
if ("error" in v) {
|
|
930
|
+
throw new ComponentException(resultType.error === null
|
|
931
|
+
? undefined
|
|
932
|
+
: toHost(v["error"], resultType.error, o));
|
|
933
|
+
}
|
|
934
|
+
return resultType.ok === null
|
|
935
|
+
? undefined
|
|
936
|
+
: toHost(v["ok"], resultType.ok, o);
|
|
937
|
+
}
|
|
938
|
+
return toHost(raw, resultType, o);
|
|
939
|
+
};
|
|
940
|
+
}
|
|
941
|
+
// sync() brand (contracts/embedder-api.md §"Functions and async"): every
|
|
942
|
+
// returned wrapper is branded, additively — the default Promise-shaped
|
|
943
|
+
// surface above is unchanged either way.
|
|
944
|
+
if (ft.async === true) {
|
|
945
|
+
markSyncCallable(wrapper, { kind: "async" });
|
|
946
|
+
}
|
|
947
|
+
else {
|
|
948
|
+
markSyncCallable(wrapper, {
|
|
949
|
+
kind: "free",
|
|
950
|
+
fn: this.#buildSyncForm(fn, ft, where, o),
|
|
951
|
+
});
|
|
952
|
+
}
|
|
953
|
+
return wrapper;
|
|
954
|
+
}
|
|
955
|
+
/**
|
|
956
|
+
* The synchronous form of a sync-typed export (sync()'s `sync()` adapter),
|
|
957
|
+
* mirroring `#wrapExportFn`'s async form exactly minus the `await`:
|
|
958
|
+
* arity check, `#lowerParams`, the plain (`SYNC_ENTRY`) entry, result
|
|
959
|
+
* mapping.
|
|
960
|
+
*
|
|
961
|
+
* `SYNC_ENTRY` is the plain-entered variant `executor.ts` attaches to every
|
|
962
|
+
* sync-typed lifted export in jspi mode (exec/boundary.ts; in plain mode
|
|
963
|
+
* the lifted function itself already returns synchronously, so `fn` is
|
|
964
|
+
* used as-is — `fn[SYNC_ENTRY] ?? fn`).
|
|
965
|
+
*/
|
|
966
|
+
#buildSyncForm(fn, ft, where, o) {
|
|
967
|
+
const resultType = ft.results.length === 0 ? null : ft.results[0];
|
|
968
|
+
const entry = fn[SYNC_ENTRY] ?? fn;
|
|
969
|
+
const unreachableThenable = (raw) => {
|
|
970
|
+
// Defensive (see the dispatch prompt / sync() failure ladder): a genuine
|
|
971
|
+
// park through a plain entry surfaces as a trap, `NeedsJspi`, or
|
|
972
|
+
// `SyncEntryBusy` — never a settled thenable VALUE. A silent
|
|
973
|
+
// Promise-as-value here would corrupt lifting rather than fail loudly,
|
|
974
|
+
// so this is a diagnostic backstop, not a documented outcome.
|
|
975
|
+
void raw;
|
|
976
|
+
throw new Error(`${where}: the sync entry returned a thenable, which should be ` +
|
|
977
|
+
`unreachable for a sync-typed WIT export (a genuine park surfaces ` +
|
|
978
|
+
`as a trap, NeedsJspi, or SyncEntryBusy instead) — this indicates ` +
|
|
979
|
+
`a runtime defect`);
|
|
980
|
+
};
|
|
981
|
+
if (resultType !== null && resultType.kind === "future") {
|
|
982
|
+
const element = resultType.element;
|
|
983
|
+
return (...args) => {
|
|
984
|
+
if (args.length !== ft.params.length) {
|
|
985
|
+
throw new TypeError(`${where}: expected ${ft.params.length} argument(s), got ` +
|
|
986
|
+
`${args.length}`);
|
|
987
|
+
}
|
|
988
|
+
const { lowered, release } = this.#lowerParams(ft.params, args, o);
|
|
989
|
+
let raw;
|
|
990
|
+
try {
|
|
991
|
+
raw = entry(...lowered);
|
|
992
|
+
}
|
|
993
|
+
finally {
|
|
994
|
+
release();
|
|
995
|
+
}
|
|
996
|
+
if (isThenable(raw))
|
|
997
|
+
unreachableThenable(raw);
|
|
998
|
+
return Future.fromLifted(raw, elementCodec(element, o));
|
|
999
|
+
};
|
|
1000
|
+
}
|
|
1001
|
+
return (...args) => {
|
|
873
1002
|
if (args.length !== ft.params.length) {
|
|
874
1003
|
throw new TypeError(`${where}: expected ${ft.params.length} argument(s), got ${args.length}`);
|
|
875
1004
|
}
|
|
876
1005
|
const { lowered, release } = this.#lowerParams(ft.params, args, o);
|
|
877
1006
|
let raw;
|
|
878
1007
|
try {
|
|
879
|
-
raw =
|
|
1008
|
+
raw = entry(...lowered);
|
|
880
1009
|
}
|
|
881
1010
|
finally {
|
|
882
|
-
// Call-scoped reps minted for `borrow<R>` arguments of a
|
|
883
|
-
// host-implemented resource live exactly as long as the call.
|
|
884
1011
|
release();
|
|
885
1012
|
}
|
|
1013
|
+
if (isThenable(raw))
|
|
1014
|
+
unreachableThenable(raw);
|
|
886
1015
|
if (resultType === null)
|
|
887
1016
|
return undefined;
|
|
888
1017
|
if (resultType.kind === "result") {
|
|
@@ -940,7 +1069,7 @@ function pick(container, path, names) {
|
|
|
940
1069
|
/**
|
|
941
1070
|
* Read a DATA property from `obj` (walking its prototype chain, nearest own
|
|
942
1071
|
* descriptor wins) without ever invoking accessors. Accessor-backed and
|
|
943
|
-
* absent members both yield `undefined`. Used by the
|
|
1072
|
+
* absent members both yield `undefined`. Used by the wrap-time suspending-mark
|
|
944
1073
|
* probe, which must not run platform getters against a bare prototype.
|
|
945
1074
|
*/
|
|
946
1075
|
function dataMember(obj, key) {
|
package/esm/embedder/mod.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// Embedder conventions layer (contracts/embedder-api.md; docs/
|
|
1
|
+
// Embedder conventions layer (contracts/embedder-api.md; docs/consumers.md).
|
|
2
2
|
//
|
|
3
3
|
// The host-facing surface: camelCase facades, resource classes on both sides,
|
|
4
4
|
// stream/future handles, version-canonical import resolution and the branded
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
// the layer works fully untyped. Bindgen (a separate track) emits compile-time
|
|
7
7
|
// types that cast this facade; no generated code participates.
|
|
8
8
|
// Copy registration (contracts/embedder-api.md §"Module identity and
|
|
9
|
-
// @polyengine/protocol"
|
|
9
|
+
// @polyengine/protocol"; issue #83). Runs at module evaluation, so
|
|
10
10
|
// merely importing the embedder surface puts this copy on the census — which
|
|
11
11
|
// is what makes every cross-copy diagnostic below able to name both sides.
|
|
12
12
|
// Multiple copies are DIAGNOSED, NEVER REFUSED: two isolated bundles on one
|
|
@@ -22,8 +22,8 @@ registerRuntimeCopy({
|
|
|
22
22
|
protocolGeneration: PROTOCOL_GENERATION,
|
|
23
23
|
});
|
|
24
24
|
export { COPY_URL, RUNTIME_VERSION } from "./copy.js";
|
|
25
|
-
//
|
|
26
|
-
// version"): the runtime's exported surface is application-only. The
|
|
25
|
+
// The host-ABI version (contracts/embedder-api.md §"The host-ABI surface and its
|
|
26
|
+
// version"): the runtime's exported surface is application-only. The
|
|
27
27
|
// courtesy re-exports (error classes, predicates, brands, `suspending`,
|
|
28
28
|
// realm crossing, the copy registry) are removed — host modules import that
|
|
29
29
|
// vocabulary from `@polyengine/protocol` directly. The runtime still
|
|
@@ -34,9 +34,9 @@ export { requiredImports } from "./imports.js";
|
|
|
34
34
|
// `NameCollisionError` is the one error class that stays here: it's raised
|
|
35
35
|
// while building an instantiation facade, before any handle/value exists —
|
|
36
36
|
// application machinery, not host-ABI vocabulary (contracts/embedder-api.md
|
|
37
|
-
// §"The host-ABI surface and its version",
|
|
37
|
+
// §"The host-ABI surface and its version", §"The host-ABI surface and its version").
|
|
38
38
|
export { NameCollisionError } from "./errors.js";
|
|
39
|
-
// `createStream<T>()` — the
|
|
39
|
+
// `createStream<T>()` — the host-ABI version stream-pair factory (contracts/embedder-api.md
|
|
40
40
|
// §"The host-ABI surface and its version" / §"Streams and futures"): the
|
|
41
41
|
// `Stream.create()` static's application-surface spelling, since the
|
|
42
42
|
// concrete `Stream`/`StreamWriter` classes are no longer exported. Handle
|
|
@@ -46,6 +46,7 @@ export function createStream() {
|
|
|
46
46
|
return InternalStream.create();
|
|
47
47
|
}
|
|
48
48
|
export { GuestResource, HostResourceRegistry } from "./resources.js";
|
|
49
|
-
export { camelCase,
|
|
50
|
-
export {
|
|
49
|
+
export { camelCase, pascalCase } from "./casing.js";
|
|
50
|
+
export { ImportRegistrationError, ImportResolutionError, ImportResolver, } from "./version.js";
|
|
51
51
|
export { BorrowScope, fromHost, toHost, } from "./values.js";
|
|
52
|
+
export { sync } from "./sync.js";
|