@nimbus-sh/worker 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 -43
- package/dist/_shared/preview-host.d.ts +14 -1
- package/dist/_shared/preview-host.d.ts.map +1 -1
- package/dist/_shared/preview-host.js +28 -3
- package/dist/_shared/session-router.d.ts +43 -0
- package/dist/_shared/session-router.d.ts.map +1 -1
- package/dist/_shared/session-router.js +59 -12
- package/dist/esbuild-wasm-bundle.generated.d.ts +1 -1
- package/dist/esbuild-wasm-bundle.generated.js +1 -1
- package/dist/facets/cirrus-real.js +1 -1
- package/dist/facets/durable-images.d.ts +50 -0
- package/dist/facets/durable-images.d.ts.map +1 -0
- package/dist/facets/durable-images.js +75 -0
- package/dist/facets/durable-slots.d.ts +45 -0
- package/dist/facets/durable-slots.d.ts.map +1 -0
- package/dist/facets/durable-slots.js +94 -0
- package/dist/facets/esbuild-transform.d.ts +10 -0
- package/dist/facets/esbuild-transform.d.ts.map +1 -0
- package/dist/facets/esbuild-transform.js +36 -0
- package/dist/facets/manager.d.ts +167 -12
- package/dist/facets/manager.d.ts.map +1 -1
- package/dist/facets/manager.js +575 -113
- package/dist/facets/opencode-staging.d.ts.map +1 -1
- package/dist/facets/opencode-staging.js +3 -4
- package/dist/facets/real-vite-hmr.js +1 -1
- package/dist/facets/vite-dev-server.d.ts +4 -4
- package/dist/facets/vite-dev-server.js +5 -5
- package/dist/git/commands.d.ts +22 -0
- package/dist/git/commands.d.ts.map +1 -1
- package/dist/git/commands.js +431 -424
- package/dist/git/network-facet.d.ts.map +1 -1
- package/dist/git/network-facet.js +6 -5
- package/dist/git-bundle.generated.d.ts +2 -2
- package/dist/git-bundle.generated.d.ts.map +1 -1
- package/dist/git-bundle.generated.js +3 -3
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +11 -2
- package/dist/loaders/child-process/spawn-facet.js +1 -1
- package/dist/loaders/child-process/spawn-pool.d.ts.map +1 -1
- package/dist/loaders/child-process/spawn-pool.js +2 -2
- package/dist/loaders/generated-workers.d.ts +2 -2
- package/dist/loaders/generated-workers.d.ts.map +1 -1
- package/dist/loaders/generated-workers.js +3 -3
- package/dist/loaders/npm-resolve-preamble.d.ts +3 -3
- package/dist/loaders/npm-resolve-preamble.js +3 -3
- package/dist/loaders/pre-bundle-preamble.d.ts +5 -5
- package/dist/loaders/pre-bundle-preamble.js +6 -6
- package/dist/node-shims-artifact.generated.js +3 -3
- package/dist/npm/install-batch-facet.d.ts +1 -1
- package/dist/npm/install-batch-facet.js +1 -1
- package/dist/npm/installer.d.ts +6 -6
- package/dist/npm/installer.d.ts.map +1 -1
- package/dist/npm/installer.js +28 -27
- package/dist/npm/pre-bundle-facet.d.ts +2 -2
- package/dist/npm/pre-bundle-facet.js +5 -5
- package/dist/npm/r2-cache.d.ts +1 -1
- package/dist/npm/resolve-facet.d.ts +1 -1
- package/dist/npm/resolve-facet.js +1 -1
- package/dist/npm/resolve-one-facet.d.ts +5 -5
- package/dist/npm/resolve-one-facet.js +5 -5
- package/dist/router/index.d.ts.map +1 -1
- package/dist/router/index.js +61 -4
- package/dist/router/public-directory-do.d.ts +17 -0
- package/dist/router/public-directory-do.d.ts.map +1 -0
- package/dist/router/public-directory-do.js +16 -0
- package/dist/router/public-directory.d.ts +75 -0
- package/dist/router/public-directory.d.ts.map +1 -0
- package/dist/router/public-directory.js +115 -0
- package/dist/router/remote-api.d.ts +9 -1
- package/dist/router/remote-api.d.ts.map +1 -1
- package/dist/router/remote-api.js +20 -3
- package/dist/runtime/bun-repl.d.ts +1 -1
- package/dist/runtime/bun-repl.js +3 -3
- package/dist/runtime/esbuild-wasm-bytes.js +1 -1
- package/dist/runtime/facet-loader-host.d.ts +2 -2
- package/dist/runtime/facet-loader-host.js +3 -3
- package/dist/runtime/node-repl.js +2 -2
- package/dist/runtime/node-shims-artifact.js +1 -1
- package/dist/runtime/node-shims.d.ts +4 -1
- package/dist/runtime/node-shims.d.ts.map +1 -1
- package/dist/runtime/node-shims.js +326 -7
- package/dist/runtime/opencode-artifact.js +1 -1
- package/dist/runtime/opentui-wasm-bytes.js +1 -1
- package/dist/runtime/python-repl.js +3 -3
- package/dist/runtime/ruby-repl.js +2 -2
- package/dist/runtime/sqlite-wasm-bytes.js +1 -1
- package/dist/session/agent.d.ts +11 -0
- package/dist/session/agent.d.ts.map +1 -1
- package/dist/session/diag.js +1 -1
- package/dist/session/helpers.js +1 -1
- package/dist/session/hibernation.d.ts +8 -8
- package/dist/session/hibernation.d.ts.map +1 -1
- package/dist/session/hibernation.js +15 -15
- package/dist/session/init-phases.d.ts +19 -26
- package/dist/session/init-phases.d.ts.map +1 -1
- package/dist/session/init-phases.js +20 -43
- package/dist/session/init.d.ts.map +1 -1
- package/dist/session/init.js +24 -19
- package/dist/session/keys.d.ts +8 -0
- package/dist/session/keys.d.ts.map +1 -1
- package/dist/session/keys.js +12 -3
- package/dist/session/nimbus-session.d.ts +33 -19
- package/dist/session/nimbus-session.d.ts.map +1 -1
- package/dist/session/nimbus-session.js +78 -37
- package/dist/session/port-capability.d.ts +95 -8
- package/dist/session/port-capability.d.ts.map +1 -1
- package/dist/session/port-capability.js +165 -8
- package/dist/session/programmatic.d.ts +50 -4
- package/dist/session/programmatic.d.ts.map +1 -1
- package/dist/session/programmatic.js +112 -15
- package/dist/session/routes.d.ts +17 -0
- package/dist/session/routes.d.ts.map +1 -1
- package/dist/session/routes.js +111 -43
- package/dist/session/rpc.d.ts +7 -7
- package/dist/session/rpc.d.ts.map +1 -1
- package/dist/session/rpc.js +34 -23
- package/dist/session/shell-socket.d.ts +76 -0
- package/dist/session/shell-socket.d.ts.map +1 -0
- package/dist/session/shell-socket.js +172 -0
- package/dist/session/start-real-vite.js +2 -2
- package/dist/session/supervisor-op.d.ts +5 -0
- package/dist/session/supervisor-op.d.ts.map +1 -0
- package/dist/session/supervisor-op.js +74 -0
- package/dist/session/supervisor-rpc.d.ts +6 -10
- package/dist/session/supervisor-rpc.d.ts.map +1 -1
- package/dist/session/supervisor-rpc.js +98 -79
- package/dist/session/ws.d.ts +1 -3
- package/dist/session/ws.d.ts.map +1 -1
- package/dist/session/ws.js +23 -7
- package/package.json +4 -3
- package/public/_assets/runtime/{node-shims-fea906843f6d594e.js → node-shims-9e1380d9ee430fcc.js} +320 -5
- package/public/s/index.html +101 -5
- package/scripts/bundle-esbuild-wasm.mjs +2 -2
- package/scripts/bundle-facet-workers.mjs +5 -7
- package/scripts/bundle-sqlite-wasm.mjs +1 -1
package/README.md
CHANGED
|
@@ -1,14 +1,18 @@
|
|
|
1
1
|
# @nimbus-sh/worker
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
Run a Linux-like sandbox on Cloudflare Workers. A session gets a filesystem,
|
|
4
|
+
a shell, real processes, and ports you can reach from a browser, all inside
|
|
5
|
+
one Durable Object.
|
|
6
|
+
|
|
7
|
+
This package is the Cloudflare half: the `NimbusSession` Durable Object, the
|
|
8
|
+
router, the static assets, the facet machinery, and the auth internals. The
|
|
9
|
+
filesystem, shell, and WASI runtime layer live in
|
|
6
10
|
[`@nimbus-sh/core`](https://www.npmjs.com/package/@nimbus-sh/core), which
|
|
7
|
-
this package composes on
|
|
11
|
+
this package composes on. Core also runs standalone in bun or node.
|
|
8
12
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
13
|
+
Import the deploy-time API from `@nimbus-sh/sdk/worker`. Install this package
|
|
14
|
+
as well, because it carries the runtime implementation and the static assets
|
|
15
|
+
that entrypoint serves.
|
|
12
16
|
|
|
13
17
|
## Install
|
|
14
18
|
|
|
@@ -123,9 +127,9 @@ Cloudflare Dashboard once for the account, then rerun the setup command.
|
|
|
123
127
|
single-tenant demos can set `NIMBUS_LEGACY_PUBLIC=1` and rely on URL
|
|
124
128
|
possession instead.
|
|
125
129
|
|
|
126
|
-
##
|
|
130
|
+
## createNimbusHandler
|
|
127
131
|
|
|
128
|
-
|
|
132
|
+
The handler owns the router, auth, and the session protocol. Its options:
|
|
129
133
|
|
|
130
134
|
```ts
|
|
131
135
|
{
|
|
@@ -144,8 +148,8 @@ possession instead.
|
|
|
144
148
|
|
|
145
149
|
### Custom routes
|
|
146
150
|
|
|
147
|
-
|
|
148
|
-
for a token-mint endpoint, `/healthz`, SDK smoke tests, or backend
|
|
151
|
+
A route that returns anything but `null` short-circuits Nimbus's router. Use
|
|
152
|
+
one for a token-mint endpoint, `/healthz`, SDK smoke tests, or backend
|
|
149
153
|
sandbox jobs.
|
|
150
154
|
|
|
151
155
|
```ts
|
|
@@ -209,8 +213,7 @@ Applications normally call it through `Nimbus.connect({ endpoint, token,
|
|
|
209
213
|
config })`. The route requires a valid Nimbus JWT and `sandbox:use` scope.
|
|
210
214
|
`box.destroy()` additionally requires `session:destroy` or `session:admin`.
|
|
211
215
|
|
|
212
|
-
For
|
|
213
|
-
expose the virtual port:
|
|
216
|
+
For an app server, start the process and expose its virtual port:
|
|
214
217
|
|
|
215
218
|
```ts
|
|
216
219
|
await box.files.write('/home/user/app/server.js', serverSource);
|
|
@@ -220,19 +223,39 @@ const port = await box.ports.expose(3000);
|
|
|
220
223
|
// box.processes.kill(proc.pid) stops it
|
|
221
224
|
```
|
|
222
225
|
|
|
226
|
+
### Hooks
|
|
227
|
+
|
|
228
|
+
```ts
|
|
229
|
+
export default createNimbusHandler({
|
|
230
|
+
hooks: {
|
|
231
|
+
onSessionStart: ({ sessionId, tenantSegment, request }) => {
|
|
232
|
+
console.log(`[${tenantSegment}] session ${sessionId} attached from ${request.headers.get('cf-connecting-ip')}`);
|
|
233
|
+
},
|
|
234
|
+
},
|
|
235
|
+
});
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
### Auth modes
|
|
239
|
+
|
|
240
|
+
| Mode | Meaning |
|
|
241
|
+
|---|---|
|
|
242
|
+
| `'auto'` (default) | Verify token when `JWT_SECRET` is set AND `NIMBUS_LEGACY_PUBLIC` is unset. Otherwise legacy-public. |
|
|
243
|
+
| `'enforce'` | Always verify token; fail closed if `JWT_SECRET` is missing. |
|
|
244
|
+
| `'legacy'` | Never verify; all requests route to the single `legacy:public:_` tenant. Use only for single-tenant demos. |
|
|
245
|
+
|
|
223
246
|
## Session Agent
|
|
224
247
|
|
|
225
|
-
The bundled session shell
|
|
226
|
-
workspace.
|
|
227
|
-
`/api/agent
|
|
248
|
+
The bundled session shell carries an agent surface inside the editor
|
|
249
|
+
workspace. Its route lives in the session Durable Object under
|
|
250
|
+
`/api/agent/*`. The stable Cloudflare OAuth callback is
|
|
228
251
|
`/api/nimbus/oauth/callback`.
|
|
229
252
|
|
|
230
|
-
The agent
|
|
231
|
-
|
|
232
|
-
|
|
253
|
+
The agent uses the same session tools as the SDK: shell exec, files, runtime
|
|
254
|
+
installs, long-running processes, logs, and preview ports. Model calls use
|
|
255
|
+
the AI SDK with Cloudflare Workers AI's OpenAI-compatible endpoint and an
|
|
233
256
|
optional AI Gateway name.
|
|
234
257
|
|
|
235
|
-
For user-owned quota, create a Cloudflare OAuth client
|
|
258
|
+
For user-owned quota, create a Cloudflare OAuth client. Use response type
|
|
236
259
|
`Code`, grant type `Authorization Code`, token authentication method `None`,
|
|
237
260
|
and redirect URL `https://<your-nimbus-host>/api/nimbus/oauth/callback`.
|
|
238
261
|
Nimbus uses PKCE and stores user OAuth tokens only in encrypted `HttpOnly`,
|
|
@@ -257,26 +280,6 @@ npx wrangler secret put NIMBUS_AGENT_COOKIE_SECRET
|
|
|
257
280
|
npx wrangler secret put NIMBUS_CLOUDFLARE_API_TOKEN
|
|
258
281
|
```
|
|
259
282
|
|
|
260
|
-
### Hooks
|
|
261
|
-
|
|
262
|
-
```ts
|
|
263
|
-
export default createNimbusHandler({
|
|
264
|
-
hooks: {
|
|
265
|
-
onSessionStart: ({ sessionId, tenantSegment, request }) => {
|
|
266
|
-
console.log(`[${tenantSegment}] session ${sessionId} attached from ${request.headers.get('cf-connecting-ip')}`);
|
|
267
|
-
},
|
|
268
|
-
},
|
|
269
|
-
});
|
|
270
|
-
```
|
|
271
|
-
|
|
272
|
-
### Auth modes
|
|
273
|
-
|
|
274
|
-
| Mode | Meaning |
|
|
275
|
-
|---|---|
|
|
276
|
-
| `'auto'` (default) | Verify token when `JWT_SECRET` is set AND `NIMBUS_LEGACY_PUBLIC` is unset. Otherwise legacy-public. |
|
|
277
|
-
| `'enforce'` | Always verify token; fail closed if `JWT_SECRET` is missing. |
|
|
278
|
-
| `'legacy'` | Never verify; all requests route to the single `legacy:public:_` tenant. Use only for single-tenant demos. |
|
|
279
|
-
|
|
280
283
|
## Subpath exports
|
|
281
284
|
|
|
282
285
|
| Subpath | What |
|
|
@@ -288,7 +291,7 @@ export default createNimbusHandler({
|
|
|
288
291
|
|
|
289
292
|
## Required bindings
|
|
290
293
|
|
|
291
|
-
|
|
294
|
+
None of these is optional. See `apps/hosted-demo/wrangler.jsonc` or
|
|
292
295
|
`@nimbus-sh/config` for the canonical set:
|
|
293
296
|
|
|
294
297
|
- `NIMBUS_SESSION` (Durable Object) — per-session SQLite state
|
|
@@ -299,7 +302,7 @@ Every binding is load-bearing — see `apps/hosted-demo/wrangler.jsonc` or
|
|
|
299
302
|
|
|
300
303
|
## Status
|
|
301
304
|
|
|
302
|
-
v0.1
|
|
303
|
-
changes through v0.x. Issues + PRs welcome.
|
|
305
|
+
v0.1 is the first public release. SemVer is not stable yet, so expect
|
|
306
|
+
breaking changes through v0.x. Issues + PRs welcome.
|
|
304
307
|
|
|
305
308
|
MIT. © Ashish Kumar Singh + contributors.
|
|
@@ -14,9 +14,22 @@
|
|
|
14
14
|
export interface PreviewHost {
|
|
15
15
|
port: number;
|
|
16
16
|
sid: string;
|
|
17
|
+
/**
|
|
18
|
+
* Present only on the public capability form `<cap>--<port>--<sid>`: the
|
|
19
|
+
* bearer is the capability itself, so the request skips session-attach
|
|
20
|
+
* auth entirely — the session decides by the port's stored visibility.
|
|
21
|
+
*/
|
|
22
|
+
capability?: string;
|
|
17
23
|
}
|
|
18
|
-
export declare function isPreviewHostSafeSid(sid: string): boolean;
|
|
19
24
|
export declare function buildPreviewHost(sid: string, port: number, suffix: string): string;
|
|
25
|
+
/**
|
|
26
|
+
* `<capability>--<port>--<sid>.<suffix>` — the unauthenticated sibling of
|
|
27
|
+
* `buildPreviewHost`, for applications whose visibility is `public`. The
|
|
28
|
+
* capability is the bearer: 24 lowercase hex, the same shape the port
|
|
29
|
+
* registry mints.
|
|
30
|
+
*/
|
|
31
|
+
export declare function buildPublicPreviewHost(sid: string, port: number, capability: string, suffix: string): string;
|
|
32
|
+
export declare function isPreviewHostSafeSid(sid: string): boolean;
|
|
20
33
|
/**
|
|
21
34
|
* Read the configured preview-host suffix out of a bindings env.
|
|
22
35
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"preview-host.d.ts","sourceRoot":"","sources":["../../src/_shared/preview-host.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;
|
|
1
|
+
{"version":3,"file":"preview-host.d.ts","sourceRoot":"","sources":["../../src/_shared/preview-host.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAcH,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAElF;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,CACpC,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,MAAM,EACZ,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,MAAM,GACb,MAAM,CAER;AAED,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAEzD;AAED;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAGjE;AAED,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,GAChC,WAAW,GAAG,IAAI,CAgCpB;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,OAAO,GAAG,OAAO,CAEpE"}
|
|
@@ -14,14 +14,28 @@
|
|
|
14
14
|
const PREVIEW_HOST_SAFE_SID_RE = /^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/;
|
|
15
15
|
/** Canonical port form only: no leading zeros, so `03000--x` is not a host. */
|
|
16
16
|
const PREVIEW_HOST_LABEL_RE = /^(0|[1-9]\d*)--(.+)$/;
|
|
17
|
+
// The public capability form: the bearer is the capability itself, no
|
|
18
|
+
// attach token and no embedder credential ever crosses this hostname.
|
|
19
|
+
// The label carries everything a request needs — which port, which
|
|
20
|
+
// session, and which token — so it works with no server-side lookup.
|
|
21
|
+
const PREVIEW_CAPABILITY_HOST_LABEL_RE = /^([a-f0-9]{24})--(\d{1,5})--([a-z0-9-]{1,63})$/;
|
|
17
22
|
/** Binding that carries the deployment's preview-host suffix. */
|
|
18
23
|
const PREVIEW_HOST_SUFFIX_BINDING = 'NIMBUS_PREVIEW_HOST_SUFFIX';
|
|
19
|
-
export function isPreviewHostSafeSid(sid) {
|
|
20
|
-
return sid.length <= 56 && PREVIEW_HOST_SAFE_SID_RE.test(sid);
|
|
21
|
-
}
|
|
22
24
|
export function buildPreviewHost(sid, port, suffix) {
|
|
23
25
|
return `${port}--${sid}.${suffix}`;
|
|
24
26
|
}
|
|
27
|
+
/**
|
|
28
|
+
* `<capability>--<port>--<sid>.<suffix>` — the unauthenticated sibling of
|
|
29
|
+
* `buildPreviewHost`, for applications whose visibility is `public`. The
|
|
30
|
+
* capability is the bearer: 24 lowercase hex, the same shape the port
|
|
31
|
+
* registry mints.
|
|
32
|
+
*/
|
|
33
|
+
export function buildPublicPreviewHost(sid, port, capability, suffix) {
|
|
34
|
+
return `${capability}--${port}--${sid}.${suffix}`;
|
|
35
|
+
}
|
|
36
|
+
export function isPreviewHostSafeSid(sid) {
|
|
37
|
+
return sid.length <= 56 && PREVIEW_HOST_SAFE_SID_RE.test(sid);
|
|
38
|
+
}
|
|
25
39
|
/**
|
|
26
40
|
* Read the configured preview-host suffix out of a bindings env.
|
|
27
41
|
*
|
|
@@ -46,6 +60,17 @@ export function parsePreviewHost(host, suffix) {
|
|
|
46
60
|
const label = normalizedHost.slice(0, -suffixWithDot.length);
|
|
47
61
|
if (!label || label.includes('.'))
|
|
48
62
|
return null;
|
|
63
|
+
// The capability form is checked first: its leading 24-hex run would
|
|
64
|
+
// otherwise parse as the port of the legacy form's widest match.
|
|
65
|
+
const capabilityMatch = label.match(PREVIEW_CAPABILITY_HOST_LABEL_RE);
|
|
66
|
+
if (capabilityMatch) {
|
|
67
|
+
const capability = capabilityMatch[1];
|
|
68
|
+
const port = Number(capabilityMatch[2]);
|
|
69
|
+
const sid = capabilityMatch[3];
|
|
70
|
+
if (port < 1 || port > 65535 || !isPreviewHostSafeSid(sid))
|
|
71
|
+
return null;
|
|
72
|
+
return { port, sid, capability };
|
|
73
|
+
}
|
|
49
74
|
const match = label.match(PREVIEW_HOST_LABEL_RE);
|
|
50
75
|
if (!match)
|
|
51
76
|
return null;
|
|
@@ -51,6 +51,23 @@ export declare const TENANT_HEADER = "X-Nimbus-Tenant";
|
|
|
51
51
|
* `Authorization` is preserved — the one route on which that is true.
|
|
52
52
|
*/
|
|
53
53
|
export declare const PREVIEW_CAPABILITY_HEADER = "x-nimbus-preview-capability";
|
|
54
|
+
/**
|
|
55
|
+
* Header the Worker sets when a request arrived on the public capability
|
|
56
|
+
* host form `<cap>--<port>--<sid>` — the request was never attached to a
|
|
57
|
+
* session, so the capability in `PREVIEW_CAPABILITY_HEADER` is the only
|
|
58
|
+
* credential it carries, and the session may honor it ONLY for a port whose
|
|
59
|
+
* stored visibility is `public`. Any other request answering on that mark
|
|
60
|
+
* is 404.
|
|
61
|
+
*/
|
|
62
|
+
export declare const PUBLIC_BEARER_HEADER = "x-nimbus-public-bearer";
|
|
63
|
+
/**
|
|
64
|
+
* Header the Worker sets carrying the caller's verified token scopes, so the
|
|
65
|
+
* DO can gate routes that need more than session attach (e.g. the _diag
|
|
66
|
+
* abort). The header is deleted from the caller's own request before the
|
|
67
|
+
* verified value is set — a forged inbound copy never survives forwarding.
|
|
68
|
+
* Absent/empty in legacy mode, where no scopes were verified.
|
|
69
|
+
*/
|
|
70
|
+
export declare const CALLER_SCOPES_HEADER = "x-nimbus-caller-scopes";
|
|
54
71
|
/**
|
|
55
72
|
* DO-name segment used when tenant scoping is disabled (legacy-public).
|
|
56
73
|
* Picked so it cannot collide with a verified token's
|
|
@@ -85,6 +102,17 @@ export declare function parseSessionRoute(pathname: string): ParsedSessionRoute
|
|
|
85
102
|
export interface ForwardOptions {
|
|
86
103
|
/** Verified tenant segment for DO naming. */
|
|
87
104
|
tenantSegment: string;
|
|
105
|
+
/**
|
|
106
|
+
* Router-minted headers the DO must see — the preview capability, the
|
|
107
|
+
* public-bearer mark. Entries arrive only from code that vetted them.
|
|
108
|
+
*/
|
|
109
|
+
extraHeaders?: Readonly<Record<string, string>>;
|
|
110
|
+
/**
|
|
111
|
+
* The caller's verified token scopes. Forwarded as
|
|
112
|
+
* {@link CALLER_SCOPES_HEADER} so scope-gated routes inside the DO can
|
|
113
|
+
* check them. Omitted in legacy mode or when the caller was not verified.
|
|
114
|
+
*/
|
|
115
|
+
callerScopes?: readonly string[];
|
|
88
116
|
}
|
|
89
117
|
/**
|
|
90
118
|
* Forward a request to the session's DO.
|
|
@@ -106,6 +134,21 @@ export interface ForwardOptions {
|
|
|
106
134
|
* @param opts Tenant scoping. See {@link ForwardOptions}.
|
|
107
135
|
*/
|
|
108
136
|
export declare function forwardToSession(request: Request, route: ParsedSessionRoute, env: any, opts: ForwardOptions): Promise<Response>;
|
|
137
|
+
/**
|
|
138
|
+
* The card every session-facing error/status page shares: dark, centered,
|
|
139
|
+
* mono title. `metaRefreshSeconds` opts the page into self-refresh — the
|
|
140
|
+
* "starting" page re-asks on its own timer, the invalid page never does.
|
|
141
|
+
*/
|
|
142
|
+
export declare function renderSessionStatusPage(input: {
|
|
143
|
+
title: string;
|
|
144
|
+
heading: string;
|
|
145
|
+
body: string;
|
|
146
|
+
action?: {
|
|
147
|
+
href: string;
|
|
148
|
+
label: string;
|
|
149
|
+
};
|
|
150
|
+
metaRefreshSeconds?: number;
|
|
151
|
+
}): string;
|
|
109
152
|
/** HTML body for the "invalid session ID" 400 page. Tiny, inline-only. */
|
|
110
153
|
export declare function renderInvalidSessionHtml(attemptedId: string): string;
|
|
111
154
|
//# sourceMappingURL=session-router.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session-router.d.ts","sourceRoot":"","sources":["../../src/_shared/session-router.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAIH,8EAA8E;AAC9E,eAAO,MAAM,oBAAoB,OAAO,CAAC;AAEzC,qEAAqE;AACrE,eAAO,MAAM,gBAAgB,kBAAkB,CAAC;AAEhD;;;;GAIG;AACH,eAAO,MAAM,aAAa,oBAAoB,CAAC;AAE/C;;;;;;GAMG;AACH,eAAO,MAAM,yBAAyB,gCAAgC,CAAC;AAEvE;;;;;GAKG;AACH,eAAO,MAAM,wBAAwB,oBAAoB,CAAC;AAS1D,MAAM,WAAW,kBAAkB;IACjC,sEAAsE;IACtE,SAAS,EAAE,MAAM,CAAC;IAClB,kFAAkF;IAClF,SAAS,EAAE,MAAM,CAAC;IAClB,4EAA4E;IAC5E,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,kBAAkB,GAAG,IAAI,CAa7E;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,cAAc;IAC7B,6CAA6C;IAC7C,aAAa,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"session-router.d.ts","sourceRoot":"","sources":["../../src/_shared/session-router.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAIH,8EAA8E;AAC9E,eAAO,MAAM,oBAAoB,OAAO,CAAC;AAEzC,qEAAqE;AACrE,eAAO,MAAM,gBAAgB,kBAAkB,CAAC;AAEhD;;;;GAIG;AACH,eAAO,MAAM,aAAa,oBAAoB,CAAC;AAE/C;;;;;;GAMG;AACH,eAAO,MAAM,yBAAyB,gCAAgC,CAAC;AAEvE;;;;;;;GAOG;AACH,eAAO,MAAM,oBAAoB,2BAA2B,CAAC;AAE7D;;;;;;GAMG;AACH,eAAO,MAAM,oBAAoB,2BAA2B,CAAC;AAE7D;;;;;GAKG;AACH,eAAO,MAAM,wBAAwB,oBAAoB,CAAC;AAS1D,MAAM,WAAW,kBAAkB;IACjC,sEAAsE;IACtE,SAAS,EAAE,MAAM,CAAC;IAClB,kFAAkF;IAClF,SAAS,EAAE,MAAM,CAAC;IAClB,4EAA4E;IAC5E,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,kBAAkB,GAAG,IAAI,CAa7E;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,cAAc;IAC7B,6CAA6C;IAC7C,aAAa,EAAE,MAAM,CAAC;IACtB;;;OAGG;IACH,YAAY,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IAChD;;;;OAIG;IACH,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAClC;AACD;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,OAAO,EAChB,KAAK,EAAE,kBAAkB,EACzB,GAAG,EAAE,GAAG,EACR,IAAI,EAAE,cAAc,GACnB,OAAO,CAAC,QAAQ,CAAC,CA8CnB;AAED;;;;GAIG;AACH,wBAAgB,uBAAuB,CAAC,KAAK,EAAE;IAC7C,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;IACzC,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC7B,GAAG,MAAM,CA8BT;AAED,0EAA0E;AAC1E,wBAAgB,wBAAwB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAapE"}
|
|
@@ -52,6 +52,23 @@ export const TENANT_HEADER = 'X-Nimbus-Tenant';
|
|
|
52
52
|
* `Authorization` is preserved — the one route on which that is true.
|
|
53
53
|
*/
|
|
54
54
|
export const PREVIEW_CAPABILITY_HEADER = 'x-nimbus-preview-capability';
|
|
55
|
+
/**
|
|
56
|
+
* Header the Worker sets when a request arrived on the public capability
|
|
57
|
+
* host form `<cap>--<port>--<sid>` — the request was never attached to a
|
|
58
|
+
* session, so the capability in `PREVIEW_CAPABILITY_HEADER` is the only
|
|
59
|
+
* credential it carries, and the session may honor it ONLY for a port whose
|
|
60
|
+
* stored visibility is `public`. Any other request answering on that mark
|
|
61
|
+
* is 404.
|
|
62
|
+
*/
|
|
63
|
+
export const PUBLIC_BEARER_HEADER = 'x-nimbus-public-bearer';
|
|
64
|
+
/**
|
|
65
|
+
* Header the Worker sets carrying the caller's verified token scopes, so the
|
|
66
|
+
* DO can gate routes that need more than session attach (e.g. the _diag
|
|
67
|
+
* abort). The header is deleted from the caller's own request before the
|
|
68
|
+
* verified value is set — a forged inbound copy never survives forwarding.
|
|
69
|
+
* Absent/empty in legacy mode, where no scopes were verified.
|
|
70
|
+
*/
|
|
71
|
+
export const CALLER_SCOPES_HEADER = 'x-nimbus-caller-scopes';
|
|
55
72
|
/**
|
|
56
73
|
* DO-name segment used when tenant scoping is disabled (legacy-public).
|
|
57
74
|
* Picked so it cannot collide with a verified token's
|
|
@@ -115,6 +132,16 @@ export function forwardToSession(request, route, env, opts) {
|
|
|
115
132
|
const headers = new Headers(request.headers);
|
|
116
133
|
headers.set(BASE_PATH_HEADER, route.basePath);
|
|
117
134
|
headers.set(TENANT_HEADER, opts.tenantSegment);
|
|
135
|
+
// Verified scopes come from the router alone: a caller-supplied copy is
|
|
136
|
+
// deleted before the real value is set, never appended to.
|
|
137
|
+
headers.delete(CALLER_SCOPES_HEADER);
|
|
138
|
+
if (opts.callerScopes !== undefined) {
|
|
139
|
+
headers.set(CALLER_SCOPES_HEADER, opts.callerScopes.join(' '));
|
|
140
|
+
}
|
|
141
|
+
if (opts.extraHeaders) {
|
|
142
|
+
for (const [name, value] of Object.entries(opts.extraHeaders))
|
|
143
|
+
headers.set(name, value);
|
|
144
|
+
}
|
|
118
145
|
// Load-bearing, not hygiene: the query is a first-class auth channel
|
|
119
146
|
// (`extractBearerToken` reads it), so anything forwarded with the token
|
|
120
147
|
// still attached would carry a live credential into inner-DO logs, the
|
|
@@ -142,18 +169,23 @@ export function forwardToSession(request, route, env, opts) {
|
|
|
142
169
|
const stub = env.NIMBUS_SESSION.get(id);
|
|
143
170
|
return stub.fetch(inner);
|
|
144
171
|
}
|
|
145
|
-
/**
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
172
|
+
/**
|
|
173
|
+
* The card every session-facing error/status page shares: dark, centered,
|
|
174
|
+
* mono title. `metaRefreshSeconds` opts the page into self-refresh — the
|
|
175
|
+
* "starting" page re-asks on its own timer, the invalid page never does.
|
|
176
|
+
*/
|
|
177
|
+
export function renderSessionStatusPage(input) {
|
|
178
|
+
const refresh = input.metaRefreshSeconds !== undefined
|
|
179
|
+
? `<meta http-equiv="refresh" content="${input.metaRefreshSeconds}">`
|
|
180
|
+
: '';
|
|
181
|
+
const action = input.action !== undefined
|
|
182
|
+
? `<a class="btn" href="${input.action.href}">${input.action.label}</a>`
|
|
183
|
+
: '';
|
|
153
184
|
return `<!DOCTYPE html>
|
|
154
185
|
<html lang="en"><head>
|
|
155
186
|
<meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1">
|
|
156
|
-
|
|
187
|
+
${refresh}
|
|
188
|
+
<title>${input.title} — Nimbus</title>
|
|
157
189
|
<style>
|
|
158
190
|
*{margin:0;padding:0;box-sizing:border-box}
|
|
159
191
|
html,body{height:100%}
|
|
@@ -168,8 +200,23 @@ export function renderInvalidSessionHtml(attemptedId) {
|
|
|
168
200
|
a.btn:hover{filter:brightness(1.1)}
|
|
169
201
|
</style></head>
|
|
170
202
|
<body><div class="card">
|
|
171
|
-
<h1
|
|
172
|
-
<p
|
|
173
|
-
|
|
203
|
+
<h1>${input.heading}</h1>
|
|
204
|
+
<p>${input.body}</p>
|
|
205
|
+
${action}
|
|
174
206
|
</div></body></html>`;
|
|
175
207
|
}
|
|
208
|
+
/** HTML body for the "invalid session ID" 400 page. Tiny, inline-only. */
|
|
209
|
+
export function renderInvalidSessionHtml(attemptedId) {
|
|
210
|
+
// Escape the attempted ID for display. We don't use innerHTML anywhere,
|
|
211
|
+
// but defensive escaping keeps the HTML validator happy and avoids any
|
|
212
|
+
// future XSS footguns if someone refactors this to document.write().
|
|
213
|
+
const safe = String(attemptedId).replace(/[&<>"']/g, (c) => ({
|
|
214
|
+
'&': '&', '<': '<', '>': '>', '"': '"', "'": ''',
|
|
215
|
+
}[c]));
|
|
216
|
+
return renderSessionStatusPage({
|
|
217
|
+
title: 'Invalid session',
|
|
218
|
+
heading: 'Invalid session',
|
|
219
|
+
body: `The ID <code>${safe}</code> isn’t a valid Nimbus session URL.<br>Launch a new one to get started.`,
|
|
220
|
+
action: { href: '/', label: '← Back to Nimbus' },
|
|
221
|
+
});
|
|
222
|
+
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* esbuild-wasm-bundle.generated.ts — AUTO-GENERATED by scripts/bundle-esbuild-wasm.mjs
|
|
3
3
|
* DO NOT EDIT.
|
|
4
4
|
*
|
|
5
|
-
* Bundled esbuild-wasm 0.24.2 adapter for use inside
|
|
5
|
+
* Bundled esbuild-wasm 0.24.2 adapter for use inside NimbusIsolatePool
|
|
6
6
|
* isolates. Only the JS adapter is bundled here (~117 KiB); the wasm
|
|
7
7
|
* binary itself lives in the static-assets layer at
|
|
8
8
|
* public/_assets/esbuild-0.24.2.wasm and is fetched on demand by
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* esbuild-wasm-bundle.generated.ts — AUTO-GENERATED by scripts/bundle-esbuild-wasm.mjs
|
|
3
3
|
* DO NOT EDIT.
|
|
4
4
|
*
|
|
5
|
-
* Bundled esbuild-wasm 0.24.2 adapter for use inside
|
|
5
|
+
* Bundled esbuild-wasm 0.24.2 adapter for use inside NimbusIsolatePool
|
|
6
6
|
* isolates. Only the JS adapter is bundled here (~117 KiB); the wasm
|
|
7
7
|
* binary itself lives in the static-assets layer at
|
|
8
8
|
* public/_assets/esbuild-0.24.2.wasm and is fetched on demand by
|
|
@@ -57,7 +57,7 @@ import { REAL_VITE_VERSION, VITE_CLIENT_MJS, VITE_ENV_MJS, getRealViteBundle, ge
|
|
|
57
57
|
import { CIRRUS_PLUGIN_REACT_VERSION, getCirrusPluginReactBundle, } from '../cirrus-plugin-react.generated.js';
|
|
58
58
|
import { CIRRUS_NPM_CJS_VERSIONS, getCirrusNpmCjsBundles } from '../cirrus-npm-cjs.generated.js';
|
|
59
59
|
import { CF_COMPAT_DATE } from '@nimbus-sh/core/constants.js';
|
|
60
|
-
import { getCtxExports } from '@nimbus-sh/fabric/
|
|
60
|
+
import { getCtxExports } from '@nimbus-sh/fabric/composition.js';
|
|
61
61
|
import { buildFsSnapshot, generateFsShimModuleCode, generateFsPromisesShimModuleCode, generateSyntheticModuleCode, } from './real-vite-fs-shim.js';
|
|
62
62
|
import { HmrBridge, registerHmrBridge, generateWsShimModuleCode, generateChokidarShimModuleCode, } from './real-vite-hmr.js';
|
|
63
63
|
import { CRED_KERNEL } from '@nimbus-sh/core/runtime/os-contracts.js';
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* facets/durable-images.ts — the durable application's boot-image store.
|
|
3
|
+
*
|
|
4
|
+
* A durable spawn's journal row carries only digests — the launch's code,
|
|
5
|
+
* modules, and environment have to live somewhere a reset can still read
|
|
6
|
+
* them, and that is the kernel-space VFS under `.nimbus/images/<sha256>`,
|
|
7
|
+
* content-addressed so a re-drive with the same recipe reads the same bytes.
|
|
8
|
+
*
|
|
9
|
+
* The store is deliberately separate from the process boot-image sweep in
|
|
10
|
+
* `image-store.ts`: that sweep is rooted at live pids and would collect an
|
|
11
|
+
* application's image the moment its process ends — the precise condition a
|
|
12
|
+
* durable spawn exists to survive. Durable images are kept for the
|
|
13
|
+
* application's life and released only by explicit removal.
|
|
14
|
+
*
|
|
15
|
+
* Two blobs per launch: `runner` is the worker.js source text, `application`
|
|
16
|
+
* is a JSON payload of `{ modules, env, vfsWasmModules }`. The digests in the
|
|
17
|
+
* journal's `recipe.image` are sha256 hashes of those two payloads, and a
|
|
18
|
+
* self-owned spawn mints them at spawn time; an embedder-owned spawn is given
|
|
19
|
+
* them by its own bookkeeping instead.
|
|
20
|
+
*/
|
|
21
|
+
import type { SqliteVFS } from '@nimbus-sh/core/vfs/sqlite-vfs.js';
|
|
22
|
+
import type { ResidentCodeSpec } from '@nimbus-sh/fabric/process-fabric.js';
|
|
23
|
+
import type { ResolvedWorkerLaunch, WorkerRecipe } from './manager.js';
|
|
24
|
+
/** The directory every durable application's image blobs live under. */
|
|
25
|
+
export declare const DURABLE_IMAGE_DIR = ".nimbus/images";
|
|
26
|
+
/**
|
|
27
|
+
* Persist a launch's image blobs, minting their digests, for a self-owned
|
|
28
|
+
* durable spawn. Reads and writes as CRED_KERNEL: the directory is session
|
|
29
|
+
* kernel data, not user content, and a durable application's images must not
|
|
30
|
+
* be writable — or deletable — by the user process they belong to.
|
|
31
|
+
*/
|
|
32
|
+
export declare function persistDurableWorkerImage(vfs: SqliteVFS, workerCode: string, payload: {
|
|
33
|
+
modules: Record<string, string | {
|
|
34
|
+
wasm: ArrayBuffer;
|
|
35
|
+
}>;
|
|
36
|
+
env?: ResidentCodeSpec['env'];
|
|
37
|
+
vfsWasmModules?: Record<string, string>;
|
|
38
|
+
}): Promise<{
|
|
39
|
+
runner: string;
|
|
40
|
+
application: string;
|
|
41
|
+
}>;
|
|
42
|
+
/**
|
|
43
|
+
* The default resolver a self-owned durable spawn answers through: read the
|
|
44
|
+
* two blobs the spawn persisted, restore the env, and hand back the launch a
|
|
45
|
+
* re-drive can boot. Returns null only when an image row has gone missing —
|
|
46
|
+
* which a re-drive treats as 'the application is gone', exactly as an
|
|
47
|
+
* embedder-owned launch whose embedder answers null.
|
|
48
|
+
*/
|
|
49
|
+
export declare function resolveDurableWorkerImage(vfs: SqliteVFS, recipe: WorkerRecipe): Promise<ResolvedWorkerLaunch | null>;
|
|
50
|
+
//# sourceMappingURL=durable-images.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"durable-images.d.ts","sourceRoot":"","sources":["../../src/facets/durable-images.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,mCAAmC,CAAC;AAEnE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,qCAAqC,CAAC;AAC5E,OAAO,KAAK,EAAE,oBAAoB,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAEvE,wEAAwE;AACxE,eAAO,MAAM,iBAAiB,mBAAmB,CAAC;AASlD;;;;;GAKG;AACH,wBAAsB,yBAAyB,CAC7C,GAAG,EAAE,SAAS,EACd,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE;IACP,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG;QAAE,IAAI,EAAE,WAAW,CAAA;KAAE,CAAC,CAAC;IACxD,GAAG,CAAC,EAAE,gBAAgB,CAAC,KAAK,CAAC,CAAC;IAC9B,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACzC,GACA,OAAO,CAAC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,CAAC,CAalD;AAED;;;;;;GAMG;AACH,wBAAsB,yBAAyB,CAC7C,GAAG,EAAE,SAAS,EACd,MAAM,EAAE,YAAY,GACnB,OAAO,CAAC,oBAAoB,GAAG,IAAI,CAAC,CAwBtC"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* facets/durable-images.ts — the durable application's boot-image store.
|
|
3
|
+
*
|
|
4
|
+
* A durable spawn's journal row carries only digests — the launch's code,
|
|
5
|
+
* modules, and environment have to live somewhere a reset can still read
|
|
6
|
+
* them, and that is the kernel-space VFS under `.nimbus/images/<sha256>`,
|
|
7
|
+
* content-addressed so a re-drive with the same recipe reads the same bytes.
|
|
8
|
+
*
|
|
9
|
+
* The store is deliberately separate from the process boot-image sweep in
|
|
10
|
+
* `image-store.ts`: that sweep is rooted at live pids and would collect an
|
|
11
|
+
* application's image the moment its process ends — the precise condition a
|
|
12
|
+
* durable spawn exists to survive. Durable images are kept for the
|
|
13
|
+
* application's life and released only by explicit removal.
|
|
14
|
+
*
|
|
15
|
+
* Two blobs per launch: `runner` is the worker.js source text, `application`
|
|
16
|
+
* is a JSON payload of `{ modules, env, vfsWasmModules }`. The digests in the
|
|
17
|
+
* journal's `recipe.image` are sha256 hashes of those two payloads, and a
|
|
18
|
+
* self-owned spawn mints them at spawn time; an embedder-owned spawn is given
|
|
19
|
+
* them by its own bookkeeping instead.
|
|
20
|
+
*/
|
|
21
|
+
import { CRED_KERNEL } from '@nimbus-sh/core/runtime/os-contracts.js';
|
|
22
|
+
/** The directory every durable application's image blobs live under. */
|
|
23
|
+
export const DURABLE_IMAGE_DIR = '.nimbus/images';
|
|
24
|
+
const imagePath = (digest) => `${DURABLE_IMAGE_DIR}/${digest}`;
|
|
25
|
+
async function sha256Hex(text) {
|
|
26
|
+
const digest = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(text));
|
|
27
|
+
return Array.from(new Uint8Array(digest), (b) => b.toString(16).padStart(2, '0')).join('');
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Persist a launch's image blobs, minting their digests, for a self-owned
|
|
31
|
+
* durable spawn. Reads and writes as CRED_KERNEL: the directory is session
|
|
32
|
+
* kernel data, not user content, and a durable application's images must not
|
|
33
|
+
* be writable — or deletable — by the user process they belong to.
|
|
34
|
+
*/
|
|
35
|
+
export async function persistDurableWorkerImage(vfs, workerCode, payload) {
|
|
36
|
+
const kernel = vfs.as(CRED_KERNEL);
|
|
37
|
+
kernel.mkdir(DURABLE_IMAGE_DIR, { recursive: true });
|
|
38
|
+
const runner = await sha256Hex(workerCode);
|
|
39
|
+
kernel.writeFile(imagePath(runner), workerCode);
|
|
40
|
+
const applicationPayload = JSON.stringify({
|
|
41
|
+
modules: payload.modules,
|
|
42
|
+
env: payload.env ?? null,
|
|
43
|
+
vfsWasmModules: payload.vfsWasmModules ?? null,
|
|
44
|
+
});
|
|
45
|
+
const application = await sha256Hex(applicationPayload);
|
|
46
|
+
kernel.writeFile(imagePath(application), applicationPayload);
|
|
47
|
+
return { runner, application };
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* The default resolver a self-owned durable spawn answers through: read the
|
|
51
|
+
* two blobs the spawn persisted, restore the env, and hand back the launch a
|
|
52
|
+
* re-drive can boot. Returns null only when an image row has gone missing —
|
|
53
|
+
* which a re-drive treats as 'the application is gone', exactly as an
|
|
54
|
+
* embedder-owned launch whose embedder answers null.
|
|
55
|
+
*/
|
|
56
|
+
export async function resolveDurableWorkerImage(vfs, recipe) {
|
|
57
|
+
const kernel = vfs.as(CRED_KERNEL);
|
|
58
|
+
let runnerBytes;
|
|
59
|
+
let applicationBytes;
|
|
60
|
+
try {
|
|
61
|
+
runnerBytes = kernel.readFile(imagePath(recipe.image.runner));
|
|
62
|
+
applicationBytes = kernel.readFile(imagePath(recipe.image.application));
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
const runner = new TextDecoder().decode(runnerBytes);
|
|
68
|
+
const { modules = {}, env = null, vfsWasmModules = undefined } = JSON.parse(new TextDecoder().decode(applicationBytes));
|
|
69
|
+
return {
|
|
70
|
+
env: env ?? null,
|
|
71
|
+
globalOutbound: undefined,
|
|
72
|
+
modules: { 'worker.js': runner, ...modules },
|
|
73
|
+
...(vfsWasmModules !== null ? { vfsWasmModules } : {}),
|
|
74
|
+
};
|
|
75
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* facets/durable-slots.ts — the durable application's facet-name allocator.
|
|
3
|
+
*
|
|
4
|
+
* A durable application's storage has to answer under ONE facet name for its
|
|
5
|
+
* whole life: abort ends the process, not the store, so relaunch and eviction
|
|
6
|
+
* re-attach the same SQLite — which is only true while the name is stable.
|
|
7
|
+
* The ephemeral slot book in `workerd-facet-host.ts` cannot supply it: it is
|
|
8
|
+
* in-memory, and a fresh incarnation restarts `proc-slot-<n>` numbering from
|
|
9
|
+
* zero, so a durable name needs a store of its own — the DO storage this file
|
|
10
|
+
* keeps it in.
|
|
11
|
+
*
|
|
12
|
+
* The rows:
|
|
13
|
+
*
|
|
14
|
+
* durable-slot:next — the lowest never-issued slot number. Minting burns
|
|
15
|
+
* one facet ID forever, so a freed name goes to
|
|
16
|
+
* `free`, never back to `next`.
|
|
17
|
+
* durable-slot:free — slot numbers whose applications were removed.
|
|
18
|
+
* durable-slot:<owner> — the owner's pinned slot. Written once, ever;
|
|
19
|
+
* re-read on every relaunch and re-drive.
|
|
20
|
+
*
|
|
21
|
+
* Names carry the `app-slot-` prefix, disjoint by construction from the
|
|
22
|
+
* ephemeral book's `proc-slot-` — the two namespaces share the facet-ID
|
|
23
|
+
* budget, and a collision would hand one application's retained storage to
|
|
24
|
+
* another process.
|
|
25
|
+
*/
|
|
26
|
+
/** The facet name a durable slot number names. */
|
|
27
|
+
export declare function durableFacetName(slot: number): string;
|
|
28
|
+
/**
|
|
29
|
+
* The owner's facet name, minted on first call and pinned for the
|
|
30
|
+
* application's life. The owner key, counter and free list move inside one
|
|
31
|
+
* transaction, so a concurrent spawn cannot split the claim, and a re-read
|
|
32
|
+
* after a reset — or after eviction — answers the same name.
|
|
33
|
+
*
|
|
34
|
+
* A fresh name records the mint against the lifetime facet-ID ledger so the
|
|
35
|
+
* budget a durable spawn consumes is counted the same way an ephemeral one's
|
|
36
|
+
* is.
|
|
37
|
+
*/
|
|
38
|
+
export declare function acquireDurableFacetSlot(ctx: DurableObjectState, owner: string): Promise<string>;
|
|
39
|
+
/**
|
|
40
|
+
* Hand the owner's slot back to the free list and drop its pin — the last
|
|
41
|
+
* step of explicit removal, after the facet's SQLite is already gone. Answers
|
|
42
|
+
* the freed name, or null when the owner held nothing.
|
|
43
|
+
*/
|
|
44
|
+
export declare function freeDurableFacetSlot(ctx: DurableObjectState, owner: string): Promise<string | null>;
|
|
45
|
+
//# sourceMappingURL=durable-slots.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"durable-slots.d.ts","sourceRoot":"","sources":["../../src/facets/durable-slots.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAUH,kDAAkD;AAClD,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAErD;AAED;;;;;;;;;GASG;AACH,wBAAsB,uBAAuB,CAC3C,GAAG,EAAE,kBAAkB,EACvB,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,MAAM,CAAC,CA4BjB;AAED;;;;GAIG;AACH,wBAAsB,oBAAoB,CACxC,GAAG,EAAE,kBAAkB,EACvB,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAYxB"}
|