@statewalker/cellb 0.3.0 → 0.5.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/README.md +46 -3
- package/bin/cellb.mjs +20 -0
- package/dist/browser.d.ts +2 -0
- package/dist/browser.js +1 -0
- package/dist/browser.js.map +1 -1
- package/dist/bucket/celld-cells.js +25 -3
- package/dist/bucket/celld-cells.js.map +1 -1
- package/dist/cli.d.ts +3 -0
- package/dist/cli.js +62 -0
- package/dist/cli.js.map +1 -0
- package/dist/isolate/browser.d.ts +2 -1
- package/dist/isolate/browser.js +2 -1
- package/dist/isolate/browser.js.map +1 -1
- package/dist/isolate/isolate.js +5 -3
- package/dist/isolate/isolate.js.map +1 -1
- package/dist/isolate/protocol.d.ts +4 -2
- package/dist/isolate/protocol.js +4 -4
- package/dist/isolate/protocol.js.map +1 -1
- package/dist/node.d.ts +3 -0
- package/dist/node.js +2 -0
- package/dist/node.js.map +1 -1
- package/dist/project/browser-host.d.ts +6 -12
- package/dist/project/browser-host.js +2 -25
- package/dist/project/browser-host.js.map +1 -1
- package/dist/project/browser-load.d.ts +22 -0
- package/dist/project/browser-load.js +63 -0
- package/dist/project/browser-load.js.map +1 -0
- package/dist/project/browser-shards.d.ts +8 -9
- package/dist/project/browser-shards.js +88 -52
- package/dist/project/browser-shards.js.map +1 -1
- package/dist/project/browser-tabs.d.ts +14 -4
- package/dist/project/browser-tabs.js +193 -208
- package/dist/project/browser-tabs.js.map +1 -1
- package/dist/project/build.d.ts +14 -0
- package/dist/project/build.js +53 -0
- package/dist/project/build.js.map +1 -0
- package/dist/project/built-project.d.ts +88 -0
- package/dist/project/built-project.js +118 -0
- package/dist/project/built-project.js.map +1 -0
- package/dist/project/node-load.d.ts +14 -0
- package/dist/project/node-load.js +187 -0
- package/dist/project/node-load.js.map +1 -0
- package/dist/project/wrangler.d.ts +9 -0
- package/dist/project/wrangler.js +24 -0
- package/dist/project/wrangler.js.map +1 -1
- package/dist/transport/cells-http.d.ts +6 -0
- package/dist/transport/cells-http.js +22 -1
- package/dist/transport/cells-http.js.map +1 -1
- package/dist/transport/cells-link.d.ts +2 -0
- package/dist/transport/cells-link.js +5 -0
- package/dist/transport/cells-link.js.map +1 -1
- package/dist/transport/port.js +4 -0
- package/dist/transport/port.js.map +1 -1
- package/dist/ws/server.d.ts +39 -23
- package/dist/ws/server.js +99 -140
- package/dist/ws/server.js.map +1 -1
- package/package.json +9 -4
package/README.md
CHANGED
|
@@ -24,7 +24,7 @@ mix celld nodes, cellb nodes and browsers.
|
|
|
24
24
|
|
|
25
25
|
```sh
|
|
26
26
|
npm install @statewalker/cellb
|
|
27
|
-
npm install esbuild # for
|
|
27
|
+
npm install esbuild # for loadProject and cellb build on Node (an optional peer dependency)
|
|
28
28
|
```
|
|
29
29
|
|
|
30
30
|
The package has three entry points, one per platform, and two more for bundling. Published, it is
|
|
@@ -37,8 +37,8 @@ worker is started that way (checked with a Vite build, see `scripts/pack-check.m
|
|
|
37
37
|
| Import | What it gives |
|
|
38
38
|
|---|---|
|
|
39
39
|
| `@statewalker/cellb` | the runtime (`createCellRuntime`, `createProjectRuntime`), `DurableObject` and `WorkerEntrypoint`, the ports and `CellStorage`, `celldBucket`, the object stores (`s3ObjectStore`, `memoryObjectStore`, `filesApiObjectStore`), `filesApiLtxLog`, `filesApiChangeLog`, `filesApiBackup`, `wasmMemoryOpener` (in-memory SQLite WASM), links over ports, `CellRouter`, `createShardWorker`, the WebSocket classes, and the errors embedders catch |
|
|
40
|
-
| `@statewalker/cellb/node` | `nodeSqliteOpener`, `sqliteObjectStore` (celld dev's local bucket), `loadNodeProject`, `applyNodeD1Migrations`, and celld's peer protocol (`startCelldPeerServer`, `celldPeers`) |
|
|
41
|
-
| `@statewalker/cellb/browser` | `browserProjectRuntime`, `startProjectShards`, `joinProjectShards` and `serveProjectShard` (shards in workers and tabs), `installProjectRouter` (the ServiceWorker side), `sahpoolOpener`, `idbObjectStore`, `serviceWorkerConnector` |
|
|
40
|
+
| `@statewalker/cellb/node` | `loadProject` and `buildProject` (the `cellb build` command), `nodeSqliteOpener`, `sqliteObjectStore` (celld dev's local bucket), `loadNodeProject`, `applyNodeD1Migrations`, and celld's peer protocol (`startCelldPeerServer`, `celldPeers`) |
|
|
41
|
+
| `@statewalker/cellb/browser` | `loadProject` (a `cellb build` folder by URL), `browserProjectRuntime`, `startProjectShards`, `joinProjectShards` and `serveProjectShard` (shards in workers and tabs), `installProjectRouter` (the ServiceWorker side), `sahpoolOpener`, `idbObjectStore`, `serviceWorkerConnector` |
|
|
42
42
|
| `@statewalker/cellb/site` | `createProjectSite`: a project's sources compiled in the browser and served by an in-browser HTTP server (`browserProjectRuntime({ site })`). Needs the optional peer dependencies `@statewalker/webrun-modules` and `@statewalker/webrun-site-builder`; the site is hosted in a ServiceWorker with `@statewalker/webrun-site-host`, also an optional peer |
|
|
43
43
|
| `@statewalker/cellb/host-modules` | for bundlers other than cellb's own (webrun-bundler in a browser, say): the sources of `cloudflare:workers`, `node:*` and the Workers globals as cellb's realms provide them (`cellbHostModule`, `CELLS_GLOBALS_MODULE`) |
|
|
44
44
|
| `@statewalker/cellb/isolate-worker` | the Dedicated Worker that runs a Worker-Loader Worker in a realm of its own (`isolateScript`) |
|
|
@@ -56,6 +56,49 @@ runtime answers requests with 503, and an object's code still running fails with
|
|
|
56
56
|
|
|
57
57
|
## Examples
|
|
58
58
|
|
|
59
|
+
### One project loader for Node and browsers
|
|
60
|
+
|
|
61
|
+
`loadProject(source, options)` has the same name and options in both entry points:
|
|
62
|
+
|
|
63
|
+
```ts
|
|
64
|
+
// Node: a folder with wrangler.json(c), its `main` bundled with esbuild
|
|
65
|
+
import { loadProject } from "@statewalker/cellb/node";
|
|
66
|
+
const project = await loadProject("./counter", { data: "./data" });
|
|
67
|
+
|
|
68
|
+
// Browser, in a Dedicated Worker: the URL of a folder written by `cellb build`
|
|
69
|
+
import { loadProject } from "@statewalker/cellb/browser";
|
|
70
|
+
const project = await loadProject("/apps/counter/", { data: "counter" });
|
|
71
|
+
|
|
72
|
+
// Both
|
|
73
|
+
const res = await project.fetch(new Request("http://localhost/increment?name=alpha"));
|
|
74
|
+
project.unsupported; // configuration cellb does not run
|
|
75
|
+
await project.close();
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
A browser loads a project built ahead of time, on Node:
|
|
79
|
+
|
|
80
|
+
```sh
|
|
81
|
+
npx cellb build ./counter --out public/apps/counter
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
The folder holds `wrangler.json`, `worker.js` (`main` bundled for browsers, with nodejs_compat
|
|
85
|
+
when the flags ask for it), `assets/` (the files of `assets.directory`) and `cellb.json`, the
|
|
86
|
+
manifest that lists the assets, since a static host cannot list a folder. Any static host serves
|
|
87
|
+
it; Node loads it too.
|
|
88
|
+
|
|
89
|
+
- `source`: Node takes a folder (built or not), a `file:` or `http(s):` URL of a built folder, or
|
|
90
|
+
a FilesApi holding either; a browser takes the URL of a built folder or a FilesApi holding one.
|
|
91
|
+
- `data`: where cells keep their data. On Node, a directory (SQLite files, and the project's files
|
|
92
|
+
under `<data>/files`); without it, memory. In a browser, an OPFS name: the SQLite pool `<data>`
|
|
93
|
+
and the files directory `<data>-files` (default: the project's `name`).
|
|
94
|
+
- `bucket`: a fleet bucket, as `loadNodeProject`'s `storage` (a `CellStorage`) and
|
|
95
|
+
`browserProjectRuntime`'s `bucket` (a `BrowserBucket`).
|
|
96
|
+
- `ai`: the Workers AI endpoint. `sqlite3` (browser): a SQLite WASM module, if the caller has one
|
|
97
|
+
(default: `@sqlite.org/sqlite-wasm`). `isolateScript` (browser): where Worker Loader code runs,
|
|
98
|
+
the package's own isolate worker by default, `false` for this realm.
|
|
99
|
+
|
|
100
|
+
`loadNodeProject` and `browserProjectRuntime`, below, stay as the lower-level calls.
|
|
101
|
+
|
|
59
102
|
### A Worker and a Durable Object in Node
|
|
60
103
|
|
|
61
104
|
```ts
|
package/bin/cellb.mjs
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// The `cellb` command: the compiled CLI of the published package (dist/), or the TypeScript
|
|
3
|
+
// sources in the repository (Node strips their types; `.js` specifiers resolve to `.ts`).
|
|
4
|
+
import { existsSync } from "node:fs";
|
|
5
|
+
import { register } from "node:module";
|
|
6
|
+
|
|
7
|
+
const dist = new URL("../dist/cli.js", import.meta.url);
|
|
8
|
+
let entry = dist;
|
|
9
|
+
if (!existsSync(dist)) {
|
|
10
|
+
const hook = `export async function resolve(s, c, next) {
|
|
11
|
+
try { return await next(s, c); } catch (e) {
|
|
12
|
+
if (s.startsWith(".") && s.endsWith(".js")) return next(s.slice(0, -3) + ".ts", c);
|
|
13
|
+
throw e;
|
|
14
|
+
}
|
|
15
|
+
}`;
|
|
16
|
+
register(`data:text/javascript,${encodeURIComponent(hook)}`);
|
|
17
|
+
entry = new URL("../src/cli.ts", import.meta.url);
|
|
18
|
+
}
|
|
19
|
+
const { main } = await import(entry.href);
|
|
20
|
+
process.exitCode = await main(process.argv.slice(2));
|
package/dist/browser.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
export { sahpoolOpener } from "./adapters/wasm-sqlite.js";
|
|
2
2
|
export { idbObjectStore } from "./bucket/idb-object-store.js";
|
|
3
3
|
export { type BrowserBucket, type BrowserProjectOptions, browserProjectRuntime, } from "./project/browser-host.js";
|
|
4
|
+
export { type BrowserLoadProjectOptions, loadProject } from "./project/browser-load.js";
|
|
4
5
|
export { type ProjectShards, serveProjectShard, startProjectShards, } from "./project/browser-shards.js";
|
|
5
6
|
export { installProjectRouter, joinProjectShards, type ProjectTabs, type ProjectTabsOptions, type ServiceWorkerScope, shardOwner, } from "./project/browser-tabs.js";
|
|
7
|
+
export type { LoadedProject, LoadProjectOptions, ProjectSource, } from "./project/built-project.js";
|
|
6
8
|
export { serviceWorkerConnector } from "./ws/sw-connector.js";
|
package/dist/browser.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export { sahpoolOpener } from "./adapters/wasm-sqlite.js";
|
|
2
2
|
export { idbObjectStore } from "./bucket/idb-object-store.js";
|
|
3
3
|
export { browserProjectRuntime, } from "./project/browser-host.js";
|
|
4
|
+
export { loadProject } from "./project/browser-load.js";
|
|
4
5
|
export { serveProjectShard, startProjectShards, } from "./project/browser-shards.js";
|
|
5
6
|
export { installProjectRouter, joinProjectShards, shardOwner, } from "./project/browser-tabs.js";
|
|
6
7
|
export { serviceWorkerConnector } from "./ws/sw-connector.js";
|
package/dist/browser.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"browser.js","sourceRoot":"","sources":["../src/browser.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAC1D,OAAO,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAC9D,OAAO,EAGL,qBAAqB,GACtB,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAEL,iBAAiB,EACjB,kBAAkB,GACnB,MAAM,6BAA6B,CAAC;AACrC,OAAO,EACL,oBAAoB,EACpB,iBAAiB,EAIjB,UAAU,GACX,MAAM,2BAA2B,CAAC;
|
|
1
|
+
{"version":3,"file":"browser.js","sourceRoot":"","sources":["../src/browser.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAC1D,OAAO,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAC9D,OAAO,EAGL,qBAAqB,GACtB,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAAkC,WAAW,EAAE,MAAM,2BAA2B,CAAC;AACxF,OAAO,EAEL,iBAAiB,EACjB,kBAAkB,GACnB,MAAM,6BAA6B,CAAC;AACrC,OAAO,EACL,oBAAoB,EACpB,iBAAiB,EAIjB,UAAU,GACX,MAAM,2BAA2B,CAAC;AAMnC,OAAO,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC","sourcesContent":["export { sahpoolOpener } from \"./adapters/wasm-sqlite.js\";\nexport { idbObjectStore } from \"./bucket/idb-object-store.js\";\nexport {\n type BrowserBucket,\n type BrowserProjectOptions,\n browserProjectRuntime,\n} from \"./project/browser-host.js\";\nexport { type BrowserLoadProjectOptions, loadProject } from \"./project/browser-load.js\";\nexport {\n type ProjectShards,\n serveProjectShard,\n startProjectShards,\n} from \"./project/browser-shards.js\";\nexport {\n installProjectRouter,\n joinProjectShards,\n type ProjectTabs,\n type ProjectTabsOptions,\n type ServiceWorkerScope,\n shardOwner,\n} from \"./project/browser-tabs.js\";\nexport type {\n LoadedProject,\n LoadProjectOptions,\n ProjectSource,\n} from \"./project/built-project.js\";\nexport { serviceWorkerConnector } from \"./ws/sw-connector.js\";\n"]}
|
|
@@ -17,6 +17,9 @@ export function celldBucket(store, opts) {
|
|
|
17
17
|
let renew;
|
|
18
18
|
let deadline;
|
|
19
19
|
let leaseWritten;
|
|
20
|
+
/** The lease write on its way to the bucket, if any: `close()` waits for it before the delete. */
|
|
21
|
+
let writing = Promise.resolve();
|
|
22
|
+
let closed = false;
|
|
20
23
|
/** nodes/<node>.json, renewed every ttl/3; a failed renewal stops every cell (self-fencing). */
|
|
21
24
|
// celld decodes every node record (ownership_store.rs NodeLeaseWire / NodeLoadWire): a record
|
|
22
25
|
// missing a required load field breaks its fleet posture repair. This one is complete. It
|
|
@@ -47,7 +50,15 @@ export function celldBucket(store, opts) {
|
|
|
47
50
|
}
|
|
48
51
|
})();
|
|
49
52
|
let validUntil = 0;
|
|
50
|
-
const writeLease =
|
|
53
|
+
const writeLease = () => {
|
|
54
|
+
const w = putLease();
|
|
55
|
+
// Every write in flight, not only the latest: they may land in any order.
|
|
56
|
+
writing = Promise.all([writing, w.catch(() => { })]);
|
|
57
|
+
return w;
|
|
58
|
+
};
|
|
59
|
+
const putLease = async () => {
|
|
60
|
+
if (closed)
|
|
61
|
+
return;
|
|
51
62
|
const t = now();
|
|
52
63
|
const body = {
|
|
53
64
|
node: opts.node,
|
|
@@ -81,7 +92,8 @@ export function celldBucket(store, opts) {
|
|
|
81
92
|
// Our authority lasts until the record we just wrote expires — minus a margin, so a node
|
|
82
93
|
// that stalls (a paused process, a frozen tab) stops acknowledging before celld may take
|
|
83
94
|
// over: celld waits for expires_ms; we stop at expires_ms - margin.
|
|
84
|
-
|
|
95
|
+
if (!closed)
|
|
96
|
+
validUntil = t + ttl - Math.min(1_000, ttl / 4);
|
|
85
97
|
};
|
|
86
98
|
const owned = new Set();
|
|
87
99
|
/** Cells whose ownership record was found lost, with the time their refusal ends. */
|
|
@@ -89,6 +101,8 @@ export function celldBucket(store, opts) {
|
|
|
89
101
|
const ensureLease = () => {
|
|
90
102
|
leaseWritten ??= probeKeyReady.then(writeLease).then(() => {
|
|
91
103
|
const tick = () => {
|
|
104
|
+
if (closed)
|
|
105
|
+
return;
|
|
92
106
|
renew = setTimeout(() => {
|
|
93
107
|
writeLease().then(tick, (e) => lost.abort(e));
|
|
94
108
|
}, ttl / 3);
|
|
@@ -113,7 +127,7 @@ export function celldBucket(store, opts) {
|
|
|
113
127
|
};
|
|
114
128
|
// One LTX writer (ltx-log.ts) holds each cell's epoch, TXID and page mirror.
|
|
115
129
|
// A write is acknowledged only while our node lease is still ours (self-fencing).
|
|
116
|
-
const log = ltxLog(store, { now, authorized: () => now() < validUntil });
|
|
130
|
+
const log = ltxLog(store, { now, authorized: () => !closed && now() < validUntil });
|
|
117
131
|
const ownership = {
|
|
118
132
|
async tryAcquire(cellKey) {
|
|
119
133
|
await ensureLease();
|
|
@@ -260,10 +274,18 @@ export function celldBucket(store, opts) {
|
|
|
260
274
|
},
|
|
261
275
|
/** Clean shutdown: stop renewing, remove this node's lease record, close the store. */
|
|
262
276
|
async close() {
|
|
277
|
+
closed = true;
|
|
263
278
|
if (renew)
|
|
264
279
|
clearTimeout(renew);
|
|
265
280
|
if (deadline)
|
|
266
281
|
clearTimeout(deadline);
|
|
282
|
+
validUntil = 0; // no write is acknowledged once closing has begun
|
|
283
|
+
// A renewal already on its way would write the record back after the delete, and the
|
|
284
|
+
// node's cells would stay "owned by a live node" for a whole lease: let it land first —
|
|
285
|
+
// for at most a lease, since one that lands later expires within a lease anyway.
|
|
286
|
+
let waited;
|
|
287
|
+
await Promise.race([writing, new Promise((r) => (waited = setTimeout(r, ttl)))]);
|
|
288
|
+
clearTimeout(waited);
|
|
267
289
|
if (leaseWritten)
|
|
268
290
|
await store.delete(`nodes/${opts.node}.json`).catch(() => { });
|
|
269
291
|
store.close?.();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"celld-cells.js","sourceRoot":"","sources":["../../src/bucket/celld-cells.ts"],"names":[],"mappings":"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","sourcesContent":["import { bytesToHex } from \"@noble/hashes/utils.js\";\nimport { scopeOf } from \"../core/celld-schema.js\";\nimport { decodeLtx, encodeLtx, ltxFileName } from \"../ltx/ltx.js\";\nimport type { BackupInfo, CellBackup, CellDurability } from \"../ports.js\";\nimport { equal, pad16, pageSizeOf, toRollback } from \"./celld-format.js\";\nimport { ltxLog } from \"./ltx-log.js\";\nimport { type ObjectStore, PreconditionFailedError } from \"./object-store.js\";\n\n/**\n * Cells stored the way celld stores them (docs/guarantees.md, and the keys celld dev writes\n * in `.celld/dev/objects.sqlite3`), on any ObjectStore:\n *\n * cells/<Class>:<idHex>/own.json {\"node\",\"epoch\"}: ownership record\n * cells/<Class>:<idHex>/ltx/e<epoch>/0000/<min>-<max>.ltx LTX v3, legacy LZ4 frames\n * nodes/<node>.json {\"node\",\"expires_ms\",\"addr\",…}: node lease\n *\n * Ownership: an activation creates the record (If-None-Match) or advances its epoch with a\n * compare-and-swap (If-Match), taking over only from a node whose lease expired. Durability:\n * each committed transaction becomes an LTX file in the cell's current epoch prefix — the\n * first one a snapshot (TXID 1), then the changed pages — and the output gate waits for the\n * PUT. Restore: the newest epoch that starts with a snapshot, its files in TXID order.\n */\nexport interface CelldBucketOptions {\n /** This node's name (celld: `dev-…` / the fleet node name). */\n node: string;\n addr?: string;\n leaseTtlMs?: number;\n now?: () => number;\n /** The waker-role lease (celld: 3 × its due-scan tick); match 3 × the runtime's wakePollMs. */\n wakerTtlMs?: number;\n}\n\nconst enc = new TextEncoder();\nconst dec = new TextDecoder();\n\n/** celld 0.6.3: how long a node refuses a cell whose ownership record it found lost. */\nexport const LOST_RECORD_RECHECK_MS = 10_000;\nconst lostRecordError = (scope: string) =>\n Object.assign(\n new Error(\n `ownership record lost: cells/${scope}/own.json is missing, but the bucket holds LTX under ` +\n `cells/${scope}/ltx/; refusing to claim ${scope} as a new cell (celld docs/guarantees.md: ` +\n `write a released record {\"node\": \"\", \"epoch\": <newest epoch>} to restore it)`,\n ),\n { name: \"OwnershipRecordLostError\" },\n );\n\n/** The bucket's `close()` also closes `store` (when it has a `close`): pass a store of its own. */\nexport function celldBucket(store: ObjectStore & { close?(): void }, opts: CelldBucketOptions) {\n const now = opts.now ?? (() => Date.now());\n const ttl = opts.leaseTtlMs ?? 10_000;\n const lost = new AbortController();\n let renew: ReturnType<typeof setTimeout> | undefined;\n let deadline: ReturnType<typeof setTimeout> | undefined;\n let leaseWritten: Promise<void> | undefined;\n\n /** nodes/<node>.json, renewed every ttl/3; a failed renewal stops every cell (self-fencing). */\n // celld decodes every node record (ownership_store.rs NodeLeaseWire / NodeLoadWire): a record\n // missing a required load field breaks its fleet posture repair. This one is complete. It\n // reports pressure and zero placement weight, so celld never offers this node a cell — it\n // takes cells only when a request arrives.\n //\n // The address must not be empty: celld treats a live owner WITHOUT an address as dead and\n // takes its cells over (logic/lib.rs apply_node_lease_result) — a split brain. With an\n // address celld routes a request for our cell to it; a node that does not accept celld's\n // peer protocol (a browser) gets an unroutable placeholder, so the request fails instead.\n let addr = opts.addr || `${opts.node}.cellb.invalid:9`;\n // celld's per-process generation is its signed-probe public key (Ed25519, peer_probe.rs):\n // `probe_public_key` = `ownership_index_generation`. A real key lets `celld diagnose --peer`\n // verify that a probe reached this node.\n let generation = bytesToHex(crypto.getRandomValues(new Uint8Array(32)));\n let probeKey: CryptoKey | undefined;\n const probeKeyReady = (async () => {\n try {\n const pair = (await crypto.subtle.generateKey({ name: \"Ed25519\" }, true, [\n \"sign\",\n \"verify\",\n ])) as CryptoKeyPair;\n probeKey = pair.privateKey;\n generation = bytesToHex(new Uint8Array(await crypto.subtle.exportKey(\"raw\", pair.publicKey)));\n } catch {\n // no Ed25519 in this WebCrypto: the record keeps a random generation (probes then fail)\n }\n })();\n let validUntil = 0;\n const writeLease = async () => {\n const t = now();\n const body = {\n node: opts.node,\n expires_ms: t + ttl,\n addr,\n probe_public_key: generation,\n peer_protocol: 5,\n paced_handoff: false,\n ownership_index_generation: generation,\n load: {\n sampled_ms: t,\n owned_cells: owned.size,\n placement_weight: 0,\n bucket_format: 2,\n resident_cells: owned.size,\n host_websockets: 0,\n rss_bytes: 0,\n in_use_bytes: 0,\n cpu_percent_x100: 0,\n open_fds: 0,\n fd_limit: 0,\n pressured: true,\n memory_headroom: false,\n shed_cells: 0,\n restoring: 0,\n rebalance_paused: false,\n draining: false,\n },\n };\n await store.put(`nodes/${opts.node}.json`, enc.encode(JSON.stringify(body)));\n // Our authority lasts until the record we just wrote expires — minus a margin, so a node\n // that stalls (a paused process, a frozen tab) stops acknowledging before celld may take\n // over: celld waits for expires_ms; we stop at expires_ms - margin.\n validUntil = t + ttl - Math.min(1_000, ttl / 4);\n };\n const owned = new Set<string>();\n /** Cells whose ownership record was found lost, with the time their refusal ends. */\n const lostRecords = new Map<string, number>();\n const ensureLease = () => {\n leaseWritten ??= probeKeyReady.then(writeLease).then(() => {\n const tick = () => {\n renew = setTimeout(() => {\n writeLease().then(tick, (e) => lost.abort(e));\n }, ttl / 3);\n // If renewals stop arriving (a stalled process, an unreachable bucket), every cell\n // stops at the local deadline — before celld may take it over.\n if (deadline) clearTimeout(deadline);\n deadline = setTimeout(\n () => {\n if (now() >= validUntil)\n lost.abort(new Error(`node ${opts.node}: lease deadline passed`));\n },\n Math.max(0, validUntil - now()),\n );\n (deadline as { unref?: () => void }).unref?.();\n (renew as { unref?: () => void }).unref?.();\n };\n tick();\n });\n return leaseWritten;\n };\n\n const readJson = async <T>(key: string) => {\n const o = await store.get(key);\n return o ? { value: JSON.parse(dec.decode(o.body)) as T, etag: o.etag } : undefined;\n };\n\n // One LTX writer (ltx-log.ts) holds each cell's epoch, TXID and page mirror.\n // A write is acknowledged only while our node lease is still ours (self-fencing).\n const log = ltxLog(store, { now, authorized: () => now() < validUntil });\n\n const ownership = {\n async tryAcquire(cellKey: string) {\n await ensureLease();\n const scope = scopeOf(cellKey);\n const key = `cells/${scope}/own.json`;\n const t0 = now();\n if ((lostRecords.get(scope) ?? 0) > t0) throw lostRecordError(scope); // still refused\n let cur = await readJson<{ node: string; epoch: number }>(key);\n // celld 0.6.3 (actor.rs read_owner_for_claim): a missing record next to LTX means the\n // record was LOST (e.g. a lifecycle rule) — claiming epoch 1 would start the cell empty.\n // Read once more, then refuse the cell for 10 s without asking the bucket again.\n if (!cur && (await store.list(`cells/${scope}/ltx`)).length > 0) {\n cur = await readJson<{ node: string; epoch: number }>(key);\n if (!cur) {\n lostRecords.set(scope, t0 + LOST_RECORD_RECHECK_MS);\n throw lostRecordError(scope);\n }\n }\n let epoch: number;\n try {\n if (!cur) {\n epoch = 1;\n await store.put(key, enc.encode(JSON.stringify({ node: opts.node, epoch })), {\n ifNoneMatch: \"*\",\n });\n } else {\n const holder = cur.value.node;\n if (holder && holder !== opts.node) {\n const lease = await readJson<{ expires_ms: number }>(`nodes/${holder}.json`);\n if (lease && lease.value.expires_ms > now()) return undefined; // a live owner\n }\n epoch = cur.value.epoch + 1; // every activation advances the epoch\n await store.put(key, enc.encode(JSON.stringify({ node: opts.node, epoch })), {\n ifMatch: cur.etag,\n });\n }\n } catch (e) {\n if (e instanceof PreconditionFailedError) return undefined; // another node won the race\n throw e;\n }\n log.begin(cellKey, epoch); // a new owner epoch starts a new generation\n owned.add(cellKey);\n return {\n name: cellKey,\n epoch,\n lost: lost.signal,\n release: async () => {\n const now2 = await readJson<{ node: string; epoch: number }>(key);\n if (now2?.value.node === opts.node && now2.value.epoch === epoch)\n await store\n .put(key, enc.encode(JSON.stringify({ node: \"\", epoch })), { ifMatch: now2.etag })\n .catch(() => {});\n log.forget(cellKey);\n owned.delete(cellKey);\n },\n };\n },\n };\n\n // celld's waker role (wake.rs try_hold_waker): one node per fleet wakes alarms of cells that\n // nobody owns or whose owner is dead — an advisory CAS lease in wake/waker.json; every node\n // wakes its own cells; a live owner's cells are never hinted.\n const holdWaker = async (ttlMs: number) => {\n const key = \"wake/waker.json\";\n const cur = await readJson<{ node: string; expires_ms: number }>(key);\n const t = now();\n if (cur && cur.value.node !== opts.node && cur.value.expires_ms > t) return false;\n try {\n await store.put(\n key,\n enc.encode(JSON.stringify({ node: opts.node, expires_ms: t + ttlMs })),\n cur ? { ifMatch: cur.etag } : { ifNoneMatch: \"*\" },\n );\n return true;\n } catch (e) {\n if (!(e instanceof PreconditionFailedError)) return false;\n const again = await readJson<{ node: string; expires_ms: number }>(key);\n return again?.value.node === opts.node && again.value.expires_ms > t;\n }\n };\n const allWakes = log.wakes!.bind(log);\n const durability = Object.assign(log, {\n async wakes() {\n const due = await allWakes();\n let elected: boolean | undefined;\n const out: typeof due = [];\n for (const w of due) {\n const own = await readJson<{ node: string }>(`cells/${scopeOf(w.cellKey)}/own.json`);\n const owner = own?.value.node;\n if (owner === opts.node) {\n out.push(w); // our own cell: always\n continue;\n }\n let ownerExpires = 0;\n if (owner) {\n const lease = await readJson<{ expires_ms: number }>(`nodes/${owner}.json`);\n ownerExpires = lease?.value.expires_ms ?? 0;\n }\n elected ??= await holdWaker(opts.wakerTtlMs ?? 90_000);\n if (!elected) continue; // unowned or dead owners are the elected waker's job\n // A live owner fires its own alarms; the waker looks again when that owner's lease\n // would have run out (a stopped owner's cells are then due here).\n out.push(ownerExpires > now() ? { ...w, at: Math.max(w.at, ownerExpires + 1) } : w);\n }\n return out;\n },\n });\n\n return {\n ownership,\n durability,\n backup: durability,\n /** The fleet bucket itself (R2 objects live directly in it, under `r2/<bucket>/`). */\n objects: store,\n /**\n * The address celld nodes reach this node at (its celld peer server): published in the\n * node record now, so celld routes requests for our cells here instead of refusing them.\n */\n /**\n * Answers celld's signed peer probe (peer_probe.rs): an Ed25519 signature over\n * \"cells-peer-probe-v1\\n<node>\\n<advertised addr>\\n<challenge>\" by this node's probe key.\n */\n async probe(challenge: string) {\n await probeKeyReady;\n if (!probeKey || !/^[0-9a-f]{64}$/.test(challenge))\n throw new Error(\"cannot answer the probe\");\n const payload = `cells-peer-probe-v1\\n${opts.node}\\n${addr}\\n${challenge}`;\n const signature = new Uint8Array(\n await crypto.subtle.sign(\"Ed25519\", probeKey, new TextEncoder().encode(payload)),\n );\n return {\n version: 1,\n node: opts.node,\n advertise: addr,\n challenge,\n signature: bytesToHex(signature),\n };\n },\n async advertise(address: string) {\n addr = address;\n if (leaseWritten) await writeLease();\n },\n /** Clean shutdown: stop renewing, remove this node's lease record, close the store. */\n async close() {\n if (renew) clearTimeout(renew);\n if (deadline) clearTimeout(deadline);\n if (leaseWritten) await store.delete(`nodes/${opts.node}.json`).catch(() => {});\n store.close?.();\n },\n };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"celld-cells.js","sourceRoot":"","sources":["../../src/bucket/celld-cells.ts"],"names":[],"mappings":"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","sourcesContent":["import { bytesToHex } from \"@noble/hashes/utils.js\";\nimport { scopeOf } from \"../core/celld-schema.js\";\nimport { decodeLtx, encodeLtx, ltxFileName } from \"../ltx/ltx.js\";\nimport type { BackupInfo, CellBackup, CellDurability } from \"../ports.js\";\nimport { equal, pad16, pageSizeOf, toRollback } from \"./celld-format.js\";\nimport { ltxLog } from \"./ltx-log.js\";\nimport { type ObjectStore, PreconditionFailedError } from \"./object-store.js\";\n\n/**\n * Cells stored the way celld stores them (docs/guarantees.md, and the keys celld dev writes\n * in `.celld/dev/objects.sqlite3`), on any ObjectStore:\n *\n * cells/<Class>:<idHex>/own.json {\"node\",\"epoch\"}: ownership record\n * cells/<Class>:<idHex>/ltx/e<epoch>/0000/<min>-<max>.ltx LTX v3, legacy LZ4 frames\n * nodes/<node>.json {\"node\",\"expires_ms\",\"addr\",…}: node lease\n *\n * Ownership: an activation creates the record (If-None-Match) or advances its epoch with a\n * compare-and-swap (If-Match), taking over only from a node whose lease expired. Durability:\n * each committed transaction becomes an LTX file in the cell's current epoch prefix — the\n * first one a snapshot (TXID 1), then the changed pages — and the output gate waits for the\n * PUT. Restore: the newest epoch that starts with a snapshot, its files in TXID order.\n */\nexport interface CelldBucketOptions {\n /** This node's name (celld: `dev-…` / the fleet node name). */\n node: string;\n addr?: string;\n leaseTtlMs?: number;\n now?: () => number;\n /** The waker-role lease (celld: 3 × its due-scan tick); match 3 × the runtime's wakePollMs. */\n wakerTtlMs?: number;\n}\n\nconst enc = new TextEncoder();\nconst dec = new TextDecoder();\n\n/** celld 0.6.3: how long a node refuses a cell whose ownership record it found lost. */\nexport const LOST_RECORD_RECHECK_MS = 10_000;\nconst lostRecordError = (scope: string) =>\n Object.assign(\n new Error(\n `ownership record lost: cells/${scope}/own.json is missing, but the bucket holds LTX under ` +\n `cells/${scope}/ltx/; refusing to claim ${scope} as a new cell (celld docs/guarantees.md: ` +\n `write a released record {\"node\": \"\", \"epoch\": <newest epoch>} to restore it)`,\n ),\n { name: \"OwnershipRecordLostError\" },\n );\n\n/** The bucket's `close()` also closes `store` (when it has a `close`): pass a store of its own. */\nexport function celldBucket(store: ObjectStore & { close?(): void }, opts: CelldBucketOptions) {\n const now = opts.now ?? (() => Date.now());\n const ttl = opts.leaseTtlMs ?? 10_000;\n const lost = new AbortController();\n let renew: ReturnType<typeof setTimeout> | undefined;\n let deadline: ReturnType<typeof setTimeout> | undefined;\n let leaseWritten: Promise<void> | undefined;\n /** The lease write on its way to the bucket, if any: `close()` waits for it before the delete. */\n let writing: Promise<unknown> = Promise.resolve();\n let closed = false;\n\n /** nodes/<node>.json, renewed every ttl/3; a failed renewal stops every cell (self-fencing). */\n // celld decodes every node record (ownership_store.rs NodeLeaseWire / NodeLoadWire): a record\n // missing a required load field breaks its fleet posture repair. This one is complete. It\n // reports pressure and zero placement weight, so celld never offers this node a cell — it\n // takes cells only when a request arrives.\n //\n // The address must not be empty: celld treats a live owner WITHOUT an address as dead and\n // takes its cells over (logic/lib.rs apply_node_lease_result) — a split brain. With an\n // address celld routes a request for our cell to it; a node that does not accept celld's\n // peer protocol (a browser) gets an unroutable placeholder, so the request fails instead.\n let addr = opts.addr || `${opts.node}.cellb.invalid:9`;\n // celld's per-process generation is its signed-probe public key (Ed25519, peer_probe.rs):\n // `probe_public_key` = `ownership_index_generation`. A real key lets `celld diagnose --peer`\n // verify that a probe reached this node.\n let generation = bytesToHex(crypto.getRandomValues(new Uint8Array(32)));\n let probeKey: CryptoKey | undefined;\n const probeKeyReady = (async () => {\n try {\n const pair = (await crypto.subtle.generateKey({ name: \"Ed25519\" }, true, [\n \"sign\",\n \"verify\",\n ])) as CryptoKeyPair;\n probeKey = pair.privateKey;\n generation = bytesToHex(new Uint8Array(await crypto.subtle.exportKey(\"raw\", pair.publicKey)));\n } catch {\n // no Ed25519 in this WebCrypto: the record keeps a random generation (probes then fail)\n }\n })();\n let validUntil = 0;\n const writeLease = () => {\n const w = putLease();\n // Every write in flight, not only the latest: they may land in any order.\n writing = Promise.all([writing, w.catch(() => {})]);\n return w;\n };\n const putLease = async () => {\n if (closed) return;\n const t = now();\n const body = {\n node: opts.node,\n expires_ms: t + ttl,\n addr,\n probe_public_key: generation,\n peer_protocol: 5,\n paced_handoff: false,\n ownership_index_generation: generation,\n load: {\n sampled_ms: t,\n owned_cells: owned.size,\n placement_weight: 0,\n bucket_format: 2,\n resident_cells: owned.size,\n host_websockets: 0,\n rss_bytes: 0,\n in_use_bytes: 0,\n cpu_percent_x100: 0,\n open_fds: 0,\n fd_limit: 0,\n pressured: true,\n memory_headroom: false,\n shed_cells: 0,\n restoring: 0,\n rebalance_paused: false,\n draining: false,\n },\n };\n await store.put(`nodes/${opts.node}.json`, enc.encode(JSON.stringify(body)));\n // Our authority lasts until the record we just wrote expires — minus a margin, so a node\n // that stalls (a paused process, a frozen tab) stops acknowledging before celld may take\n // over: celld waits for expires_ms; we stop at expires_ms - margin.\n if (!closed) validUntil = t + ttl - Math.min(1_000, ttl / 4);\n };\n const owned = new Set<string>();\n /** Cells whose ownership record was found lost, with the time their refusal ends. */\n const lostRecords = new Map<string, number>();\n const ensureLease = () => {\n leaseWritten ??= probeKeyReady.then(writeLease).then(() => {\n const tick = () => {\n if (closed) return;\n renew = setTimeout(() => {\n writeLease().then(tick, (e) => lost.abort(e));\n }, ttl / 3);\n // If renewals stop arriving (a stalled process, an unreachable bucket), every cell\n // stops at the local deadline — before celld may take it over.\n if (deadline) clearTimeout(deadline);\n deadline = setTimeout(\n () => {\n if (now() >= validUntil)\n lost.abort(new Error(`node ${opts.node}: lease deadline passed`));\n },\n Math.max(0, validUntil - now()),\n );\n (deadline as { unref?: () => void }).unref?.();\n (renew as { unref?: () => void }).unref?.();\n };\n tick();\n });\n return leaseWritten;\n };\n\n const readJson = async <T>(key: string) => {\n const o = await store.get(key);\n return o ? { value: JSON.parse(dec.decode(o.body)) as T, etag: o.etag } : undefined;\n };\n\n // One LTX writer (ltx-log.ts) holds each cell's epoch, TXID and page mirror.\n // A write is acknowledged only while our node lease is still ours (self-fencing).\n const log = ltxLog(store, { now, authorized: () => !closed && now() < validUntil });\n\n const ownership = {\n async tryAcquire(cellKey: string) {\n await ensureLease();\n const scope = scopeOf(cellKey);\n const key = `cells/${scope}/own.json`;\n const t0 = now();\n if ((lostRecords.get(scope) ?? 0) > t0) throw lostRecordError(scope); // still refused\n let cur = await readJson<{ node: string; epoch: number }>(key);\n // celld 0.6.3 (actor.rs read_owner_for_claim): a missing record next to LTX means the\n // record was LOST (e.g. a lifecycle rule) — claiming epoch 1 would start the cell empty.\n // Read once more, then refuse the cell for 10 s without asking the bucket again.\n if (!cur && (await store.list(`cells/${scope}/ltx`)).length > 0) {\n cur = await readJson<{ node: string; epoch: number }>(key);\n if (!cur) {\n lostRecords.set(scope, t0 + LOST_RECORD_RECHECK_MS);\n throw lostRecordError(scope);\n }\n }\n let epoch: number;\n try {\n if (!cur) {\n epoch = 1;\n await store.put(key, enc.encode(JSON.stringify({ node: opts.node, epoch })), {\n ifNoneMatch: \"*\",\n });\n } else {\n const holder = cur.value.node;\n if (holder && holder !== opts.node) {\n const lease = await readJson<{ expires_ms: number }>(`nodes/${holder}.json`);\n if (lease && lease.value.expires_ms > now()) return undefined; // a live owner\n }\n epoch = cur.value.epoch + 1; // every activation advances the epoch\n await store.put(key, enc.encode(JSON.stringify({ node: opts.node, epoch })), {\n ifMatch: cur.etag,\n });\n }\n } catch (e) {\n if (e instanceof PreconditionFailedError) return undefined; // another node won the race\n throw e;\n }\n log.begin(cellKey, epoch); // a new owner epoch starts a new generation\n owned.add(cellKey);\n return {\n name: cellKey,\n epoch,\n lost: lost.signal,\n release: async () => {\n const now2 = await readJson<{ node: string; epoch: number }>(key);\n if (now2?.value.node === opts.node && now2.value.epoch === epoch)\n await store\n .put(key, enc.encode(JSON.stringify({ node: \"\", epoch })), { ifMatch: now2.etag })\n .catch(() => {});\n log.forget(cellKey);\n owned.delete(cellKey);\n },\n };\n },\n };\n\n // celld's waker role (wake.rs try_hold_waker): one node per fleet wakes alarms of cells that\n // nobody owns or whose owner is dead — an advisory CAS lease in wake/waker.json; every node\n // wakes its own cells; a live owner's cells are never hinted.\n const holdWaker = async (ttlMs: number) => {\n const key = \"wake/waker.json\";\n const cur = await readJson<{ node: string; expires_ms: number }>(key);\n const t = now();\n if (cur && cur.value.node !== opts.node && cur.value.expires_ms > t) return false;\n try {\n await store.put(\n key,\n enc.encode(JSON.stringify({ node: opts.node, expires_ms: t + ttlMs })),\n cur ? { ifMatch: cur.etag } : { ifNoneMatch: \"*\" },\n );\n return true;\n } catch (e) {\n if (!(e instanceof PreconditionFailedError)) return false;\n const again = await readJson<{ node: string; expires_ms: number }>(key);\n return again?.value.node === opts.node && again.value.expires_ms > t;\n }\n };\n const allWakes = log.wakes!.bind(log);\n const durability = Object.assign(log, {\n async wakes() {\n const due = await allWakes();\n let elected: boolean | undefined;\n const out: typeof due = [];\n for (const w of due) {\n const own = await readJson<{ node: string }>(`cells/${scopeOf(w.cellKey)}/own.json`);\n const owner = own?.value.node;\n if (owner === opts.node) {\n out.push(w); // our own cell: always\n continue;\n }\n let ownerExpires = 0;\n if (owner) {\n const lease = await readJson<{ expires_ms: number }>(`nodes/${owner}.json`);\n ownerExpires = lease?.value.expires_ms ?? 0;\n }\n elected ??= await holdWaker(opts.wakerTtlMs ?? 90_000);\n if (!elected) continue; // unowned or dead owners are the elected waker's job\n // A live owner fires its own alarms; the waker looks again when that owner's lease\n // would have run out (a stopped owner's cells are then due here).\n out.push(ownerExpires > now() ? { ...w, at: Math.max(w.at, ownerExpires + 1) } : w);\n }\n return out;\n },\n });\n\n return {\n ownership,\n durability,\n backup: durability,\n /** The fleet bucket itself (R2 objects live directly in it, under `r2/<bucket>/`). */\n objects: store,\n /**\n * The address celld nodes reach this node at (its celld peer server): published in the\n * node record now, so celld routes requests for our cells here instead of refusing them.\n */\n /**\n * Answers celld's signed peer probe (peer_probe.rs): an Ed25519 signature over\n * \"cells-peer-probe-v1\\n<node>\\n<advertised addr>\\n<challenge>\" by this node's probe key.\n */\n async probe(challenge: string) {\n await probeKeyReady;\n if (!probeKey || !/^[0-9a-f]{64}$/.test(challenge))\n throw new Error(\"cannot answer the probe\");\n const payload = `cells-peer-probe-v1\\n${opts.node}\\n${addr}\\n${challenge}`;\n const signature = new Uint8Array(\n await crypto.subtle.sign(\"Ed25519\", probeKey, new TextEncoder().encode(payload)),\n );\n return {\n version: 1,\n node: opts.node,\n advertise: addr,\n challenge,\n signature: bytesToHex(signature),\n };\n },\n async advertise(address: string) {\n addr = address;\n if (leaseWritten) await writeLease();\n },\n /** Clean shutdown: stop renewing, remove this node's lease record, close the store. */\n async close() {\n closed = true;\n if (renew) clearTimeout(renew);\n if (deadline) clearTimeout(deadline);\n validUntil = 0; // no write is acknowledged once closing has begun\n // A renewal already on its way would write the record back after the delete, and the\n // node's cells would stay \"owned by a live node\" for a whole lease: let it land first —\n // for at most a lease, since one that lands later expires within a lease anyway.\n let waited: ReturnType<typeof setTimeout> | undefined;\n await Promise.race([writing, new Promise((r) => (waited = setTimeout(r, ttl)))]);\n clearTimeout(waited);\n if (leaseWritten) await store.delete(`nodes/${opts.node}.json`).catch(() => {});\n store.close?.();\n },\n };\n}\n"]}
|
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* The `cellb` command.
|
|
4
|
+
*
|
|
5
|
+
* cellb build <project-dir> [--out <dir>] writes a folder `loadProject` loads in a browser
|
|
6
|
+
*/
|
|
7
|
+
import { buildProject } from "./project/build.js";
|
|
8
|
+
const USAGE = `Usage:
|
|
9
|
+
cellb build <project-dir> [--out <dir>] [--no-lower-await]
|
|
10
|
+
|
|
11
|
+
Bundles the project's \`main\` for browsers and writes wrangler.json, worker.js, assets/ and
|
|
12
|
+
cellb.json into <dir> (default: <project-dir>/dist/cellb), for loadProject() from
|
|
13
|
+
@statewalker/cellb/browser or @statewalker/cellb/node.`;
|
|
14
|
+
/** Runs the command line `args` (without node and the script); returns the exit code. */
|
|
15
|
+
export async function main(args) {
|
|
16
|
+
const [command, ...rest] = args;
|
|
17
|
+
if (!command || command === "--help" || command === "-h" || command === "help") {
|
|
18
|
+
console.log(USAGE);
|
|
19
|
+
return command ? 0 : 1;
|
|
20
|
+
}
|
|
21
|
+
if (command !== "build") {
|
|
22
|
+
console.error(`cellb: unknown command ${JSON.stringify(command)}\n\n${USAGE}`);
|
|
23
|
+
return 1;
|
|
24
|
+
}
|
|
25
|
+
let dir;
|
|
26
|
+
let out;
|
|
27
|
+
let lowerAwait;
|
|
28
|
+
for (let i = 0; i < rest.length; i++) {
|
|
29
|
+
const a = rest[i];
|
|
30
|
+
if (a === "--out" || a === "-o")
|
|
31
|
+
out = rest[++i];
|
|
32
|
+
else if (a.startsWith("--out="))
|
|
33
|
+
out = a.slice(6);
|
|
34
|
+
else if (a === "--no-lower-await")
|
|
35
|
+
lowerAwait = false;
|
|
36
|
+
else if (a.startsWith("-")) {
|
|
37
|
+
console.error(`cellb build: unknown option ${a}\n\n${USAGE}`);
|
|
38
|
+
return 1;
|
|
39
|
+
}
|
|
40
|
+
else if (dir === undefined)
|
|
41
|
+
dir = a;
|
|
42
|
+
else {
|
|
43
|
+
console.error(`cellb build: one project directory only\n\n${USAGE}`);
|
|
44
|
+
return 1;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
if (!dir || (out !== undefined && !out)) {
|
|
48
|
+
console.error(USAGE);
|
|
49
|
+
return 1;
|
|
50
|
+
}
|
|
51
|
+
out ??= `${dir.replace(/\/+$/, "")}/dist/cellb`;
|
|
52
|
+
try {
|
|
53
|
+
const manifest = await buildProject(dir, out, { lowerAwait });
|
|
54
|
+
console.log(`cellb build: ${out} (${manifest.main ?? "no main"}, ${manifest.assets.length} asset file(s))`);
|
|
55
|
+
return 0;
|
|
56
|
+
}
|
|
57
|
+
catch (err) {
|
|
58
|
+
console.error(`cellb build: ${err.message ?? err}`);
|
|
59
|
+
return 1;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA;;;;GAIG;AACH,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAElD,MAAM,KAAK,GAAG;;;;;2DAK6C,CAAC;AAE5D,yFAAyF;AACzF,MAAM,CAAC,KAAK,UAAU,IAAI,CAAC,IAAc;IACvC,MAAM,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,GAAG,IAAI,CAAC;IAChC,IAAI,CAAC,OAAO,IAAI,OAAO,KAAK,QAAQ,IAAI,OAAO,KAAK,IAAI,IAAI,OAAO,KAAK,MAAM,EAAE,CAAC;QAC/E,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QACnB,OAAO,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACzB,CAAC;IACD,IAAI,OAAO,KAAK,OAAO,EAAE,CAAC;QACxB,OAAO,CAAC,KAAK,CAAC,0BAA0B,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,OAAO,KAAK,EAAE,CAAC,CAAC;QAC/E,OAAO,CAAC,CAAC;IACX,CAAC;IACD,IAAI,GAAuB,CAAC;IAC5B,IAAI,GAAuB,CAAC;IAC5B,IAAI,UAA+B,CAAC;IACpC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACrC,MAAM,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,IAAI,CAAC,KAAK,OAAO,IAAI,CAAC,KAAK,IAAI;YAAE,GAAG,GAAG,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC;aAC5C,IAAI,CAAC,CAAC,UAAU,CAAC,QAAQ,CAAC;YAAE,GAAG,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;aAC7C,IAAI,CAAC,KAAK,kBAAkB;YAAE,UAAU,GAAG,KAAK,CAAC;aACjD,IAAI,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;YAC3B,OAAO,CAAC,KAAK,CAAC,+BAA+B,CAAC,OAAO,KAAK,EAAE,CAAC,CAAC;YAC9D,OAAO,CAAC,CAAC;QACX,CAAC;aAAM,IAAI,GAAG,KAAK,SAAS;YAAE,GAAG,GAAG,CAAC,CAAC;aACjC,CAAC;YACJ,OAAO,CAAC,KAAK,CAAC,8CAA8C,KAAK,EAAE,CAAC,CAAC;YACrE,OAAO,CAAC,CAAC;QACX,CAAC;IACH,CAAC;IACD,IAAI,CAAC,GAAG,IAAI,CAAC,GAAG,KAAK,SAAS,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;QACxC,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QACrB,OAAO,CAAC,CAAC;IACX,CAAC;IACD,GAAG,KAAK,GAAG,GAAG,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,aAAa,CAAC;IAChD,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,MAAM,YAAY,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,UAAU,EAAE,CAAC,CAAC;QAC9D,OAAO,CAAC,GAAG,CACT,gBAAgB,GAAG,KAAK,QAAQ,CAAC,IAAI,IAAI,SAAS,KAAK,QAAQ,CAAC,MAAM,CAAC,MAAM,iBAAiB,CAC/F,CAAC;QACF,OAAO,CAAC,CAAC;IACX,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,CAAC,KAAK,CAAC,gBAAiB,GAAa,CAAC,OAAO,IAAI,GAAG,EAAE,CAAC,CAAC;QAC/D,OAAO,CAAC,CAAC;IACX,CAAC;AACH,CAAC","sourcesContent":["#!/usr/bin/env node\n/**\n * The `cellb` command.\n *\n * cellb build <project-dir> [--out <dir>] writes a folder `loadProject` loads in a browser\n */\nimport { buildProject } from \"./project/build.js\";\n\nconst USAGE = `Usage:\n cellb build <project-dir> [--out <dir>] [--no-lower-await]\n\n Bundles the project's \\`main\\` for browsers and writes wrangler.json, worker.js, assets/ and\n cellb.json into <dir> (default: <project-dir>/dist/cellb), for loadProject() from\n @statewalker/cellb/browser or @statewalker/cellb/node.`;\n\n/** Runs the command line `args` (without node and the script); returns the exit code. */\nexport async function main(args: string[]): Promise<number> {\n const [command, ...rest] = args;\n if (!command || command === \"--help\" || command === \"-h\" || command === \"help\") {\n console.log(USAGE);\n return command ? 0 : 1;\n }\n if (command !== \"build\") {\n console.error(`cellb: unknown command ${JSON.stringify(command)}\\n\\n${USAGE}`);\n return 1;\n }\n let dir: string | undefined;\n let out: string | undefined;\n let lowerAwait: boolean | undefined;\n for (let i = 0; i < rest.length; i++) {\n const a = rest[i];\n if (a === \"--out\" || a === \"-o\") out = rest[++i];\n else if (a.startsWith(\"--out=\")) out = a.slice(6);\n else if (a === \"--no-lower-await\") lowerAwait = false;\n else if (a.startsWith(\"-\")) {\n console.error(`cellb build: unknown option ${a}\\n\\n${USAGE}`);\n return 1;\n } else if (dir === undefined) dir = a;\n else {\n console.error(`cellb build: one project directory only\\n\\n${USAGE}`);\n return 1;\n }\n }\n if (!dir || (out !== undefined && !out)) {\n console.error(USAGE);\n return 1;\n }\n out ??= `${dir.replace(/\\/+$/, \"\")}/dist/cellb`;\n try {\n const manifest = await buildProject(dir, out, { lowerAwait });\n console.log(\n `cellb build: ${out} (${manifest.main ?? \"no main\"}, ${manifest.assets.length} asset file(s))`,\n );\n return 0;\n } catch (err) {\n console.error(`cellb build: ${(err as Error).message ?? err}`);\n return 1;\n }\n}\n"]}
|
|
@@ -7,7 +7,8 @@ import type { CellStorage } from "../ports.js";
|
|
|
7
7
|
* travel to the isolate and become Blob-URL modules there.
|
|
8
8
|
*/
|
|
9
9
|
export declare function browserIsolatedLoader(opts: {
|
|
10
|
-
|
|
10
|
+
/** The isolate's script, or a function that starts a Dedicated Worker running it. */
|
|
11
|
+
isolateScript: URL | (() => Worker);
|
|
11
12
|
siteBaseUrl?: string;
|
|
12
13
|
/** Prefix of the OPFS SQLite pool each isolate keeps its facets in (one pool per loaded id). */
|
|
13
14
|
storagePrefix?: string;
|
package/dist/isolate/browser.js
CHANGED
|
@@ -11,7 +11,8 @@ export function browserIsolatedLoader(opts) {
|
|
|
11
11
|
return isolatedModuleLoader({
|
|
12
12
|
storage: opts.storage,
|
|
13
13
|
spawn: () => {
|
|
14
|
-
const
|
|
14
|
+
const script = opts.isolateScript;
|
|
15
|
+
const w = typeof script === "function" ? script() : new Worker(script, { type: "module" });
|
|
15
16
|
return { port: w, terminate: () => w.terminate() };
|
|
16
17
|
},
|
|
17
18
|
prepare: async (id, code) => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"browser.js","sourceRoot":"","sources":["../../src/isolate/browser.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAC5D,OAAO,EAAE,oBAAoB,EAAE,MAAM,WAAW,CAAC;AAGjD;;;;;GAKG;AACH,MAAM,UAAU,qBAAqB,CAAC,
|
|
1
|
+
{"version":3,"file":"browser.js","sourceRoot":"","sources":["../../src/isolate/browser.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAC5D,OAAO,EAAE,oBAAoB,EAAE,MAAM,WAAW,CAAC;AAGjD;;;;;GAKG;AACH,MAAM,UAAU,qBAAqB,CAAC,IAQrC;IACC,MAAM,WAAW,GAAG,CAAC,EAAU,EAAE,EAAE,CACjC,GAAG,IAAI,CAAC,aAAa,IAAI,SAAS,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,UAAU,EAAE,GAAG,CAAC,CAAC;IACtE,OAAO,oBAAoB,CAAC;QAC1B,OAAO,EAAE,IAAI,CAAC,OAAO;QACrB,KAAK,EAAE,GAAG,EAAE;YACV,MAAM,MAAM,GAAG,IAAI,CAAC,aAAa,CAAC;YAClC,MAAM,CAAC,GAAG,OAAO,MAAM,KAAK,UAAU,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,IAAI,MAAM,CAAC,MAAM,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAC;YAC3F,OAAO,EAAE,IAAI,EAAE,CAAwB,EAAE,SAAS,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC;QAC5E,CAAC;QACD,OAAO,EAAE,KAAK,EAAE,EAAU,EAAE,IAAgB,EAAE,EAAE;YAC9C,IAAI,CAAC,IAAI,CAAC,WAAW;gBACnB,OAAO;oBACL,OAAO,EAAE,IAAI,CAAC,OAAO;oBACrB,UAAU,EAAE,IAAI,CAAC,UAAU;oBAC3B,WAAW,EAAE,WAAW,CAAC,EAAE,CAAC;iBACpB,CAAC;YACb,MAAM,IAAI,GAAG,IAAI,CAAC,WAAW,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,WAAW,GAAG,CAAC;YACxF,MAAM,GAAG,GAAG,kBAAkB,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC;YAChD,KAAK,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;gBACrD,MAAM,eAAe,CAAC,GAAG,IAAI,cAAc,GAAG,IAAI,IAAI,EAAE,EAAE,CAAC,CAAC,CAAC;YAC/D,CAAC;YACD,OAAO;gBACL,GAAG,EAAE,GAAG,IAAI,UAAU,GAAG,MAAM,IAAI,CAAC,UAAU,EAAE;gBAChD,SAAS,EAAE,WAAW,EAAE,EAAE;gBAC1B,WAAW,EAAE,WAAW,CAAC,EAAE,CAAC;aACpB,CAAC;QACb,CAAC;KACF,CAAC,CAAC;AACL,CAAC","sourcesContent":["import type { ModuleLoader, WorkerCode } from \"../bindings/worker-loader.js\";\nimport type { CellStorage } from \"../ports.js\";\nimport { putLoadedModule } from \"../project/site-loader.js\";\nimport { isolatedModuleLoader } from \"./host.js\";\nimport type { PortLike } from \"./protocol.js\";\n\n/**\n * Isolated loading in browsers: each loaded Worker runs in its own Dedicated Worker\n * (`isolateScript`, e.g. src/isolate/isolate-worker.ts). With `siteBaseUrl` (the in-browser\n * HTTP server, docs/browser-host.md) its modules are uploaded and imported from URLs; otherwise they\n * travel to the isolate and become Blob-URL modules there.\n */\nexport function browserIsolatedLoader(opts: {\n /** The isolate's script, or a function that starts a Dedicated Worker running it. */\n isolateScript: URL | (() => Worker);\n siteBaseUrl?: string;\n /** Prefix of the OPFS SQLite pool each isolate keeps its facets in (one pool per loaded id). */\n storagePrefix?: string;\n /** The host's storage: facets of loaded code are logged and restored through it. */\n storage?: () => CellStorage | undefined;\n}): ModuleLoader {\n const storageName = (id: string) =>\n `${opts.storagePrefix ?? \"isolate\"}-${id}`.replace(/[^\\w.-]/g, \"_\");\n return isolatedModuleLoader({\n storage: opts.storage,\n spawn: () => {\n const script = opts.isolateScript;\n const w = typeof script === \"function\" ? script() : new Worker(script, { type: \"module\" });\n return { port: w as unknown as PortLike, terminate: () => w.terminate() };\n },\n prepare: async (id: string, code: WorkerCode) => {\n if (!opts.siteBaseUrl)\n return {\n modules: code.modules,\n mainModule: code.mainModule,\n storageName: storageName(id),\n } as never;\n const base = opts.siteBaseUrl.endsWith(\"/\") ? opts.siteBaseUrl : `${opts.siteBaseUrl}/`;\n const dir = encodeURIComponent(`isolate-${id}`);\n for (const [name, m] of Object.entries(code.modules)) {\n await putLoadedModule(`${base}loaded-src/${dir}/${name}`, m);\n }\n return {\n url: `${base}loaded/${dir}/~/${code.mainModule}`,\n consoleId: `isolate-${id}`,\n storageName: storageName(id),\n } as never;\n },\n });\n}\n"]}
|
package/dist/isolate/isolate.js
CHANGED
|
@@ -147,9 +147,11 @@ export function serveIsolate(port, importCode, opts = {}) {
|
|
|
147
147
|
}
|
|
148
148
|
if (m.type === "image")
|
|
149
149
|
return await images.get(m.token)?.();
|
|
150
|
-
// The host is stopping this isolate: the sockets
|
|
151
|
-
//
|
|
152
|
-
//
|
|
150
|
+
// The host is stopping this isolate: the sockets served here hear 1001 first. A 101 carries
|
|
151
|
+
// its early frames, so a socket's close normally goes straight to its client; only a client
|
|
152
|
+
// end that had sent unacknowledged frames before its hand-over keeps a relay here
|
|
153
|
+
// (takeClientEnd), and the answer waits a macrotask so that relay forwards the 1001 before
|
|
154
|
+
// the host terminates the isolate.
|
|
153
155
|
if (m.type === "goAway") {
|
|
154
156
|
CfWebSocket.goAway("the loaded Worker stopped");
|
|
155
157
|
return await new Promise((r) => setTimeout(r, 0));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"isolate.js","sourceRoot":"","sources":["../../src/isolate/isolate.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The isolate side: loads the Worker code in THIS realm and serves fetch/RPC for the host.\n * `importCode` is realm specific (data: URL of a bundle on Node, a site URL in browsers).\n */\nimport { ctxExports, entrypointStub } from \"../core/handles.js\";\nimport type { DurableObjectId } from \"../core/id.js\";\nimport { runInDurableObject } from \"../core/namespace.js\";\nimport { type CellRuntime, createCellRuntime, type WorkerModule } from \"../core/runtime.js\";\nimport type { Lease, LeaseHandle, OpenCellSql } from \"../ports.js\";\nimport { realmWorkerScope } from \"../project/worker-scope.js\";\nimport { CfWebSocket } from \"../ws/server.js\";\nimport { type FetchTarget, isolatePort, type PortLike } from \"./protocol.js\";\n\nexport function serveIsolate(\n port: PortLike,\n importCode: (\n load: any,\n logs: (level: string, args: unknown[]) => void,\n ) => Promise<Record<string, unknown>>,\n opts: {\n patchConsole?: boolean;\n /**\n * Storage for Durable Object classes run INSIDE this isolate (facets of loaded code):\n * each facet is a cell of an isolate-local runtime, with its own SQLite.\n */\n openSql?: (load: any) => OpenCellSql | Promise<OpenCellSql>;\n } = {},\n) {\n let module: Record<string, unknown> = {};\n const runtimes = new Map<string, CellRuntime>();\n // Facet logging through the host: the isolate-local cell key → the facet's key in the\n // root's storage (celld: <root>/facets/<sha>) and the root's epoch.\n const facetKeys = new Map<string, { key: string; epoch?: number }>();\n const images = new Map<number, () => unknown>();\n let nextImage = 0;\n const hostLogged = (internal: string) => facetKeys.get(internal);\n const durability = {\n capturePages: true,\n async committed(cellKey: string, info: any) {\n const f = hostLogged(cellKey);\n if (!f) return;\n const token = ++nextImage;\n if (typeof info.image === \"function\") images.set(token, info.image);\n try {\n await call({\n type: \"durable\",\n op: \"committed\",\n cellKey: f.key,\n info: {\n seq: info.seq,\n pages: info.pages,\n epoch: f.epoch,\n image: typeof info.image === \"function\" ? token : info.image,\n },\n });\n } finally {\n images.delete(token);\n }\n },\n };\n const backup = {\n async load(cellKey: string) {\n const f = hostLogged(cellKey);\n return f\n ? ((await call({ type: \"durable\", op: \"load\", cellKey: f.key })) as Uint8Array | undefined)\n : undefined;\n },\n async save() {},\n };\n // The host holds the root's lease and attaches each facet with its epoch: handing that epoch\n // out as the facet's own lease makes its ObjectHost rebuild the local database from the\n // bucket when it opens, as a root does under a lease (a pool kept from an earlier ownership\n // is older than what other nodes wrote since). Without an epoch, the local file stays primary.\n const ownership: Lease = {\n async tryAcquire(cellKey) {\n const lost = new AbortController().signal; // fencing stays with the root's lease, in the host\n return {\n name: cellKey,\n epoch: hostLogged(cellKey)?.epoch,\n lost,\n release: async () => {},\n } as LeaseHandle;\n },\n };\n let loadMsg: any;\n let sql: Promise<OpenCellSql> | undefined;\n const objectRuntime = async (className: string) => {\n let rt = runtimes.get(className);\n if (!rt) {\n if (!opts.openSql)\n throw new Error(\"this isolate cannot host Durable Object classes (no storage)\");\n sql ??= Promise.resolve(opts.openSql(loadMsg));\n const openSql = await sql;\n if (runtimes.has(className)) return runtimes.get(className)!;\n if (typeof module[className] !== \"function\")\n throw new Error(`Loaded worker does not export class \"${className}\"`);\n rt = createCellRuntime({\n worker: { ...(module as WorkerModule), default: {} },\n durableObjects: { [className]: { className } },\n openSql,\n vars: env, // the loaded Worker's env: plain values and host stubs\n storage: { durability, backup, ownership },\n facets: true, // rows move to this run's scope on open, as celld does for facets\n ctxIdOf: (key) => {\n const name = idNames.get(key);\n return name === undefined\n ? undefined\n : (rt!.env[className] as { idFromName(n: string): DurableObjectId }).idFromName(name);\n },\n });\n runtimes.set(className, rt);\n }\n return rt;\n };\n let env: Record<string, unknown> = {};\n let exports: Record<string, unknown> = {};\n // The loaded Worker is this realm's own: its `cloudflare:workers` reads `env` and `exports` here.\n const scope = realmWorkerScope();\n // The name of the `id` a facet was given by `ctx.facets.get`, by its isolate-local cell key:\n // the facet's `ctx.id` (partyserver's `this.name`). Two facets may be given the same id; each\n // keeps its own object and storage, addressed by the parent's facet cell key.\n const idNames = new Map<string, string>();\n // A facet of loaded code: the object lives here, named by the parent's facet cell key.\n const facet = async (className: string, cellKey: string) => {\n const rt = await objectRuntime(className);\n const ns = rt.env[className] as {\n getByName(n: string): any;\n idFromName(n: string): { toString(): string };\n };\n return { rt, stub: ns.getByName(cellKey), key: `${className}/${ns.idFromName(cellKey)}` };\n };\n const exported = (name: string, props?: unknown) => {\n const target = module[name];\n if (target === undefined) throw new Error(`Loaded worker does not export \"${name}\"`);\n return entrypointStub(target, env, props, exports);\n };\n const fetchTarget = async (t: FetchTarget, req: Request): Promise<Response> =>\n \"className\" in t\n ? (await facet(t.className, t.cellKey)).stub.fetch(req)\n : exported(t.name, t.props).fetch(req);\n const handle = async (m: any) => {\n if (m.type === \"load\") {\n loadMsg = m;\n env = m.env; // host bindings arrive as stubs: every call goes back to the host\n // Logs go to the host (Tail Workers, and the host prints them): through the global\n // console, or — where the realm has other code that logs — a console scoped to the\n // loaded modules (importCode receives the sink).\n const sink = (level: string, args: unknown[]) =>\n call({ type: \"log\", level, args: safe(args) }).catch(() => {});\n if (opts.patchConsole !== false)\n for (const level of [\"log\", \"info\", \"warn\", \"error\", \"debug\"] as const)\n console[level] = (...args: unknown[]) => sink(level, args);\n if (m.outbound === \"fetcher\")\n globalThis.fetch = (async (input: RequestInfo | URL, init?: RequestInit) =>\n outbound(new Request(input, init))) as typeof fetch;\n if (m.outbound === \"blocked\")\n globalThis.fetch = (async () => {\n throw new Error(\n \"This worker is not permitted to access the internet via global functions like fetch(). It must use capabilities (such as bindings in 'env') to talk to the outside world.\",\n );\n }) as typeof fetch;\n scope.bind({ env, exports }); // read while the module is evaluated\n module = await importCode(m, sink);\n exports = ctxExports(module, env);\n scope.bind({ env, exports });\n return Object.fromEntries(Object.entries(module).map(([k, v]) => [k, typeof v]));\n }\n if (m.type === \"image\") return await images.get(m.token)?.();\n // The host is stopping this isolate: the sockets accepted here hear 1001 first.\n // A socket whose early frames went through a relay port hears it one hop later: the\n // answer waits a macrotask, so the host does not terminate the isolate before that.\n if (m.type === \"goAway\") {\n CfWebSocket.goAway(\"the loaded Worker stopped\");\n return await new Promise((r) => setTimeout(r, 0));\n }\n if (m.type === \"object\") {\n const { rt, stub, key } = await facet(m.className, m.cellKey);\n if (m.op === \"attach\") {\n facetKeys.set(key, { key: m.cellKey, epoch: m.epoch });\n if (typeof m.idName === \"string\") idNames.set(key, m.idName);\n return;\n }\n if (m.op === \"call\") return stub[m.method](...m.args);\n if (m.op === \"restart\") return rt.restartObject(key);\n if (m.op === \"delete\") {\n await runInDurableObject(\n stub,\n (_o: unknown, ctx: { storage: { deleteAll(): Promise<void> } }) =>\n ctx.storage.deleteAll(),\n );\n return rt.restartObject(key);\n }\n throw new Error(`unknown object op ${m.op}`);\n }\n if (m.type === \"call\") return exported(m.name, m.props)[m.method](...m.args);\n throw new Error(`unknown isolate message ${m.type}`);\n };\n const { call, outbound } = isolatePort(port, handle, fetchTarget);\n}\n\nconst safe = (args: unknown[]) =>\n args.map((a) => {\n try {\n return structuredClone(a);\n } catch {\n return String(a);\n }\n });\n"]}
|
|
1
|
+
{"version":3,"file":"isolate.js","sourceRoot":"","sources":["../../src/isolate/isolate.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The isolate side: loads the Worker code in THIS realm and serves fetch/RPC for the host.\n * `importCode` is realm specific (data: URL of a bundle on Node, a site URL in browsers).\n */\nimport { ctxExports, entrypointStub } from \"../core/handles.js\";\nimport type { DurableObjectId } from \"../core/id.js\";\nimport { runInDurableObject } from \"../core/namespace.js\";\nimport { type CellRuntime, createCellRuntime, type WorkerModule } from \"../core/runtime.js\";\nimport type { Lease, LeaseHandle, OpenCellSql } from \"../ports.js\";\nimport { realmWorkerScope } from \"../project/worker-scope.js\";\nimport { CfWebSocket } from \"../ws/server.js\";\nimport { type FetchTarget, isolatePort, type PortLike } from \"./protocol.js\";\n\nexport function serveIsolate(\n port: PortLike,\n importCode: (\n load: any,\n logs: (level: string, args: unknown[]) => void,\n ) => Promise<Record<string, unknown>>,\n opts: {\n patchConsole?: boolean;\n /**\n * Storage for Durable Object classes run INSIDE this isolate (facets of loaded code):\n * each facet is a cell of an isolate-local runtime, with its own SQLite.\n */\n openSql?: (load: any) => OpenCellSql | Promise<OpenCellSql>;\n } = {},\n) {\n let module: Record<string, unknown> = {};\n const runtimes = new Map<string, CellRuntime>();\n // Facet logging through the host: the isolate-local cell key → the facet's key in the\n // root's storage (celld: <root>/facets/<sha>) and the root's epoch.\n const facetKeys = new Map<string, { key: string; epoch?: number }>();\n const images = new Map<number, () => unknown>();\n let nextImage = 0;\n const hostLogged = (internal: string) => facetKeys.get(internal);\n const durability = {\n capturePages: true,\n async committed(cellKey: string, info: any) {\n const f = hostLogged(cellKey);\n if (!f) return;\n const token = ++nextImage;\n if (typeof info.image === \"function\") images.set(token, info.image);\n try {\n await call({\n type: \"durable\",\n op: \"committed\",\n cellKey: f.key,\n info: {\n seq: info.seq,\n pages: info.pages,\n epoch: f.epoch,\n image: typeof info.image === \"function\" ? token : info.image,\n },\n });\n } finally {\n images.delete(token);\n }\n },\n };\n const backup = {\n async load(cellKey: string) {\n const f = hostLogged(cellKey);\n return f\n ? ((await call({ type: \"durable\", op: \"load\", cellKey: f.key })) as Uint8Array | undefined)\n : undefined;\n },\n async save() {},\n };\n // The host holds the root's lease and attaches each facet with its epoch: handing that epoch\n // out as the facet's own lease makes its ObjectHost rebuild the local database from the\n // bucket when it opens, as a root does under a lease (a pool kept from an earlier ownership\n // is older than what other nodes wrote since). Without an epoch, the local file stays primary.\n const ownership: Lease = {\n async tryAcquire(cellKey) {\n const lost = new AbortController().signal; // fencing stays with the root's lease, in the host\n return {\n name: cellKey,\n epoch: hostLogged(cellKey)?.epoch,\n lost,\n release: async () => {},\n } as LeaseHandle;\n },\n };\n let loadMsg: any;\n let sql: Promise<OpenCellSql> | undefined;\n const objectRuntime = async (className: string) => {\n let rt = runtimes.get(className);\n if (!rt) {\n if (!opts.openSql)\n throw new Error(\"this isolate cannot host Durable Object classes (no storage)\");\n sql ??= Promise.resolve(opts.openSql(loadMsg));\n const openSql = await sql;\n if (runtimes.has(className)) return runtimes.get(className)!;\n if (typeof module[className] !== \"function\")\n throw new Error(`Loaded worker does not export class \"${className}\"`);\n rt = createCellRuntime({\n worker: { ...(module as WorkerModule), default: {} },\n durableObjects: { [className]: { className } },\n openSql,\n vars: env, // the loaded Worker's env: plain values and host stubs\n storage: { durability, backup, ownership },\n facets: true, // rows move to this run's scope on open, as celld does for facets\n ctxIdOf: (key) => {\n const name = idNames.get(key);\n return name === undefined\n ? undefined\n : (rt!.env[className] as { idFromName(n: string): DurableObjectId }).idFromName(name);\n },\n });\n runtimes.set(className, rt);\n }\n return rt;\n };\n let env: Record<string, unknown> = {};\n let exports: Record<string, unknown> = {};\n // The loaded Worker is this realm's own: its `cloudflare:workers` reads `env` and `exports` here.\n const scope = realmWorkerScope();\n // The name of the `id` a facet was given by `ctx.facets.get`, by its isolate-local cell key:\n // the facet's `ctx.id` (partyserver's `this.name`). Two facets may be given the same id; each\n // keeps its own object and storage, addressed by the parent's facet cell key.\n const idNames = new Map<string, string>();\n // A facet of loaded code: the object lives here, named by the parent's facet cell key.\n const facet = async (className: string, cellKey: string) => {\n const rt = await objectRuntime(className);\n const ns = rt.env[className] as {\n getByName(n: string): any;\n idFromName(n: string): { toString(): string };\n };\n return { rt, stub: ns.getByName(cellKey), key: `${className}/${ns.idFromName(cellKey)}` };\n };\n const exported = (name: string, props?: unknown) => {\n const target = module[name];\n if (target === undefined) throw new Error(`Loaded worker does not export \"${name}\"`);\n return entrypointStub(target, env, props, exports);\n };\n const fetchTarget = async (t: FetchTarget, req: Request): Promise<Response> =>\n \"className\" in t\n ? (await facet(t.className, t.cellKey)).stub.fetch(req)\n : exported(t.name, t.props).fetch(req);\n const handle = async (m: any) => {\n if (m.type === \"load\") {\n loadMsg = m;\n env = m.env; // host bindings arrive as stubs: every call goes back to the host\n // Logs go to the host (Tail Workers, and the host prints them): through the global\n // console, or — where the realm has other code that logs — a console scoped to the\n // loaded modules (importCode receives the sink).\n const sink = (level: string, args: unknown[]) =>\n call({ type: \"log\", level, args: safe(args) }).catch(() => {});\n if (opts.patchConsole !== false)\n for (const level of [\"log\", \"info\", \"warn\", \"error\", \"debug\"] as const)\n console[level] = (...args: unknown[]) => sink(level, args);\n if (m.outbound === \"fetcher\")\n globalThis.fetch = (async (input: RequestInfo | URL, init?: RequestInit) =>\n outbound(new Request(input, init))) as typeof fetch;\n if (m.outbound === \"blocked\")\n globalThis.fetch = (async () => {\n throw new Error(\n \"This worker is not permitted to access the internet via global functions like fetch(). It must use capabilities (such as bindings in 'env') to talk to the outside world.\",\n );\n }) as typeof fetch;\n scope.bind({ env, exports }); // read while the module is evaluated\n module = await importCode(m, sink);\n exports = ctxExports(module, env);\n scope.bind({ env, exports });\n return Object.fromEntries(Object.entries(module).map(([k, v]) => [k, typeof v]));\n }\n if (m.type === \"image\") return await images.get(m.token)?.();\n // The host is stopping this isolate: the sockets served here hear 1001 first. A 101 carries\n // its early frames, so a socket's close normally goes straight to its client; only a client\n // end that had sent unacknowledged frames before its hand-over keeps a relay here\n // (takeClientEnd), and the answer waits a macrotask so that relay forwards the 1001 before\n // the host terminates the isolate.\n if (m.type === \"goAway\") {\n CfWebSocket.goAway(\"the loaded Worker stopped\");\n return await new Promise((r) => setTimeout(r, 0));\n }\n if (m.type === \"object\") {\n const { rt, stub, key } = await facet(m.className, m.cellKey);\n if (m.op === \"attach\") {\n facetKeys.set(key, { key: m.cellKey, epoch: m.epoch });\n if (typeof m.idName === \"string\") idNames.set(key, m.idName);\n return;\n }\n if (m.op === \"call\") return stub[m.method](...m.args);\n if (m.op === \"restart\") return rt.restartObject(key);\n if (m.op === \"delete\") {\n await runInDurableObject(\n stub,\n (_o: unknown, ctx: { storage: { deleteAll(): Promise<void> } }) =>\n ctx.storage.deleteAll(),\n );\n return rt.restartObject(key);\n }\n throw new Error(`unknown object op ${m.op}`);\n }\n if (m.type === \"call\") return exported(m.name, m.props)[m.method](...m.args);\n throw new Error(`unknown isolate message ${m.type}`);\n };\n const { call, outbound } = isolatePort(port, handle, fetchTarget);\n}\n\nconst safe = (args: unknown[]) =>\n args.map((a) => {\n try {\n return structuredClone(a);\n } catch {\n return String(a);\n }\n });\n"]}
|
|
@@ -23,8 +23,10 @@
|
|
|
23
23
|
* A WebSocket upgrade (host → isolate) has a port of its own instead, since HTTP/1.1 drops the
|
|
24
24
|
* `Upgrade` header: its meta carries the request's method and headers; the isolate answers with
|
|
25
25
|
* the response's head and closes the port. A 101's head carries the socket's own MessagePort,
|
|
26
|
-
* transferred
|
|
27
|
-
*
|
|
26
|
+
* transferred, and the frames the server sent before the 101 (replayed by the host's end): the
|
|
27
|
+
* socket's frames then go straight between the client and the isolate, outside the multiplexer
|
|
28
|
+
* and through no relay in the isolate, so open sockets hold none of its ports (it allows
|
|
29
|
+
* `maxPorts` at once) and a socket's 1001 reaches its client before the isolate ends.
|
|
28
30
|
*/
|
|
29
31
|
export interface PortLike {
|
|
30
32
|
postMessage(msg: unknown, transfer?: Transferable[]): void;
|
package/dist/isolate/protocol.js
CHANGED
|
@@ -2,7 +2,7 @@ import { fetchOverDuplex, serveFetchOverDuplex } from "@statewalker/webrun-http-
|
|
|
2
2
|
import { callPort, duplexOverPort, getPortCloseSignal, listenPort, multiplexPort, NO_TIMEOUT, serveDuplexOverPort, structuredCodec, } from "@statewalker/webrun-rpc";
|
|
3
3
|
import { cross, pipeTo, rpcPromise, STUB } from "../core/handles.js";
|
|
4
4
|
import { withUrl } from "../core/request-body.js";
|
|
5
|
-
import { CellsResponse, CfWebSocket,
|
|
5
|
+
import { CellsResponse, CfWebSocket, takeClientEnd } from "../ws/server.js";
|
|
6
6
|
import { carryWebSocketHeaders } from "../ws/upgrade.js";
|
|
7
7
|
/**
|
|
8
8
|
* The host's end of an isolate's port: one webrun-rpc multiplexer over it, a calls port on
|
|
@@ -112,7 +112,7 @@ async function upgradeOver(mux, meta, req) {
|
|
|
112
112
|
return resolve(new CellsResponse(null, {
|
|
113
113
|
status: 101,
|
|
114
114
|
headers: head.headers,
|
|
115
|
-
webSocket: new CfWebSocket(head.socket),
|
|
115
|
+
webSocket: new CfWebSocket(head.socket, head.frames),
|
|
116
116
|
}));
|
|
117
117
|
resolve(new Response(nullBody(head.status) ? null : head.body, {
|
|
118
118
|
status: head.status,
|
|
@@ -156,8 +156,8 @@ function serveUpgrade(p, meta, fetch) {
|
|
|
156
156
|
const res = await fetch(req);
|
|
157
157
|
const ws = res.webSocket;
|
|
158
158
|
if (res.status === 101 && ws) {
|
|
159
|
-
const socket =
|
|
160
|
-
answer({ status: 101, headers: [...res.headers], body: null, socket }, [socket]);
|
|
159
|
+
const { port: socket, frames } = takeClientEnd(ws);
|
|
160
|
+
answer({ status: 101, headers: [...res.headers], body: null, socket, frames }, [socket]);
|
|
161
161
|
}
|
|
162
162
|
else
|
|
163
163
|
answer({
|