@polyengine/runtime 0.5.1 → 0.6.1
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 +132 -37
- 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 +123 -161
- package/esm/exec/executor.js +37 -25
- package/esm/exec/host_streams.js +31 -31
- package/esm/intrinsics/async_builtins.js +15 -7
- 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 +5 -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 +55 -44
- 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/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.1";
|
|
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,7 +11,7 @@
|
|
|
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
16
|
import { abortable, deferCancel, isAbortable, isDeferCancel, isSuspending, suspending, } from "../jspi/suspending.js";
|
|
17
17
|
import { Translator } from "../shim/mod.js";
|
|
@@ -23,14 +23,15 @@ 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";
|
|
26
27
|
/**
|
|
27
28
|
* Relay the per-declaration host-import marks from the embedder's function
|
|
28
29
|
* onto the wrapper the executor will actually receive, and return the
|
|
29
30
|
* wrapper.
|
|
30
31
|
*
|
|
31
32
|
* Every `#dispatcher` arm re-wraps the embedder's function in a closure, so a
|
|
32
|
-
* brand left on the original is INVISIBLE to `buildLoweredImport` — for
|
|
33
|
-
* that surfaced as a `NeedsJspi`, for
|
|
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
|
|
34
35
|
* silently discarded commit, which is precisely the failure the brand exists
|
|
35
36
|
* to prevent. Both marks are relayed by the same helper so a third one cannot
|
|
36
37
|
* be added to one arm and forgotten in the other three.
|
|
@@ -58,7 +59,7 @@ function elementCodec(element, o) {
|
|
|
58
59
|
* single-file JSON emitted by build-time translation (`tools/translate`,
|
|
59
60
|
* or `Translator.translateRaw`), carrying the plan and the FACT adapter
|
|
60
61
|
* modules. The production deploy set is `component.wasm` + its envelope +
|
|
61
|
-
* the runtime: no translator ships (embedder-api.md
|
|
62
|
+
* the runtime: no translator ships (contracts/embedder-api.md §"Module wiring and instantiation").
|
|
62
63
|
*
|
|
63
64
|
* Pure and fetch-agnostic: acquire the two blobs however the platform
|
|
64
65
|
* likes (HTTP, fs, bundler asset) and hand them over. The envelope embeds
|
|
@@ -76,7 +77,7 @@ export function artifactsFromEnvelope(envelopeJson, componentBytes) {
|
|
|
76
77
|
* guest code runs: generated `instantiate` wrappers call this, verify the
|
|
77
78
|
* plan, and only then delegate to `instantiate` below.
|
|
78
79
|
* @internal — bindgen-generated code only — the digest handshake needs the
|
|
79
|
-
* plan before instantiating (
|
|
80
|
+
* plan before instantiating (§"Module wiring and instantiation").
|
|
80
81
|
*/
|
|
81
82
|
export async function resolveArtifacts(src) {
|
|
82
83
|
if ("plan" in src)
|
|
@@ -126,7 +127,7 @@ export async function instantiate(source, imports = {}, opts = {}) {
|
|
|
126
127
|
return instance;
|
|
127
128
|
}
|
|
128
129
|
/**
|
|
129
|
-
* Alias matching
|
|
130
|
+
* Alias matching bindgen's generated import spelling.
|
|
130
131
|
* @internal — alias kept for bindgen-generated code only; hosts call
|
|
131
132
|
* `instantiate`.
|
|
132
133
|
*/
|
|
@@ -175,7 +176,7 @@ class Facade {
|
|
|
175
176
|
// guest `start` function can resolve resource types normally.
|
|
176
177
|
//
|
|
177
178
|
// One resource TYPE can be reached through several resource TABLES
|
|
178
|
-
// (plan-format.md
|
|
179
|
+
// (plan-format.md "Type exports index into `resourceTables`": a type export's index is a table
|
|
179
180
|
// index, and the executor sets impl/dtor on every table whose `resource`
|
|
180
181
|
// matches), hence index-keyed bindings with tokens as aliases.
|
|
181
182
|
artifacts.plan.resourceTables.forEach((table, i) => {
|
|
@@ -188,8 +189,8 @@ class Facade {
|
|
|
188
189
|
this.leaves = requiredImports(this.loaded);
|
|
189
190
|
// A component that imports a resource TYPE cannot be wired without
|
|
190
191
|
// `plan.importedResources`: that table is the only thing mapping the
|
|
191
|
-
// import back to a `ResourceIndex` (
|
|
192
|
-
//
|
|
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
|
|
193
194
|
// inside a call, as an unattributable `InvalidHandleError`.
|
|
194
195
|
const resourceLeaves = this.leaves.filter((l) => l.kind === "resource");
|
|
195
196
|
if (resourceLeaves.length > 0 &&
|
|
@@ -259,7 +260,7 @@ class Facade {
|
|
|
259
260
|
#guestClass(b) {
|
|
260
261
|
b.cls ??= buildGuestResourceClass({ name: b.name, ctor: null, ctorParams: null, methods: [], statics: [] },
|
|
261
262
|
// The rt is supplied per wrapper, so an anonymous class needs none here.
|
|
262
|
-
{ impl: null, dtor: null }, () => Promise.reject(new TypeError("no methods")), () => []);
|
|
263
|
+
{ impl: null, dtor: null }, () => () => Promise.reject(new TypeError("no methods")), () => []);
|
|
263
264
|
return b.cls;
|
|
264
265
|
}
|
|
265
266
|
/**
|
|
@@ -270,7 +271,7 @@ class Facade {
|
|
|
270
271
|
* construction — before instantiation, and therefore before a guest `start`
|
|
271
272
|
* function can call an import that carries an `own`/`borrow` of one.
|
|
272
273
|
* Imported resources occupy `ResourceIndex` 0..n-1 in `importedResources`
|
|
273
|
-
* order (plan-format.md
|
|
274
|
+
* order (the `importedResources` field, contracts/plan-format.md schema).
|
|
274
275
|
*/
|
|
275
276
|
#bindHostResources() {
|
|
276
277
|
const importedResources = this.artifacts.plan.importedResources ?? [];
|
|
@@ -349,7 +350,7 @@ class Facade {
|
|
|
349
350
|
return rep;
|
|
350
351
|
},
|
|
351
352
|
dropOwn(rep, t) {
|
|
352
|
-
//
|
|
353
|
+
// resource stream: a lowered `own` the guest will never take (an un-taken
|
|
353
354
|
// stream element). Destroy it exactly as a guest-side drop would:
|
|
354
355
|
// host-implemented R runs the instance's [Symbol.dispose] through
|
|
355
356
|
// the registry; guest-implemented R runs the guest dtor via the
|
|
@@ -454,7 +455,7 @@ class Facade {
|
|
|
454
455
|
}
|
|
455
456
|
return impl(...raw);
|
|
456
457
|
};
|
|
457
|
-
//
|
|
458
|
+
// suspending mark/cancellation discard brand relay, layer 2 of 2 (see #dispatcher): the executor reads
|
|
458
459
|
// the brands off this wrapper, which is what lands in its hostImports
|
|
459
460
|
// record.
|
|
460
461
|
return relayMarks(dispatch, wrapper);
|
|
@@ -493,11 +494,11 @@ class Facade {
|
|
|
493
494
|
throw new PlanError(`host import '${label(leaf)}' missing or not a function (got ` +
|
|
494
495
|
`${describe(fn)}); expected '${camelCase(m.name)}'`);
|
|
495
496
|
}
|
|
496
|
-
//
|
|
497
|
+
// suspending mark/cancellation discard: the `suspending()` and `deferCancel()` brands ride the
|
|
497
498
|
// dispatch closure so #wrapLeaf can relay them onto the value the
|
|
498
499
|
// executor actually receives.
|
|
499
500
|
//
|
|
500
|
-
//
|
|
501
|
+
// suspending mark receiver rule: an interface member is invoked with its containing
|
|
501
502
|
// object as receiver (matching the static arm's `apply(cls)`), so a
|
|
502
503
|
// class INSTANCE is a fully supported spelling of an interface
|
|
503
504
|
// provider — methods reading instance state work. A world-level bare
|
|
@@ -522,12 +523,12 @@ class Facade {
|
|
|
522
523
|
switch (m.form) {
|
|
523
524
|
case "constructor":
|
|
524
525
|
// Never markable: guest-driven construction of a host resource is
|
|
525
|
-
// synchronous
|
|
526
|
+
// synchronous (contracts/embedder-api.md §"Functions and async"), and stage-3 reserves no
|
|
526
527
|
// constructor-decorator position.
|
|
527
528
|
// deno-lint-ignore no-explicit-any
|
|
528
529
|
return (args) => new cls(...args);
|
|
529
530
|
case "method": {
|
|
530
|
-
//
|
|
531
|
+
// suspending mark: the brand authority for an instance method is the CLASS
|
|
531
532
|
// PROTOTYPE, read at wrap time — the Suspending-wrap decision is
|
|
532
533
|
// per-declaration and taken at instantiation, before any instance
|
|
533
534
|
// exists. Instance-level method overrides do not change
|
|
@@ -539,7 +540,7 @@ class Facade {
|
|
|
539
540
|
// `URLSearchParams.prototype.size`) brand-checks its receiver, and a
|
|
540
541
|
// raw `prototype[member]` read runs it with `this` = the prototype —
|
|
541
542
|
// an engine TypeError at instantiation, even for guests that never
|
|
542
|
-
// 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
|
|
543
544
|
// mark (stage-3 method decorators install data properties), so an
|
|
544
545
|
// accessor-backed member yields no wrap-time function here and stays
|
|
545
546
|
// a call-time concern for the per-call lookup below.
|
|
@@ -561,7 +562,7 @@ class Facade {
|
|
|
561
562
|
throw new PlanError(`host import '${label(leaf)}': ${clsName} has no static ` +
|
|
562
563
|
`'${camelCase(m.member)}'`);
|
|
563
564
|
}
|
|
564
|
-
//
|
|
565
|
+
// suspending mark: a static's brand sits on the function itself (a stage-3
|
|
565
566
|
// static-method decorator marks the function value), readable here
|
|
566
567
|
// at wrap time.
|
|
567
568
|
const dispatch = (args) => fn.apply(cls, args);
|
|
@@ -596,7 +597,7 @@ class Facade {
|
|
|
596
597
|
return fromHost(v, resultType, o);
|
|
597
598
|
};
|
|
598
599
|
const fail = (e, args) => {
|
|
599
|
-
// Brand, not class (
|
|
600
|
+
// Brand, not class (§"Module identity and @polyengine/protocol"): a `ComponentException` thrown by a host module
|
|
600
601
|
// that resolved a DIFFERENT runtime copy — or hand-rolled with the
|
|
601
602
|
// registry symbol — is the same value here (issue #83).
|
|
602
603
|
if (isComponentException(e) && isResult) {
|
|
@@ -622,9 +623,9 @@ class Facade {
|
|
|
622
623
|
`only a fallible import may signal an error value`);
|
|
623
624
|
}
|
|
624
625
|
// The #83 signature: in a graph with several copies, an UNBRANDED throw
|
|
625
|
-
// is usually a pre-
|
|
626
|
+
// is usually a pre-module identity copy's `ComponentException` (its brand rode class identity,
|
|
626
627
|
// which does not survive the copy boundary). Say so rather than leaving
|
|
627
|
-
// the latent puzzle that motivated
|
|
628
|
+
// the latent puzzle that motivated §"Module identity and @polyengine/protocol".
|
|
628
629
|
const census = copyCensus();
|
|
629
630
|
throw new Trap(`${where} threw ${describeThrow(e)}. An unbranded throw from a host ` +
|
|
630
631
|
`import is a host bug and becomes a trap: signal a WIT error with ` +
|
|
@@ -632,13 +633,13 @@ class Facade {
|
|
|
632
633
|
(census === ""
|
|
633
634
|
? ""
|
|
634
635
|
: ` (${census} — an error carrying no polyengine brand in a ` +
|
|
635
|
-
`multi-copy graph usually means a pre-
|
|
636
|
+
`multi-copy graph usually means a pre-module identity runtime copy threw ` +
|
|
636
637
|
`it, issue #83.)`));
|
|
637
638
|
};
|
|
638
639
|
return (...raw) => {
|
|
639
640
|
const scope = new BorrowScope();
|
|
640
641
|
const args = ft.params.map((p, i) => toHost(raw[i], p, o, scope));
|
|
641
|
-
// CONTRACT
|
|
642
|
+
// CONTRACT: anything the executor appended PAST the WIT-declared
|
|
642
643
|
// params is a runtime-minted extra, not a component value — today
|
|
643
644
|
// exactly the `abortable()` signal `createLoweredImport` adds for a
|
|
644
645
|
// marked import. It is forwarded verbatim (no `toHost` conversion: it
|
|
@@ -658,7 +659,7 @@ class Facade {
|
|
|
658
659
|
return fail(e, args);
|
|
659
660
|
}
|
|
660
661
|
if (isThenable(out)) {
|
|
661
|
-
//
|
|
662
|
+
// When the WIT result type is `future<T>`, a thenable
|
|
662
663
|
// return IS the future source ("for `future<T>`, a `Promise<T>` or
|
|
663
664
|
// `Future<T>`" — §"Streams and futures"), not the call's async
|
|
664
665
|
// completion. The import completes immediately with the lowered
|
|
@@ -753,8 +754,8 @@ class Facade {
|
|
|
753
754
|
continue;
|
|
754
755
|
}
|
|
755
756
|
if (exp.kind === "module") {
|
|
756
|
-
// Not WIT-expressible, digest-excluded (
|
|
757
|
-
//
|
|
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
|
|
758
759
|
// raw executor export surface still carries the compiled module.
|
|
759
760
|
continue;
|
|
760
761
|
}
|
|
@@ -788,8 +789,8 @@ class Facade {
|
|
|
788
789
|
const s = spec(member.resource);
|
|
789
790
|
// Prefer the plain-entered variant in jspi mode: the JS `new`
|
|
790
791
|
// cannot await the Promise a promising-wrapped entry returns
|
|
791
|
-
// (exec/boundary.ts
|
|
792
|
-
s.ctor = (fn[
|
|
792
|
+
// (exec/boundary.ts SYNC_ENTRY).
|
|
793
|
+
s.ctor = (fn[SYNC_ENTRY] ?? fn);
|
|
793
794
|
s.ctorParams = ft.params;
|
|
794
795
|
rtOf(ft.results[0], specRt, member.resource);
|
|
795
796
|
break;
|
|
@@ -800,6 +801,7 @@ class Facade {
|
|
|
800
801
|
raw: fn,
|
|
801
802
|
params: ft.params,
|
|
802
803
|
results: ft.results,
|
|
804
|
+
async: ft.async === true,
|
|
803
805
|
});
|
|
804
806
|
rtOf(ft.params[0], specRt, member.resource);
|
|
805
807
|
break;
|
|
@@ -810,6 +812,7 @@ class Facade {
|
|
|
810
812
|
raw: fn,
|
|
811
813
|
params: ft.params,
|
|
812
814
|
results: ft.results,
|
|
815
|
+
async: ft.async === true,
|
|
813
816
|
});
|
|
814
817
|
break;
|
|
815
818
|
}
|
|
@@ -821,7 +824,7 @@ class Facade {
|
|
|
821
824
|
throw new PlanError(`export '${id}': resource '${name}' has leaves but no own/borrow ` +
|
|
822
825
|
`type to identify it by`);
|
|
823
826
|
}
|
|
824
|
-
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))));
|
|
825
828
|
obj[claim(pascalCase(name), name)] = cls;
|
|
826
829
|
const index = this.#tokenIndex.get(rt);
|
|
827
830
|
if (index !== undefined) {
|
|
@@ -878,13 +881,14 @@ class Facade {
|
|
|
878
881
|
#wrapExportFn(fn, ft, where) {
|
|
879
882
|
const o = this.#opts(where);
|
|
880
883
|
const resultType = ft.results.length === 0 ? null : ft.results[0];
|
|
884
|
+
let wrapper;
|
|
881
885
|
if (resultType !== null && resultType.kind === "future") {
|
|
882
886
|
// See `Future.deferred`: a `future<T>` result cannot be delivered
|
|
883
887
|
// *through* a Promise, because promise resolution adopts thenables and
|
|
884
888
|
// `Future<T>` is one. The handle is returned eagerly instead; it is
|
|
885
889
|
// PromiseLike, so `await` still yields `T`.
|
|
886
890
|
const element = resultType.element;
|
|
887
|
-
|
|
891
|
+
wrapper = (...args) => {
|
|
888
892
|
// Advisory 9: the generic branch checks arity; so must this one.
|
|
889
893
|
if (args.length !== ft.params.length) {
|
|
890
894
|
throw new TypeError(`${where}: expected ${ft.params.length} argument(s), got ` +
|
|
@@ -903,20 +907,111 @@ class Facade {
|
|
|
903
907
|
return Future.deferred(pending, elementCodec(element, o));
|
|
904
908
|
};
|
|
905
909
|
}
|
|
906
|
-
|
|
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) => {
|
|
907
1002
|
if (args.length !== ft.params.length) {
|
|
908
1003
|
throw new TypeError(`${where}: expected ${ft.params.length} argument(s), got ${args.length}`);
|
|
909
1004
|
}
|
|
910
1005
|
const { lowered, release } = this.#lowerParams(ft.params, args, o);
|
|
911
1006
|
let raw;
|
|
912
1007
|
try {
|
|
913
|
-
raw =
|
|
1008
|
+
raw = entry(...lowered);
|
|
914
1009
|
}
|
|
915
1010
|
finally {
|
|
916
|
-
// Call-scoped reps minted for `borrow<R>` arguments of a
|
|
917
|
-
// host-implemented resource live exactly as long as the call.
|
|
918
1011
|
release();
|
|
919
1012
|
}
|
|
1013
|
+
if (isThenable(raw))
|
|
1014
|
+
unreachableThenable(raw);
|
|
920
1015
|
if (resultType === null)
|
|
921
1016
|
return undefined;
|
|
922
1017
|
if (resultType.kind === "result") {
|
|
@@ -974,7 +1069,7 @@ function pick(container, path, names) {
|
|
|
974
1069
|
/**
|
|
975
1070
|
* Read a DATA property from `obj` (walking its prototype chain, nearest own
|
|
976
1071
|
* descriptor wins) without ever invoking accessors. Accessor-backed and
|
|
977
|
-
* absent members both yield `undefined`. Used by the
|
|
1072
|
+
* absent members both yield `undefined`. Used by the wrap-time suspending-mark
|
|
978
1073
|
* probe, which must not run platform getters against a bare prototype.
|
|
979
1074
|
*/
|
|
980
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";
|