@volter/world-core 3.0.162 → 3.0.164

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/src/events.ts CHANGED
@@ -7,11 +7,12 @@
7
7
  // Imports stay free of side effects: the delivery's transport (Node's http, the World's egress policy) is loaded when a
8
8
  // delivery is made, never when a browser client imports the kernel.
9
9
  import { createHash, createHmac } from 'node:crypto';
10
+ import { resolve } from 'node:path';
10
11
  import { applyTwinWrite, applyTwinWriteAtomic, twinResources } from './serve.ts';
11
12
  import { equalSecrets as same, ksuidFrom } from './signing.ts';
12
13
  import { deliveryTraceHeaders } from './trace-context.ts';
13
14
  import { sendRecordedEvent } from './event-delivery.ts';
14
- import { getActiveWorldStore, withWorldStore } from './world-store.ts';
15
+ import { getActiveWorldStore, withWorldStore, type WorldStore } from './world-store.ts';
15
16
  import { worldPaths } from './storage.ts';
16
17
 
17
18
  /** How the vendor signs a delivery. */
@@ -306,6 +307,26 @@ const worldTransport: EventTransport = async (url, body, headers) => {
306
307
  try { return ((await appFetch(url, { method: 'POST', headers: { 'content-type': 'application/json', ...headers }, body })) as { status: number }).status; } catch { return 0; }
307
308
  };
308
309
 
310
+ const pendingDeliveries = new WeakMap<WorldStore, Map<string, Set<Promise<void>>>>();
311
+
312
+ function forgetDeliveries(store: WorldStore, root: string, deliveries: Iterable<Promise<void>>): void {
313
+ const roots = pendingDeliveries.get(store), pending = roots?.get(root);
314
+ if (!pending) return;
315
+ for (const delivery of deliveries) pending.delete(delivery);
316
+ if (!pending.size) roots!.delete(root);
317
+ if (!roots!.size) pendingDeliveries.delete(store);
318
+ }
319
+
320
+ /** Await already-scheduled first dispatches and consume their retained persistence failures. */
321
+ export async function settleEventDeliveries(root: string): Promise<void> {
322
+ const store = getActiveWorldStore(), path = resolve(root);
323
+ const deliveries = [...(pendingDeliveries.get(store)?.get(path) ?? [])];
324
+ const outcomes = await Promise.allSettled(deliveries);
325
+ forgetDeliveries(store, path, deliveries);
326
+ const failures = outcomes.flatMap((outcome) => outcome.status === 'rejected' ? [outcome.reason] : []);
327
+ if (failures.length) throw new AggregateError(failures, 'Declared-event dispatch failed');
328
+ }
329
+
309
330
  /** Who watches signed packets before dispatch (a walk reading each as its receiver would). This is not evidence of
310
331
  * transport or acceptance. Set and restored by the walk, as its World store is; unset, nothing watches. */
311
332
  export type DeliveryObserver = (delivery: EventDelivery & { service: string }) => void;
@@ -321,13 +342,14 @@ export function setDeliveryObserver(observer: DeliveryObserver | undefined): Del
321
342
  * (`unreachable`), or it did not answer in time (`timeout`). */
322
343
  export type ApplicationAnswer = { status: number; headers: Record<string, string>; body: string; missed?: 'timeout' | 'unreachable' };
323
344
 
324
- /** Who answers what the vendor asks the application (`ctx.ask`) before the World's application route does, given to a
345
+ /** Who answers a vendor's requests to the application before the World's application route does, given to a
325
346
  * pack's fetch (`PackFetchOptions.application`) and belonging to that fetch alone: an answer it gives is the
326
347
  * application's; `'unreachable'` is a question nobody answers (a walk's life, a sealed World, takes no other); undefined
327
348
  * is a question it does not take, which goes on as any other does: the application route, the World's egress rule, the
328
349
  * network. A walk's stand-in answers every question (its life's answers, else unreachable); a hosted World's router
329
350
  * answers only what it can answer truthfully (WorldDoors.askInWorld) and leaves the rest. `bodyError` models a
330
- * disconnect after the response headers; a caller needing the complete body receives no complete answer. */
351
+ * disconnect after the response headers; a caller needing the complete body receives no complete answer, while an
352
+ * event dispatch acknowledges the response status without reading its body. */
331
353
  export type ApplicationStandIn = (url: string, init: RequestInit) => Promise<{ status: number; headers?: Record<string, string>; body: string; bodyError?: string } | 'unreachable' | undefined>;
332
354
 
333
355
  /** A message the vendor sends the application's server and decides by its answer (Stripe's real-time authorization
@@ -367,10 +389,15 @@ export async function deliverEvents(
367
389
  data: (type: string) => Promise<Record<string, unknown>>,
368
390
  transport: EventTransport = worldTransport,
369
391
  values?: (type: string) => Promise<Record<string, unknown>>,
392
+ application?: ApplicationStandIn,
370
393
  ): Promise<EventDelivery[]> {
371
394
  if (write.storedType === decl.store) return [];
395
+ const dispatch: EventTransport = application ? async (url, body, headers) => {
396
+ const answer = await application(url, { method: 'POST', headers: { 'content-type': 'application/json', ...headers }, body });
397
+ return answer === undefined ? transport(url, body, headers) : answer === 'unreachable' ? 0 : answer.status;
398
+ } : transport;
372
399
  const sent: EventDelivery[] = [];
373
- for (const type of eventTypesOf(decl, write.operation)) sent.push(...(await deliverEventType(service, decl, write, data, transport, values, type)));
400
+ for (const type of eventTypesOf(decl, write.operation)) sent.push(...(await deliverEventType(service, decl, write, data, dispatch, values, type)));
374
401
  return sent;
375
402
  }
376
403
 
@@ -466,10 +493,28 @@ async function deliverEventType(
466
493
  // active before it runs. Transport completion writes to the same retained state.
467
494
  const store = getActiveWorldStore(), deliveryRoot = worldPaths(service, write.root).root;
468
495
  for (const d of sent) deliveryObserver?.({ ...d, service });
469
- for (const packet of recorded) setTimeout(() => {
470
- // Persistence failure leaves a queued/sending claim for explicit recovery;
471
- // it must not fail the already-committed vendor write or cause a blind resend.
472
- void withWorldStore(store, () => sendRecordedEvent(service, packet.type, packet.id, deliveryRoot, transport, trace)).catch(() => {});
473
- }, 0);
496
+ const roots = pendingDeliveries.get(store) ?? new Map<string, Set<Promise<void>>>();
497
+ pendingDeliveries.set(store, roots);
498
+ const pending = roots.get(deliveryRoot) ?? new Set<Promise<void>>();
499
+ roots.set(deliveryRoot, pending);
500
+ for (const packet of recorded) {
501
+ let finish!: () => void;
502
+ let fail!: (reason: unknown) => void;
503
+ const dispatched = new Promise<void>((done, reject) => { finish = done; fail = reject; });
504
+ // Live writes return before dispatch; an explicit settlement reports persistence failures.
505
+ void dispatched.catch(() => {});
506
+ pending.add(dispatched);
507
+ setTimeout(async () => {
508
+ try {
509
+ await withWorldStore(store, () => sendRecordedEvent(service, packet.type, packet.id, deliveryRoot, transport, trace));
510
+ finish();
511
+ forgetDeliveries(store, deliveryRoot, [dispatched]);
512
+ } catch (error) {
513
+ // Keep the rejection until explicit settlement consumes it. The queued/sending claim remains for
514
+ // native recovery; this must not fail the already-committed vendor write or cause a blind resend.
515
+ fail(error);
516
+ }
517
+ }, 0);
518
+ }
474
519
  return sent;
475
520
  }
@@ -25,10 +25,16 @@ export function fileResponse(path: string, init?: ResponseInit): Response {
25
25
  * Stored bytes as a response, honouring a single `Range: bytes=` as RFC 9110 §14 has it: every satisfiable range is a
26
26
  * 206 with its Content-Range, `bytes=0-` (the whole body) included; a range that does not parse, a first-byte-pos past
27
27
  * its last-byte-pos (`bytes=5-2`) included, is ignored and the whole body is a 200; a well-formed range that starts past
28
- * the end (any range of an empty body) is a 416. `nosniff` stops a browser second-guessing the content type the vendor gave.
28
+ * the end (any range of an empty body) is a 416. Lazy sources supply inclusive ranges and stream in bounded reads.
29
+ * `nosniff` stops a browser second-guessing the content type the vendor gave.
29
30
  */
30
- export function bytesResponse(request: Request, bytes: Uint8Array, contentType: string, cacheControl = 'public, max-age=604800, immutable'): Response {
31
- const total = bytes.length;
31
+ export function bytesResponse(
32
+ request: Request,
33
+ bytes: Uint8Array | { size: number; range(start: number, end: number): Promise<Uint8Array | null> },
34
+ contentType: string,
35
+ cacheControl = 'public, max-age=604800, immutable',
36
+ ): Response {
37
+ const total = bytes instanceof Uint8Array ? bytes.length : bytes.size;
32
38
  const base = { 'content-type': contentType, 'x-content-type-options': 'nosniff', 'accept-ranges': 'bytes', 'cache-control': cacheControl };
33
39
  const range = /^bytes=(\d*)-(\d*)$/.exec((request.headers.get('range') ?? '').trim());
34
40
  let start = 0;
@@ -44,9 +50,25 @@ export function bytesResponse(request: Request, bytes: Uint8Array, contentType:
44
50
  // every satisfiable range is answered as one, the whole body's `bytes=0-` included: a media player (Safari's) reads a
45
51
  // 206 with its Content-Range as the server taking ranges, and a 200 as one it cannot seek in
46
52
  const partial = valid;
47
- const body = bytes.slice(start, end + 1);
48
- return new Response(request.method === 'HEAD' ? null : new Blob([body]), {
53
+ let body: BodyInit | null = null;
54
+ if (request.method !== 'HEAD') {
55
+ if (bytes instanceof Uint8Array) body = new Blob([bytes.slice(start, end + 1)]);
56
+ else {
57
+ let at = start;
58
+ body = new ReadableStream<Uint8Array>({
59
+ async pull(controller) {
60
+ if (at > end) { controller.close(); return; }
61
+ const last = Math.min(at + 8 * 1024 * 1024 - 1, end);
62
+ const chunk = await bytes.range(at, last);
63
+ if (!chunk) { controller.error(new Error('stored bytes are gone')); return; }
64
+ controller.enqueue(chunk);
65
+ at = last + 1;
66
+ },
67
+ });
68
+ }
69
+ }
70
+ return new Response(body, {
49
71
  status: partial ? 206 : 200,
50
- headers: { ...base, 'content-length': String(body.length), ...(partial ? { 'content-range': `bytes ${start}-${end}/${total}` } : {}) },
72
+ headers: { ...base, 'content-length': String(end + 1 - start), ...(partial ? { 'content-range': `bytes ${start}-${end}/${total}` } : {}) },
51
73
  });
52
74
  }
package/src/index.ts CHANGED
@@ -89,6 +89,7 @@ export * as protobuf from './protobuf.ts';
89
89
  export * as grpc from './grpc-wire.ts';
90
90
  export { acceptH2Connection, type H2Handler, type Http1Handler } from './h2.ts';
91
91
  export { placeholderPng } from './placeholder-image.ts';
92
+ export { imageDimensions, readImageHeader, readMp4Header } from './media-header.ts';
92
93
  export { presignSigV4, signSigV4, verifySigV4, type SigV4Verdict } from './sigv4.ts';
93
94
  export * as redis from './redis/index.ts';
94
95
  export * as mysql from './mysql/index.ts';
@@ -102,7 +103,7 @@ export type { PackScenarioAdapter, ScenarioDecision, ScenarioDocument, ScenarioE
102
103
 
103
104
  export { verifyAttestationBundle } from './attestation.ts';
104
105
 
105
- export { browserSessionToken, startBrowserSession, endBrowserSession } from './browser-session.ts';
106
+ export { cookieOf, signedIn, browserSessionToken, startBrowserSession, endBrowserSession } from './browser-session.ts';
106
107
 
107
108
  export { readHostPort } from './host-port.ts';
108
109
  export { assertBrowserName, browserOrigin, validateBrowserState, validateBrowserStorageState, validateBrowserContext, browserAcceptLanguage, cookieMatchesHost, type BrowserCookie, type BrowserState, type BrowserStorageState, type BrowserContext, type BrowserIndexedDB } from './browser-state.ts';
@@ -254,10 +254,10 @@ export function managedDatabase(url: string, options: { readOnly?: boolean } = {
254
254
  * walk/journey-kit.ts), its statements sent as its client sends them: a text alone as one simple Query (a migration
255
255
  * file, a `BEGIN`), a text with values as one extended-protocol statement, the values bind parameters. A transaction it
256
256
  * opens stays open across its statements until it ends it, as on the application's own connection. Each statement's
257
- * answer is its results in Postgres's text format, untyped. */
258
- export type DatabaseSession = { query(text: string, values?: ReadonlyArray<string | null>): Promise<StatementResult[]>; end(): Promise<void> };
257
+ * answer is its results in Postgres's text format, with native column type OIDs retained beside the values. */
258
+ export type DatabaseSession = { query(text: string, values?: ReadonlyArray<string | null>): Promise<Array<StatementResult & { fieldTypes: number[] }>>; end(): Promise<void> };
259
259
 
260
- type PgResult = { command?: string | null; rowCount?: number | null; fields?: Array<{ name: string }>; rows?: Array<Array<string | null>> };
260
+ type PgResult = { command?: string | null; rowCount?: number | null; fields?: Array<{ name: string; dataTypeID: number }>; rows?: Array<Array<string | null>> };
261
261
 
262
262
  /** The World's Postgres at `url` as one connection held until `end()`. */
263
263
  export async function databaseSession(url: string): Promise<DatabaseSession> {
@@ -277,6 +277,7 @@ export async function databaseSession(url: string): Promise<DatabaseSession> {
277
277
  const answer = (await (client.query({ text, ...(values === undefined ? {} : { values }), rowMode: 'array', types: raw }) as Promise<PgResult | PgResult[]>));
278
278
  return (Array.isArray(answer) ? answer : [answer]).map((r) => ({
279
279
  fields: (r.fields ?? []).map((f) => f.name),
280
+ fieldTypes: (r.fields ?? []).map((f) => f.dataTypeID),
280
281
  rows: r.rows ?? [],
281
282
  command: [r.command ?? '', r.command === 'INSERT' ? '0' : '', r.rowCount ?? ''].filter((part) => part !== '').join(' '),
282
283
  }));
@@ -0,0 +1,108 @@
1
+ // File-format headers only: no decoding, vendor limits, processing or rendition policy.
2
+ type ImageType = 'image/png' | 'image/gif' | 'image/jpeg' | 'image/webp';
3
+ type Dimensions = { width: number; height: number };
4
+ type ImageHeader = Dimensions & { jpeg?: { frameEnd: number; nonMarkerBytes: boolean; standaloneMarkers: boolean; fillBytes: boolean; shortSegments: boolean } };
5
+
6
+ const tag = (bytes: Uint8Array, at: number): string => String.fromCharCode(...bytes.subarray(at, at + 4));
7
+
8
+ /** Dimensions in a caller-declared image format, or undefined for an incomplete header. */
9
+ export function imageDimensions(bytes: Uint8Array, type: ImageType): ImageHeader | undefined {
10
+ const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
11
+ if (type === 'image/png') return bytes.length >= 24 ? { width: view.getUint32(16), height: view.getUint32(20) } : undefined;
12
+ if (type === 'image/gif') return bytes.length >= 10 ? { width: view.getUint16(6, true), height: view.getUint16(8, true) } : undefined;
13
+ if (type === 'image/webp') {
14
+ if (bytes.length < 30) return undefined;
15
+ const chunk = tag(bytes, 12);
16
+ if (chunk === 'VP8 ') return { width: view.getUint16(26, true) & 0x3fff, height: view.getUint16(28, true) & 0x3fff };
17
+ if (chunk === 'VP8L') {
18
+ const bits = view.getUint32(21, true);
19
+ return { width: 1 + (bits & 0x3fff), height: 1 + ((bits >>> 14) & 0x3fff) };
20
+ }
21
+ if (chunk === 'VP8X') {
22
+ const u24 = (at: number): number => bytes[at]! | (bytes[at + 1]! << 8) | (bytes[at + 2]! << 16);
23
+ return { width: 1 + u24(24), height: 1 + u24(27) };
24
+ }
25
+ return undefined;
26
+ }
27
+ let at = 2;
28
+ const jpeg = { frameEnd: 0, nonMarkerBytes: false, standaloneMarkers: false, fillBytes: false, shortSegments: false };
29
+ while (at + 4 <= bytes.length) {
30
+ if (bytes[at] !== 0xff) { jpeg.nonMarkerBytes = true; at += 1; continue; }
31
+ const marker = bytes[at + 1]!;
32
+ if (marker === 0xff) { jpeg.fillBytes = true; at += 1; continue; }
33
+ if (marker === 0xd8 || marker === 0x01 || (marker >= 0xd0 && marker <= 0xd7)) { jpeg.standaloneMarkers = true; at += 2; continue; }
34
+ const length = view.getUint16(at + 2);
35
+ if (length < 2) jpeg.shortSegments = true;
36
+ if (marker >= 0xc0 && marker <= 0xcf && marker !== 0xc4 && marker !== 0xc8 && marker !== 0xcc) {
37
+ return at + 9 <= bytes.length ? { width: view.getUint16(at + 7), height: view.getUint16(at + 5), jpeg: { ...jpeg, frameEnd: at + 9 } } : undefined;
38
+ }
39
+ if (marker === 0xda) return undefined;
40
+ at += 2 + length;
41
+ }
42
+ return undefined;
43
+ }
44
+
45
+ /** An image's signature and dimensions, without deciding whether a vendor accepts that format or size. */
46
+ export function readImageHeader(bytes: Uint8Array): (ImageHeader & { type: ImageType }) | undefined {
47
+ let type: ImageType;
48
+ if (bytes[0] === 0x89 && bytes[1] === 0x50 && bytes[2] === 0x4e && bytes[3] === 0x47) type = 'image/png';
49
+ else if (tag(bytes, 0).startsWith('GIF')) type = 'image/gif';
50
+ else if (bytes[0] === 0xff && bytes[1] === 0xd8) type = 'image/jpeg';
51
+ else if (tag(bytes, 0) === 'RIFF' && tag(bytes, 8) === 'WEBP') type = 'image/webp';
52
+ else return undefined;
53
+ const dimensions = imageDimensions(bytes, type);
54
+ return dimensions ? { type, ...dimensions } : undefined;
55
+ }
56
+
57
+ /** ISO BMFF's bounded top-level boxes and movie headers, with raw duration and 16.16 track dimensions. */
58
+ export function readMp4Header(bytes: Uint8Array): {
59
+ fileType: boolean;
60
+ hasMediaData: boolean;
61
+ movies: Array<{ headers: Array<{ timescale: number; duration: number; payloadSize: number }>; tracks: Dimensions[] }>;
62
+ } {
63
+ const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
64
+ function* boxes(from: number, to: number): Generator<{ type: string; start: number; end: number }> {
65
+ let at = from;
66
+ while (at + 8 <= to) {
67
+ let size = view.getUint32(at);
68
+ const type = tag(bytes, at + 4);
69
+ let header = 8;
70
+ if (size === 1) {
71
+ if (at + 16 > to) return;
72
+ size = Number(view.getBigUint64(at + 8));
73
+ header = 16;
74
+ }
75
+ if (size === 0) size = to - at;
76
+ if (!Number.isSafeInteger(size) || size < header || size > to - at) return;
77
+ yield { type, start: at + header, end: at + size };
78
+ at += size;
79
+ }
80
+ }
81
+ const result: ReturnType<typeof readMp4Header> = { fileType: bytes.length >= 8 && tag(bytes, 4) === 'ftyp', hasMediaData: false, movies: [] };
82
+ for (const box of boxes(0, bytes.length)) {
83
+ if (box.type === 'mdat' && box.end > box.start) result.hasMediaData = true;
84
+ if (box.type !== 'moov') continue;
85
+ const movie: (typeof result.movies)[number] = { headers: [], tracks: [] };
86
+ result.movies.push(movie);
87
+ for (const child of boxes(box.start, box.end)) {
88
+ const { type, start, end } = child;
89
+ if (type === 'mvhd' && start < end) {
90
+ const version = bytes[start];
91
+ const payloadSize = end - start;
92
+ if (version === 0 && payloadSize >= 20) movie.headers.push({ timescale: view.getUint32(start + 12), duration: view.getUint32(start + 16), payloadSize });
93
+ if (version === 1 && payloadSize >= 32) movie.headers.push({ timescale: view.getUint32(start + 20), duration: Number(view.getBigUint64(start + 24)), payloadSize });
94
+ }
95
+ if (type !== 'trak') continue;
96
+ for (const track of boxes(start, end)) {
97
+ if (track.type !== 'tkhd' || track.start >= track.end) continue;
98
+ const version = bytes[track.start];
99
+ const at = version === 1 ? track.start + 88 : version === 0 ? track.start + 76 : track.end;
100
+ if (at + 8 > track.end) continue;
101
+ const width = view.getUint32(at) / 65536;
102
+ const height = view.getUint32(at + 4) / 65536;
103
+ movie.tracks.push({ width, height });
104
+ }
105
+ }
106
+ }
107
+ return result;
108
+ }
package/src/pack-fetch.ts CHANGED
@@ -105,7 +105,7 @@ export type LanePart = {
105
105
  * World's scenario file for a model vendor's turns (`scenarioPath`, the runtime's `--scenario`). */
106
106
  export type PackFetchOptions = {
107
107
  root?: string; readOnly?: boolean; clock?: () => string; database?: string; scenarioPath?: string;
108
- /** the application a walk stands in for (a life's `application` answers): this fetch's `ctx.ask` is answered by it */
108
+ /** the application a walk stands in for: this fetch's requests and declared events are answered by it */
109
109
  application?: ApplicationStandIn;
110
110
  };
111
111
 
@@ -143,7 +143,7 @@ function hostMatcher(pattern: string): (host: string) => Match | undefined {
143
143
  };
144
144
  }
145
145
  /** The host a request is for: the vendor host the World's injector names, else its own. */
146
- const hostOf = (request: Request): string => readHostPort(request.headers.get('x-volter-twin-original-host') ?? request.headers.get('host') ?? new URL(request.url).host)?.host ?? '';
146
+ export const hostOf = (request: Request): string => readHostPort(request.headers.get('x-volter-twin-original-host') ?? request.headers.get('host') ?? new URL(request.url).host)?.host ?? '';
147
147
 
148
148
  /** Where a workspace asked `?show=<type>:<id>` opens the record (architecture, "Workspace"): the built workspace page
149
149
  * that shows that type, its parameter the id; else the one that lists the type; else the workspace's home (its root
@@ -218,13 +218,26 @@ async function encoded(m: DerivedManifest, request: Request, res: Response): Pro
218
218
  return new Response(buffered, { status: res.status, statusText: res.statusText, headers });
219
219
  }
220
220
 
221
- /** A pack's HTTP surface, as the kernel serves it from the pack's parts. */
222
221
  /** A door's path as it is served and listed: under `/_twin/`, whether the manifest writes the prefix or not. */
223
222
  const doorPath = (path: string): string => (path.startsWith('/_twin/') ? path : `/_twin${path.startsWith('/') ? '' : '/'}${path}`);
224
223
 
224
+ /** A declared World door, matched by the same normalized path for serving and response ownership. */
225
+ export function worldDoorMatcher<Door extends { method: string; path: string }>(doors: ReadonlyArray<Door>): (method: string, path: string) => { door: Door; params: Record<string, string> } | undefined {
226
+ const routes = doors.map((door) => ({ door, method: door.method.toUpperCase(), matches: pathMatcher(doorPath(door.path), false) }));
227
+ return (method, path) => {
228
+ const at = path.replace(/\/+$/, '') || '/';
229
+ for (const route of routes) {
230
+ if (route.method !== method.toUpperCase()) continue;
231
+ const hit = route.matches(at);
232
+ if (hit) return { door: route.door, params: hit.params };
233
+ }
234
+ return undefined;
235
+ };
236
+ }
237
+
225
238
  /** A request with its path's repeated slashes merged, as a front that merges them routes it (the manifest's
226
239
  * `mergeSlashes`); one with none is itself. */
227
- function slashesMerged(request: Request): Request {
240
+ export function slashesMerged(request: Request): Request {
228
241
  const url = new URL(request.url);
229
242
  const merged = url.pathname.replace(/\/{2,}/g, '/');
230
243
  if (merged === url.pathname) return request;
@@ -232,6 +245,58 @@ function slashesMerged(request: Request): Request {
232
245
  return new Request(url, request);
233
246
  }
234
247
 
248
+ /** The selected unit's request transformation, shared by serving and response judgment. */
249
+ export function unitRequestRouter(m: Pick<DerivedManifest, 'pathPrefix' | 'basePathAlias' | 'pathFromHost' | 'hostParams' | 'headerParams'>, surface: DerivedSurface, routes: ReturnType<typeof compileSurface>): (request: Request) => Request {
250
+ const prefix = m.pathPrefix ? new RegExp(`^(?:${m.pathPrefix})(?=/)`) : undefined;
251
+ const base = surface.basePath ?? '';
252
+ const baseAlias = m.basePathAlias && base ? new RegExp(`^(?:${m.basePathAlias})(?=/)`) : undefined;
253
+ const hostPath = m.pathFromHost ? new RegExp(m.pathFromHost, 'i') : undefined;
254
+ const hostParams = m.hostParams ? new RegExp(m.hostParams, 'i') : undefined;
255
+ // each operation's spanning parameters, keyed by the operation and the parameter as the route compiles them (a whole
256
+ // segment's label whose name the surface or the manifest declares spanning), so one operation's spanning name never
257
+ // keeps another's slashes, nor a label that only part of a segment holds
258
+ const spanningNames = new Set(surface.spanning ?? []);
259
+ const spanningOf = new Set(surface.operations.flatMap((op) => ((op.basePath ?? base) + op.path).split('/').flatMap((seg) => {
260
+ const label = /^\{([^}]+)\}$/.exec(seg)?.[1];
261
+ return label !== undefined && spanningNames.has(label) ? [`${op.id}\u0000${label}`] : [];
262
+ })));
263
+ /** A path as the vendor routes it: the manifest's path prefix taken off, the spec's base in an alias's place, a host's
264
+ * path label (pathFromHost) put in front. */
265
+ const rewritten = (pathname: string, host: string): string => {
266
+ let path = pathname;
267
+ if (prefix) path = path.replace(prefix, '') || '/';
268
+ // a base the vendor serves as its own: the spec's base in its place (a path already under the spec's base stays)
269
+ if (baseAlias && !(path === base || path.startsWith(`${base}/`))) path = path.replace(baseAlias, base);
270
+ const label = hostPath?.exec(host)?.[1];
271
+ if (label) path = `/${label}${path === '/' ? '' : path}`;
272
+ return path;
273
+ };
274
+ /** The request as its vendor routes it: duplicate slashes collapsed, unless the rewritten path matches an operation
275
+ * through one of that operation's spanning parameters (the surface's and the manifest's), whose value keeps its
276
+ * slashes; then rewritten. */
277
+ return (request: Request): Request => {
278
+ const url = new URL(request.url);
279
+ const host = hostOf(request);
280
+ const kept = rewritten(url.pathname, host);
281
+ const matched = matchOperation(routes, request.method, kept);
282
+ const spanning = matched !== undefined && Object.keys(matched.params).some((name) => spanningOf.has(`${matched.operation.id}\u0000${name}`));
283
+ const source = spanning ? url.pathname : url.pathname.replace(/\/{2,}/g, '/');
284
+ // a prefix's named groups (Jira's `/ex/jira/(?<cloudId>[^/]+)`) and the host's (E2B's envd at
285
+ // `49983-(?<sandboxID>…).e2b.app`) join the operation's parameters
286
+ const groups = { ...(prefix?.exec(source)?.groups ?? {}), ...(hostParams?.exec(host)?.groups ?? {}), ...Object.fromEntries(Object.entries(m.headerParams ?? {}).flatMap(([param, header]) => { const value = request.headers.get(header); return value === null ? [] : [[param, value]]; })) };
287
+ const path = spanning ? kept : rewritten(source, host);
288
+ if (path === url.pathname && !Object.keys(groups).length) return request;
289
+ // the path the client sent, which a signature over the request (SigV4's canonical URI) covers
290
+ const headers = new Headers(request.headers);
291
+ // the path as first sent: a front before this (an attach bridge) may have named the one its client signed
292
+ if (!headers.has(ORIGINAL_PATH_HEADER)) headers.set(ORIGINAL_PATH_HEADER, url.pathname);
293
+ if (Object.keys(groups).length) headers.set(PREFIX_PARAMS_HEADER, JSON.stringify(groups));
294
+ url.pathname = path;
295
+ return new Request(url, { method: request.method, headers, body: request.body, signal: request.signal, ...(request.body ? { duplex: 'half' } : {}) } as RequestInit);
296
+ };
297
+ }
298
+
299
+ /** A pack's HTTP surface, as the kernel serves it from the pack's parts. */
235
300
  export function createPackFetch(parts: PackParts, options: PackFetchOptions = {}): DerivedFetch {
236
301
  const m = parts.manifest;
237
302
  const scope: CoreScope = {
@@ -322,11 +387,8 @@ export function createPackFetch(parts: PackParts, options: PackFetchOptions = {}
322
387
  if (decision.kind === 'fault') return Response.json(decision.result.body, { status: decision.result.status, headers: decision.result.headers ?? {} });
323
388
  return api(withHeaders(request, new Headers([...request.headers, [SCENARIO_DECISION_HEADER, scenarioDecisionHeader(decision)]])));
324
389
  };
325
- const doors = (m.doors ?? []).map((d) => {
326
- const answer = parts.doors?.[d.id];
327
- if (!answer) throw new Error(`${m.vendor}: the manifest declares the door ${d.id}, and semantics/doors.ts exports no ${d.id}`);
328
- return { decl: d, path: pathMatcher(doorPath(d.path), false), answer };
329
- });
390
+ for (const d of m.doors ?? []) if (!parts.doors?.[d.id]) throw new Error(`${m.vendor}: the manifest declares the door ${d.id}, and semantics/doors.ts exports no ${d.id}`);
391
+ const door = worldDoorMatcher(m.doors ?? []);
330
392
  const screens = (m.screens ?? []).filter((s) => s.status === 'done' && parts.screens?.[s.id]).map((s) => {
331
393
  const hosts = [s.host, ...(s.hosts ?? [])].map(hostMatcher);
332
394
  return { decl: s, host: (h: string) => hosts.map((match) => match(h)).find(Boolean), path: pathMatcher(s.path, true), answer: parts.screens![s.id]! };
@@ -355,53 +417,7 @@ export function createPackFetch(parts: PackParts, options: PackFetchOptions = {}
355
417
  };
356
418
  const refusedReadOnly = (): Response => vendorError(m, m.readOnly);
357
419
 
358
- const prefix = m.pathPrefix ? new RegExp(`^(?:${m.pathPrefix})(?=/)`) : undefined;
359
- const base = surface.basePath ?? '';
360
- const baseAlias = m.basePathAlias && base ? new RegExp(`^(?:${m.basePathAlias})(?=/)`) : undefined;
361
- const hostPath = m.pathFromHost ? new RegExp(m.pathFromHost, 'i') : undefined;
362
- const hostParams = m.hostParams ? new RegExp(m.hostParams, 'i') : undefined;
363
- // each operation's spanning parameters, keyed by the operation and the parameter as the route compiles them (a whole
364
- // segment's label whose name the surface or the manifest declares spanning), so one operation's spanning name never
365
- // keeps another's slashes, nor a label that only part of a segment holds
366
- const spanningNames = new Set(surface.spanning ?? []);
367
- const spanningOf = new Set(surface.operations.flatMap((op) => ((op.basePath ?? base) + op.path).split('/').flatMap((seg) => {
368
- const label = /^\{([^}]+)\}$/.exec(seg)?.[1];
369
- return label !== undefined && spanningNames.has(label) ? [`${op.id}\u0000${label}`] : [];
370
- })));
371
- /** A path as the vendor routes it: the manifest's path prefix taken off, the spec's base in an alias's place, a host's
372
- * path label (pathFromHost) put in front. */
373
- const rewritten = (pathname: string, host: string): string => {
374
- let path = pathname;
375
- if (prefix) path = path.replace(prefix, '') || '/';
376
- // a base the vendor serves as its own: the spec's base in its place (a path already under the spec's base stays)
377
- if (baseAlias && !(path === base || path.startsWith(`${base}/`))) path = path.replace(baseAlias, base);
378
- const label = hostPath?.exec(host)?.[1];
379
- if (label) path = `/${label}${path === '/' ? '' : path}`;
380
- return path;
381
- };
382
- /** The request as its vendor routes it: duplicate slashes collapsed, unless the rewritten path matches an operation
383
- * through one of that operation's spanning parameters (the surface's and the manifest's), whose value keeps its
384
- * slashes; then rewritten. */
385
- const routed = (request: Request): Request => {
386
- const url = new URL(request.url);
387
- const host = hostOf(request);
388
- const kept = rewritten(url.pathname, host);
389
- const matched = matchOperation(routes, request.method, kept);
390
- const spanning = matched !== undefined && Object.keys(matched.params).some((name) => spanningOf.has(`${matched.operation.id}\u0000${name}`));
391
- const source = spanning ? url.pathname : url.pathname.replace(/\/{2,}/g, '/');
392
- // a prefix's named groups (Jira's `/ex/jira/(?<cloudId>[^/]+)`) and the host's (E2B's envd at
393
- // `49983-(?<sandboxID>…).e2b.app`) join the operation's parameters
394
- const groups = { ...(prefix?.exec(source)?.groups ?? {}), ...(hostParams?.exec(host)?.groups ?? {}), ...Object.fromEntries(Object.entries(m.headerParams ?? {}).flatMap(([param, header]) => { const value = request.headers.get(header); return value === null ? [] : [[param, value]]; })) };
395
- const path = spanning ? kept : rewritten(source, host);
396
- if (path === url.pathname && !Object.keys(groups).length) return request;
397
- // the path the client sent, which a signature over the request (SigV4's canonical URI) covers
398
- const headers = new Headers(request.headers);
399
- // the path as first sent: a front before this (an attach bridge) may have named the one its client signed
400
- if (!headers.has(ORIGINAL_PATH_HEADER)) headers.set(ORIGINAL_PATH_HEADER, url.pathname);
401
- if (Object.keys(groups).length) headers.set(PREFIX_PARAMS_HEADER, JSON.stringify(groups));
402
- url.pathname = path;
403
- return new Request(url, { method: request.method, headers, body: request.body, signal: request.signal, ...(request.body ? { duplex: 'half' } : {}) } as RequestInit);
404
- };
420
+ const routed = unitRequestRouter(m, surface, routes);
405
421
  const rootServes = (method: string, path: string): boolean => Boolean(matchOperation(routes, method, path));
406
422
  const lane = m.lanes ? laneRouter(m.vendor, m.lanes, parts.lanes ?? {}, rootServes) : undefined;
407
423
  // Inspect lane-owned World doors directly, without routing them back through the vendor gateway.
@@ -488,7 +504,7 @@ export function createPackFetch(parts: PackParts, options: PackFetchOptions = {}
488
504
  const own = await answerContent(arrived);
489
505
  if (own) return own;
490
506
  // Root World doors belong to the pack at every routed host, including a lane's host.
491
- const rootDoor = doors.some((d) => d.decl.method.toUpperCase() === arrived.method && d.path(asked));
507
+ const rootDoor = door(arrived.method, asked);
492
508
  const toLane = discovery || rootDoor ? undefined : lane?.(arrived);
493
509
  if (toLane) return toLane(arrived);
494
510
  const readable = await prepared(arrived);
@@ -551,12 +567,11 @@ export function createPackFetch(parts: PackParts, options: PackFetchOptions = {}
551
567
  if (kind === 'fake') resetFakePool(m.service, options.root);
552
568
  return Response.json({ kind }, { status: 201 });
553
569
  }
554
- for (const d of doors) {
555
- if (d.decl.method.toUpperCase() !== request.method) continue;
556
- const hit = d.path(path);
557
- if (!hit) continue;
570
+ const hit = door(request.method, path);
571
+ if (hit) {
558
572
  if (readOnly && request.method !== 'GET') return refusedReadOnly();
559
- return d.answer(await open(request, d.decl.id, d.decl.method, d.decl.path, hit.params));
573
+ const d = hit.door;
574
+ return parts.doors![d.id]!(await open(request, d.id, d.method, d.path, hit.params));
560
575
  }
561
576
  return gapAnswer(m, request);
562
577
  }
@@ -717,30 +732,16 @@ export function createPackFetch(parts: PackParts, options: PackFetchOptions = {}
717
732
  return derivedRequestScopes(m, Object.assign(served, { owners, ownersByUnit, serves, advanceClock: async (request: Request) => runWithStorageTenant(m.service, await selectedAccount(request), () => advanceClock(request)), workspaces, boards: Boolean(parts.board), maxRequestBodySize: m.descriptor?.maxRequestBodySize, draws: drawsUrl, contentHosts, ...(upgrade ? { upgrade } : {}), ...(ingest ? { ingest } : {}) }), options.readOnly);
718
733
  }
719
734
 
720
- /** The vendor's gateway over its lanes: the lane a request goes to (a door to the lane that declares it, then the first
721
- * route whose conditions all hold, then the default), behind the gateway's CORS; undefined when none takes it. */
722
- function laneRouter(vendor: string, decl: LanesDecl, lanes: Record<string, LanePart>, rootServes: (method: string, path: string) => boolean = () => false): (request: Request) => ((request: Request) => Promise<Response>) | undefined {
723
- const served = (name: string): ((request: Request) => Promise<Response>) => {
724
- const part = lanes[name];
725
- if (!part) throw new Error(`${vendor}: the manifest routes to the lane ${name}, and the vendor's fetch is given no ${name}`);
726
- // the lane is named on the request, so a write it makes records which lane's API made it (LANE_HEADER)
727
- const named = (request: Request): Promise<Response> => {
728
- const headers = new Headers(request.headers);
729
- headers.set(LANE_HEADER, name);
730
- return part.fetch(withHeaders(request, headers));
731
- };
732
- return decl.cors ? withCors(decl.cors, named) : named;
733
- };
734
- const doors = Object.entries(lanes).flatMap(([name, part]) => (part.manifest.doors ?? []).map((d) => ({
735
- name, method: d.method.toUpperCase(), matches: pathMatcher(d.path.startsWith('/_twin/') ? d.path : `/_twin/${d.path.replace(/^\//, '')}`, false),
736
- })));
735
+ /** The lane selected by the vendor gateway, shared by serving and response judgment. */
736
+ export function laneSelector(decl: LanesDecl, lanes: Record<string, { manifest: LanePart['manifest']; fetch: Pick<LanePart['fetch'], 'serves' | 'contentHosts'> }>, rootServes: (method: string, path: string) => boolean = () => false): (request: Request) => string | undefined {
737
+ const door = worldDoorMatcher(Object.entries(lanes).flatMap(([name, part]) => (part.manifest.doors ?? []).map((d) => ({ ...d, name }))));
737
738
  const laneRoutes = decl.routes.map((r) => {
738
739
  const at = r.path?.replace(/\/+$/, '');
739
740
  const host = r.host !== undefined ? new RegExp(r.host, 'i') : undefined;
740
741
  const except = (r.exceptHosts ?? []).map((h) => h.toLowerCase());
741
742
  const own = lanes[r.lane]?.fetch.serves;
742
743
  return {
743
- lane: served(r.lane),
744
+ lane: r.lane,
744
745
  holds: (request: Request, path: string, name: string): boolean =>
745
746
  (at === undefined || path === at || path.startsWith(`${at}/`))
746
747
  && (host === undefined || host.test(name))
@@ -749,21 +750,42 @@ function laneRouter(vendor: string, decl: LanesDecl, lanes: Record<string, LaneP
749
750
  && !(r.unlessOnlyRoot && rootServes(request.method, path) && !(own?.(request.method, path) ?? false)),
750
751
  };
751
752
  });
752
- const fallback = decl.default !== undefined ? served(decl.default) : undefined;
753
753
  return (request) => {
754
754
  // `<base>/@<host>/<path>` at a World's place, for a host a lane's built content screen names, goes to that lane, before
755
755
  // any lane is picked by the request's host or path: its own fetch answers it by its content route, under the vendor's
756
756
  // host rules (architecture, "A page at a World's place")
757
757
  const address = request.headers.has(TWIN_PREFIX_HEADER) ? contentAddressOf(new URL(request.url).pathname) : undefined;
758
758
  const content = address && Object.keys(lanes).find((l) => lanes[l]!.fetch.contentHosts?.includes(address.host));
759
- if (content) return served(content);
759
+ if (content) return content;
760
760
  const path = new URL(request.url).pathname.replace(/\/{2,}/g, '/');
761
761
  if (path.startsWith('/_twin/')) {
762
- const door = doors.find((d) => d.method === request.method && d.matches(path));
763
- if (door) return served(door.name);
762
+ const hit = door(request.method, path);
763
+ if (hit) return hit.door.name;
764
764
  }
765
765
  const name = hostOf(request);
766
- return laneRoutes.find((r) => r.holds(request, path, name))?.lane ?? fallback;
766
+ return laneRoutes.find((r) => r.holds(request, path, name))?.lane ?? decl.default;
767
+ };
768
+ }
769
+
770
+ /** The vendor's gateway over its lanes: the lane a request goes to (a door to the lane that declares it, then the first
771
+ * route whose conditions all hold, then the default), behind the gateway's CORS; undefined when none takes it. */
772
+ function laneRouter(vendor: string, decl: LanesDecl, lanes: Record<string, LanePart>, rootServes: (method: string, path: string) => boolean = () => false): (request: Request) => ((request: Request) => Promise<Response>) | undefined {
773
+ const served = (name: string): ((request: Request) => Promise<Response>) => {
774
+ const part = lanes[name];
775
+ if (!part) throw new Error(`${vendor}: the manifest routes to the lane ${name}, and the vendor's fetch is given no ${name}`);
776
+ // the lane is named on the request, so a write it makes records which lane's API made it (LANE_HEADER)
777
+ const named = (request: Request): Promise<Response> => {
778
+ const headers = new Headers(request.headers);
779
+ headers.set(LANE_HEADER, name);
780
+ return part.fetch(withHeaders(request, headers));
781
+ };
782
+ return decl.cors ? withCors(decl.cors, named) : named;
783
+ };
784
+ const selected = laneSelector(decl, lanes, rootServes);
785
+ const handlers = new Map([...new Set([...decl.routes.map((route) => route.lane), ...(decl.default === undefined ? [] : [decl.default]), ...Object.keys(lanes)])].map((name) => [name, served(name)]));
786
+ return (request) => {
787
+ const name = selected(request);
788
+ return name === undefined ? undefined : handlers.get(name);
767
789
  };
768
790
  }
769
791
 
@@ -221,7 +221,13 @@ export type TwinPack = {
221
221
  * answer: `init` writes `$issue:<service>` for each, and `up`, once this twin is running, POSTs the body to the door
222
222
  * and sets each name to its field (`file:<field>` materializes one file; `directory:<field>` a relative-file map). A twin that holds its keys (refusing any it
223
223
  * did not issue) declares one, so a World's applications hold keys it issued rather than fixtures it refuses. */
224
- credentialDoor?: { path: string; body: Record<string, unknown>; fill: Record<string, string>; account?: { field: string } };
224
+ credentialDoor?: {
225
+ path: string;
226
+ body: Record<string, unknown>;
227
+ fill: Record<string, string>;
228
+ account?: { field: string } & ({ tenancy?: never; source?: never } | { tenancy: string; source: string });
229
+ deployment?: { field: string; object: string; source: string; organizationOutputs?: string[] };
230
+ };
225
231
  /** Another pack of this vendor issues the API's credentials; architecture, descriptor World wiring. */
226
232
  credentialIssuer?: string;
227
233
  prismaAdapter?: { adapter: string; export: string; urlEnv: string };
@@ -1,8 +1,7 @@
1
- /** Compressed-request output only: Node's plain requests remain uncapped; Bun retains its own plain-body limit.
2
- * The default is Bun's existing largest plain body (128 MiB), so both runtimes decode the same.
1
+ /** The shared HTTP transport and compressed-request output retain Bun's 128 MiB plain-body default.
3
2
  * Source: bun-types/serve.d.ts, ServeOptions.maxRequestBodySize (@default 1024 * 1024 * 128),
4
3
  * https://github.com/oven-sh/bun/blob/main/packages/bun-types/serve.d.ts .
5
- * A pack's descriptor.maxRequestBodySize overrides decoded output as it does Bun's plain-body limit. */
4
+ * A pack's descriptor.maxRequestBodySize overrides both the transport and decoded-output limits. */
6
5
  export const DEFAULT_MAX_DECODED_REQUEST_BODY_SIZE = 128 * 1024 * 1024;
7
6
 
8
7
  export class RequestBodyTooLargeError extends Error {