Skip to content

Commit 9dc7141

Browse files
authored
Merge pull request #61 from lann/translator-package
@deltic/translator: packaged asset + defaultTranslator() per-platform loader
2 parents eb3f8d0 + 8786f7b commit 9dc7141

12 files changed

Lines changed: 174 additions & 30 deletions

File tree

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
/target/
2+
# @deltic/translator packaged asset (copied by `just shim`)
3+
translator/translator_shim.wasm
24
harness/generated/
35
examples/guests/build/
46
node_modules/
@@ -17,3 +19,5 @@ bench/boundary/deltic-embedder.local.mjs
1719
bench/boundary/generated/
1820
bench/boundary/node_modules/
1921
bench/boundary/guest/target/
22+
# @deltic/translator packaged asset (copied by `just shim`)
23+
translator/translator_shim.wasm

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ Pre-1.0, but densely gated:
3939
| `runtime/` | TS core: plan executor, canonical ABI, 0.3 task scheduler, JSPI bridge, embedder API (`runtime/src/embedder`) |
4040
| `crates/bindgen` | WIT → TypeScript types for the embedder conventions |
4141
| `examples/` | **start here to embed**: hello-world + kitchen-sink (WIT + Rust guest + TS host, self-checking), plus the guest fixture corpus |
42+
| `translator/` | `@deltic/translator`: the packaged translator asset + `defaultTranslator()` per-platform loader (build-time alternative: `tools/translate`) |
4243
| `wasi-shims/` | minimal WASI providers (p2 baseline + p3 clocks), one per semver track |
4344
| `ct-runner/` | conformance-suite runner for the polymorph-test L1 contract |
4445
| `harness/` + `tools/browser` | official-suite harness; Deno lane + Chromium/Firefox/WebKit lanes |

deno.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
"./harness",
55
"./wasi-shims",
66
"./ct-runner",
7-
"./examples"
7+
"./examples",
8+
"./translator"
89
]
910
}

examples/hello-world/host.ts

Lines changed: 9 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,28 +2,20 @@
22
// call: give `instantiate` the component bytes and the translator, get
33
// typed-shaped exports back.
44
//
5-
// The two wasm files are read with `Deno.readFile`, so this script runs
6-
// with a scoped read permission (run.sh passes it):
5+
// The translator comes from @deltic/translator — on Deno it arrives via a
6+
// native wasm-module import (permission-free); the only permission this
7+
// script needs is reading the component it runs:
78
//
8-
// deno run --allow-read=..,../../target host.ts
9+
// deno run --allow-read=build host.ts
910
//
10-
// (Deno's `import ... with { type: "bytes" }` will make this flag-free
11-
// once it stabilizes — it is behind --unstable-raw-imports as of Deno
12-
// 2.9, and `type: "text"` is not an option for binaries: lossy UTF-8
13-
// decoding corrupts them.)
14-
//
15-
// Inside this repository `@deltic/runtime` resolves through the Deno
16-
// workspace; a published consumer uses the same specifier via JSR/npm or
17-
// the `deltic-embedder.mjs` release bundle (deltic#16 tracks packaging).
11+
// Inside this repository `@deltic/runtime` and `@deltic/translator`
12+
// resolve through the Deno workspace; a published consumer uses the same
13+
// specifiers via JSR/npm (deltic#16 tracks packaging).
1814

1915
import { instantiate } from "@deltic/runtime/embedder";
16+
import { defaultTranslator } from "@deltic/translator";
2017

21-
const translator = await Deno.readFile(
22-
new URL(
23-
"../../target/wasm32-unknown-unknown/release/translator_shim.wasm",
24-
import.meta.url,
25-
),
26-
);
18+
const translator = await defaultTranslator();
2719
const componentBytes = await Deno.readFile(
2820
new URL("build/hello.component.wasm", import.meta.url),
2921
);

examples/hello-world/run.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,4 @@ wasm-tools component new \
1818
wasm-tools validate --features component-model build/hello.component.wasm
1919

2020
deno check host.ts
21-
deno run --allow-read=..,../../target host.ts
21+
deno run --allow-read=build host.ts

examples/kitchen-sink/host.ts

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import {
2222
suspending,
2323
WitError,
2424
} from "@deltic/runtime/embedder";
25+
import { defaultTranslator } from "@deltic/translator";
2526

2627
// Tiny self-checks so the example fails loudly if the API drifts.
2728
// (`undefined` is meaningful in the conventions — the outermost-option
@@ -117,20 +118,16 @@ const imports = {
117118

118119
// --- §1: translate + instantiate --------------------------------------------
119120

120-
const translator = await Deno.readFile(
121-
new URL(
122-
"../../target/wasm32-unknown-unknown/release/translator_shim.wasm",
123-
import.meta.url,
124-
),
125-
);
121+
const translator = await defaultTranslator();
126122
const componentBytes = await Deno.readFile(
127123
new URL("build/kitchen-sink.component.wasm", import.meta.url),
128124
);
129125

130-
// `{ componentBytes, translator }` translates internally (A3). When
131-
// instantiating several components, create one `Translator` explicitly
132-
// (`Translator.create(bytes)` from @deltic/runtime/shim) and pass it here
133-
// instead — the wasm compile is the cost worth sharing.
126+
// `{ componentBytes, translator }` translates internally (A3);
127+
// `defaultTranslator()` is @deltic/translator's packaged, per-realm-cached
128+
// loader (on Deno: a native wasm-module import — no permissions). Apps
129+
// that know their components at build time can skip the translator
130+
// entirely: see tools/translate (embedder-api A4).
134131
//
135132
// A marked import is auto-detection evidence: this instantiation selects
136133
// JSPI mode by itself. (`jspi: false` would force plain mode, where a

examples/kitchen-sink/run.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,4 @@ wasm-tools component new \
1818
wasm-tools validate --features component-model,cm-async build/kitchen-sink.component.wasm
1919

2020
deno check host.ts
21-
deno run --allow-read=..,../../target host.ts
21+
deno run --allow-read=build host.ts

justfile

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ shim:
4343
CARGO_PROFILE_RELEASE_PANIC=abort \
4444
CARGO_PROFILE_RELEASE_STRIP=symbols \
4545
cargo build -p translator-shim --target wasm32-unknown-unknown --release
46+
cp target/wasm32-unknown-unknown/release/translator_shim.wasm translator/translator_shim.wasm
4647

4748
# wasmtime CLI is optional in build.sh (smoke run only when present).
4849
# Guest fixture components (examples/guests/build/, gitignored): the
@@ -62,6 +63,7 @@ examples: shim
6263
# mismatched-pair refusal.
6364
test-translate: shim
6465
deno test --allow-read --allow-write=/tmp --allow-run tools/translate/translate_test.ts
66+
cd translator && deno task check && deno task test
6567

6668
# Rehearsal finding: 20 runtime e2e tests self-skip when it is absent —
6769
# generation must precede the runtime suite (318/0/3 with; 298/0/23 without).

translator/deno.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"name": "@deltic/translator",
3+
"version": "0.1.0",
4+
"exports": { ".": "./mod.ts" },
5+
"tasks": {
6+
"test": "deno test --allow-read tests/",
7+
"check": "deno check mod.ts tests/"
8+
}
9+
}

translator/mod.ts

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
// @deltic/translator — the packaged translator wasm plus its per-platform
2+
// loader (issue #16 delivery design note, item 3).
3+
//
4+
// Why a separate package: the translator is a versioned peer of the
5+
// runtime (plan-format coupling), so it ships inside the same release —
6+
// but embedders that translate at BUILD time (tools/translate,
7+
// embedder-api A4) deploy no translator at all, and keeping the ~1.85 MB
8+
// asset out of @deltic/runtime keeps their production graphs clean.
9+
//
10+
// The asset (`translator_shim.wasm`, sibling to this module) is copied
11+
// from the cargo build by `just shim` and is gitignored — run `just shim`
12+
// once in a fresh checkout. Publish tooling will pin the exact asset (and
13+
// its digest) into the released package when #16's packaging lands.
14+
15+
import { Translator } from "@deltic/runtime/shim";
16+
17+
let singleton: Promise<Translator> | undefined;
18+
19+
/**
20+
* The packaged translator, loaded lazily and cached for the realm.
21+
*
22+
* Platform paths, in order of preference:
23+
*
24+
* * **Deno** — native wasm-module import: stable, permission-free, and
25+
* delivery/caching ride the module cache. The shim imports nothing,
26+
* so the ESM integration instantiates it trivially and
27+
* `Translator.fromExports` wraps the namespace with no compile and no
28+
* copy. (`buildHash` is unrecoverable from an instance, so the
29+
* artifact cache keys without translator identity on this path —
30+
* see Translator.buildHash.)
31+
* * **Node** — `node:fs` read of the packaged asset (Node's wasm-module
32+
* imports are still experimental; don't build on them).
33+
* * **Browser / workers** — `fetch` of the packaged asset URL (bundlers
34+
* understand the `new URL(…, import.meta.url)` pattern and carry the
35+
* asset).
36+
*
37+
* Pass the result to `instantiate({ componentBytes, translator })`
38+
* (embedder-api A3), or call `.translate()` directly.
39+
*/
40+
export function defaultTranslator(): Promise<Translator> {
41+
return singleton ??= load();
42+
}
43+
44+
async function load(): Promise<Translator> {
45+
const url = new URL("./translator_shim.wasm", import.meta.url);
46+
try {
47+
if (typeof Deno !== "undefined") {
48+
// String-literal dynamic import: statically analyzable, so the wasm
49+
// rides the module graph permission-free; still lazy, and non-Deno
50+
// platforms never evaluate the Deno-only module (see its header).
51+
const { ns } = await import("./shim_asset_deno.ts");
52+
return Translator.fromExports(ns);
53+
}
54+
const proc = (globalThis as { process?: { versions?: { node?: string } } })
55+
.process;
56+
if (proc?.versions?.node) {
57+
const { readFile } = await import("node:fs/promises");
58+
return await Translator.create(new Uint8Array(await readFile(url)));
59+
}
60+
const res = await fetch(url);
61+
if (!res.ok) {
62+
throw new Error(`fetching the translator asset failed: ${res.status}`);
63+
}
64+
return await Translator.create(new Uint8Array(await res.arrayBuffer()));
65+
} catch (e) {
66+
// The overwhelmingly likely in-repo cause is the missing gitignored
67+
// asset; say so instead of leaking a bare module-resolution error.
68+
throw new Error(
69+
`@deltic/translator: could not load ${url}: ${e}\n` +
70+
`(in a repo checkout, run \`just shim\` to build and place the asset)`,
71+
{ cause: e },
72+
);
73+
}
74+
}

0 commit comments

Comments
 (0)