@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/dist/src/browser-session.d.ts +7 -1
- package/dist/src/browser-session.js +21 -10
- package/dist/src/derived-core.d.ts +36 -3
- package/dist/src/derived-core.js +121 -13
- package/dist/src/derived-real.js +15 -3
- package/dist/src/events.d.ts +6 -3
- package/dist/src/events.js +52 -7
- package/dist/src/file-response.d.ts +6 -2
- package/dist/src/file-response.js +29 -5
- package/dist/src/index.d.ts +2 -1
- package/dist/src/index.js +2 -1
- package/dist/src/managed-database.d.ts +4 -2
- package/dist/src/managed-database.js +1 -0
- package/dist/src/media-header.d.ts +34 -0
- package/dist/src/media-header.js +129 -0
- package/dist/src/pack-fetch.d.ts +24 -3
- package/dist/src/pack-fetch.js +114 -94
- package/dist/src/packRegistry.d.ts +12 -0
- package/dist/src/request-body.d.ts +2 -3
- package/dist/src/request-body.js +2 -3
- package/dist/src/runtime.d.ts +2 -1
- package/dist/src/runtime.js +2 -1
- package/dist/src/serve-http.d.ts +1 -1
- package/dist/src/serve-http.js +93 -17
- package/package.json +1 -1
- package/src/browser-session.ts +17 -5
- package/src/derived-core.ts +135 -17
- package/src/derived-real.ts +10 -2
- package/src/events.ts +54 -9
- package/src/file-response.ts +28 -6
- package/src/index.ts +2 -1
- package/src/managed-database.ts +4 -3
- package/src/media-header.ts +108 -0
- package/src/pack-fetch.ts +107 -85
- package/src/packRegistry.ts +7 -1
- package/src/request-body.ts +2 -3
- package/src/runtime.ts +2 -1
- package/src/serve-http.ts +70 -16
|
@@ -76,9 +76,11 @@ export declare function managedDatabase(url: string, options?: {
|
|
|
76
76
|
* walk/journey-kit.ts), its statements sent as its client sends them: a text alone as one simple Query (a migration
|
|
77
77
|
* file, a `BEGIN`), a text with values as one extended-protocol statement, the values bind parameters. A transaction it
|
|
78
78
|
* opens stays open across its statements until it ends it, as on the application's own connection. Each statement's
|
|
79
|
-
* answer is its results in Postgres's text format,
|
|
79
|
+
* answer is its results in Postgres's text format, with native column type OIDs retained beside the values. */
|
|
80
80
|
export type DatabaseSession = {
|
|
81
|
-
query(text: string, values?: ReadonlyArray<string | null>): Promise<StatementResult
|
|
81
|
+
query(text: string, values?: ReadonlyArray<string | null>): Promise<Array<StatementResult & {
|
|
82
|
+
fieldTypes: number[];
|
|
83
|
+
}>>;
|
|
82
84
|
end(): Promise<void>;
|
|
83
85
|
};
|
|
84
86
|
/** The World's Postgres at `url` as one connection held until `end()`. */
|
|
@@ -255,6 +255,7 @@ export async function databaseSession(url) {
|
|
|
255
255
|
const answer = (await client.query({ text, ...(values === undefined ? {} : { values }), rowMode: 'array', types: raw }));
|
|
256
256
|
return (Array.isArray(answer) ? answer : [answer]).map((r) => ({
|
|
257
257
|
fields: (r.fields ?? []).map((f) => f.name),
|
|
258
|
+
fieldTypes: (r.fields ?? []).map((f) => f.dataTypeID),
|
|
258
259
|
rows: r.rows ?? [],
|
|
259
260
|
command: [r.command ?? '', r.command === 'INSERT' ? '0' : '', r.rowCount ?? ''].filter((part) => part !== '').join(' '),
|
|
260
261
|
}));
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
type ImageType = 'image/png' | 'image/gif' | 'image/jpeg' | 'image/webp';
|
|
2
|
+
type Dimensions = {
|
|
3
|
+
width: number;
|
|
4
|
+
height: number;
|
|
5
|
+
};
|
|
6
|
+
type ImageHeader = Dimensions & {
|
|
7
|
+
jpeg?: {
|
|
8
|
+
frameEnd: number;
|
|
9
|
+
nonMarkerBytes: boolean;
|
|
10
|
+
standaloneMarkers: boolean;
|
|
11
|
+
fillBytes: boolean;
|
|
12
|
+
shortSegments: boolean;
|
|
13
|
+
};
|
|
14
|
+
};
|
|
15
|
+
/** Dimensions in a caller-declared image format, or undefined for an incomplete header. */
|
|
16
|
+
export declare function imageDimensions(bytes: Uint8Array, type: ImageType): ImageHeader | undefined;
|
|
17
|
+
/** An image's signature and dimensions, without deciding whether a vendor accepts that format or size. */
|
|
18
|
+
export declare function readImageHeader(bytes: Uint8Array): (ImageHeader & {
|
|
19
|
+
type: ImageType;
|
|
20
|
+
}) | undefined;
|
|
21
|
+
/** ISO BMFF's bounded top-level boxes and movie headers, with raw duration and 16.16 track dimensions. */
|
|
22
|
+
export declare function readMp4Header(bytes: Uint8Array): {
|
|
23
|
+
fileType: boolean;
|
|
24
|
+
hasMediaData: boolean;
|
|
25
|
+
movies: Array<{
|
|
26
|
+
headers: Array<{
|
|
27
|
+
timescale: number;
|
|
28
|
+
duration: number;
|
|
29
|
+
payloadSize: number;
|
|
30
|
+
}>;
|
|
31
|
+
tracks: Dimensions[];
|
|
32
|
+
}>;
|
|
33
|
+
};
|
|
34
|
+
export {};
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
const tag = (bytes, at) => String.fromCharCode(...bytes.subarray(at, at + 4));
|
|
2
|
+
/** Dimensions in a caller-declared image format, or undefined for an incomplete header. */
|
|
3
|
+
export function imageDimensions(bytes, type) {
|
|
4
|
+
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
5
|
+
if (type === 'image/png')
|
|
6
|
+
return bytes.length >= 24 ? { width: view.getUint32(16), height: view.getUint32(20) } : undefined;
|
|
7
|
+
if (type === 'image/gif')
|
|
8
|
+
return bytes.length >= 10 ? { width: view.getUint16(6, true), height: view.getUint16(8, true) } : undefined;
|
|
9
|
+
if (type === 'image/webp') {
|
|
10
|
+
if (bytes.length < 30)
|
|
11
|
+
return undefined;
|
|
12
|
+
const chunk = tag(bytes, 12);
|
|
13
|
+
if (chunk === 'VP8 ')
|
|
14
|
+
return { width: view.getUint16(26, true) & 0x3fff, height: view.getUint16(28, true) & 0x3fff };
|
|
15
|
+
if (chunk === 'VP8L') {
|
|
16
|
+
const bits = view.getUint32(21, true);
|
|
17
|
+
return { width: 1 + (bits & 0x3fff), height: 1 + ((bits >>> 14) & 0x3fff) };
|
|
18
|
+
}
|
|
19
|
+
if (chunk === 'VP8X') {
|
|
20
|
+
const u24 = (at) => bytes[at] | (bytes[at + 1] << 8) | (bytes[at + 2] << 16);
|
|
21
|
+
return { width: 1 + u24(24), height: 1 + u24(27) };
|
|
22
|
+
}
|
|
23
|
+
return undefined;
|
|
24
|
+
}
|
|
25
|
+
let at = 2;
|
|
26
|
+
const jpeg = { frameEnd: 0, nonMarkerBytes: false, standaloneMarkers: false, fillBytes: false, shortSegments: false };
|
|
27
|
+
while (at + 4 <= bytes.length) {
|
|
28
|
+
if (bytes[at] !== 0xff) {
|
|
29
|
+
jpeg.nonMarkerBytes = true;
|
|
30
|
+
at += 1;
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
const marker = bytes[at + 1];
|
|
34
|
+
if (marker === 0xff) {
|
|
35
|
+
jpeg.fillBytes = true;
|
|
36
|
+
at += 1;
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
if (marker === 0xd8 || marker === 0x01 || (marker >= 0xd0 && marker <= 0xd7)) {
|
|
40
|
+
jpeg.standaloneMarkers = true;
|
|
41
|
+
at += 2;
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
const length = view.getUint16(at + 2);
|
|
45
|
+
if (length < 2)
|
|
46
|
+
jpeg.shortSegments = true;
|
|
47
|
+
if (marker >= 0xc0 && marker <= 0xcf && marker !== 0xc4 && marker !== 0xc8 && marker !== 0xcc) {
|
|
48
|
+
return at + 9 <= bytes.length ? { width: view.getUint16(at + 7), height: view.getUint16(at + 5), jpeg: { ...jpeg, frameEnd: at + 9 } } : undefined;
|
|
49
|
+
}
|
|
50
|
+
if (marker === 0xda)
|
|
51
|
+
return undefined;
|
|
52
|
+
at += 2 + length;
|
|
53
|
+
}
|
|
54
|
+
return undefined;
|
|
55
|
+
}
|
|
56
|
+
/** An image's signature and dimensions, without deciding whether a vendor accepts that format or size. */
|
|
57
|
+
export function readImageHeader(bytes) {
|
|
58
|
+
let type;
|
|
59
|
+
if (bytes[0] === 0x89 && bytes[1] === 0x50 && bytes[2] === 0x4e && bytes[3] === 0x47)
|
|
60
|
+
type = 'image/png';
|
|
61
|
+
else if (tag(bytes, 0).startsWith('GIF'))
|
|
62
|
+
type = 'image/gif';
|
|
63
|
+
else if (bytes[0] === 0xff && bytes[1] === 0xd8)
|
|
64
|
+
type = 'image/jpeg';
|
|
65
|
+
else if (tag(bytes, 0) === 'RIFF' && tag(bytes, 8) === 'WEBP')
|
|
66
|
+
type = 'image/webp';
|
|
67
|
+
else
|
|
68
|
+
return undefined;
|
|
69
|
+
const dimensions = imageDimensions(bytes, type);
|
|
70
|
+
return dimensions ? { type, ...dimensions } : undefined;
|
|
71
|
+
}
|
|
72
|
+
/** ISO BMFF's bounded top-level boxes and movie headers, with raw duration and 16.16 track dimensions. */
|
|
73
|
+
export function readMp4Header(bytes) {
|
|
74
|
+
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
75
|
+
function* boxes(from, to) {
|
|
76
|
+
let at = from;
|
|
77
|
+
while (at + 8 <= to) {
|
|
78
|
+
let size = view.getUint32(at);
|
|
79
|
+
const type = tag(bytes, at + 4);
|
|
80
|
+
let header = 8;
|
|
81
|
+
if (size === 1) {
|
|
82
|
+
if (at + 16 > to)
|
|
83
|
+
return;
|
|
84
|
+
size = Number(view.getBigUint64(at + 8));
|
|
85
|
+
header = 16;
|
|
86
|
+
}
|
|
87
|
+
if (size === 0)
|
|
88
|
+
size = to - at;
|
|
89
|
+
if (!Number.isSafeInteger(size) || size < header || size > to - at)
|
|
90
|
+
return;
|
|
91
|
+
yield { type, start: at + header, end: at + size };
|
|
92
|
+
at += size;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
const result = { fileType: bytes.length >= 8 && tag(bytes, 4) === 'ftyp', hasMediaData: false, movies: [] };
|
|
96
|
+
for (const box of boxes(0, bytes.length)) {
|
|
97
|
+
if (box.type === 'mdat' && box.end > box.start)
|
|
98
|
+
result.hasMediaData = true;
|
|
99
|
+
if (box.type !== 'moov')
|
|
100
|
+
continue;
|
|
101
|
+
const movie = { headers: [], tracks: [] };
|
|
102
|
+
result.movies.push(movie);
|
|
103
|
+
for (const child of boxes(box.start, box.end)) {
|
|
104
|
+
const { type, start, end } = child;
|
|
105
|
+
if (type === 'mvhd' && start < end) {
|
|
106
|
+
const version = bytes[start];
|
|
107
|
+
const payloadSize = end - start;
|
|
108
|
+
if (version === 0 && payloadSize >= 20)
|
|
109
|
+
movie.headers.push({ timescale: view.getUint32(start + 12), duration: view.getUint32(start + 16), payloadSize });
|
|
110
|
+
if (version === 1 && payloadSize >= 32)
|
|
111
|
+
movie.headers.push({ timescale: view.getUint32(start + 20), duration: Number(view.getBigUint64(start + 24)), payloadSize });
|
|
112
|
+
}
|
|
113
|
+
if (type !== 'trak')
|
|
114
|
+
continue;
|
|
115
|
+
for (const track of boxes(start, end)) {
|
|
116
|
+
if (track.type !== 'tkhd' || track.start >= track.end)
|
|
117
|
+
continue;
|
|
118
|
+
const version = bytes[track.start];
|
|
119
|
+
const at = version === 1 ? track.start + 88 : version === 0 ? track.start + 76 : track.end;
|
|
120
|
+
if (at + 8 > track.end)
|
|
121
|
+
continue;
|
|
122
|
+
const width = view.getUint32(at) / 65536;
|
|
123
|
+
const height = view.getUint32(at + 4) / 65536;
|
|
124
|
+
movie.tracks.push({ width, height });
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return result;
|
|
129
|
+
}
|
package/dist/src/pack-fetch.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { type DerivedManifest, type Handler, type HandlerContext, type ScreenDecl, type VendorManifest } from './derived-core.js';
|
|
2
|
-
import { type DerivedFetch, type DerivedSurface, type BoardFrame } from './derived.js';
|
|
1
|
+
import { type DerivedManifest, type Handler, type HandlerContext, type LanesDecl, type ScreenDecl, type VendorManifest } from './derived-core.js';
|
|
2
|
+
import { compileSurface, type DerivedFetch, type DerivedSurface, type BoardFrame } from './derived.js';
|
|
3
3
|
import { type PackScenarioAdapter } from './scenario.js';
|
|
4
4
|
import type { ApplicationStandIn } from './events.js';
|
|
5
5
|
import { type SocketEngine } from './sockets.js';
|
|
@@ -83,13 +83,34 @@ export type PackFetchOptions = {
|
|
|
83
83
|
clock?: () => string;
|
|
84
84
|
database?: string;
|
|
85
85
|
scenarioPath?: string;
|
|
86
|
-
/** the application a walk stands in for
|
|
86
|
+
/** the application a walk stands in for: this fetch's requests and declared events are answered by it */
|
|
87
87
|
application?: ApplicationStandIn;
|
|
88
88
|
};
|
|
89
|
+
/** The host a request is for: the vendor host the World's injector names, else its own. */
|
|
90
|
+
export declare const hostOf: (request: Request) => string;
|
|
89
91
|
/** Where a workspace asked `?show=<type>:<id>` opens the record (architecture, "Workspace"): the built workspace page
|
|
90
92
|
* that shows that type, its parameter the id; else the one that lists the type; else the workspace's home (its root
|
|
91
93
|
* page). Every other parameter a page's path names is the asking page's, and a page that cannot be filled is passed
|
|
92
94
|
* over. Undefined when nothing is asked; the path (with no `show`) otherwise. */
|
|
93
95
|
export declare function shownRecord(url: URL, workspaces: ReadonlyArray<ScreenDecl>, params: Record<string, string>): string | undefined;
|
|
96
|
+
/** A declared World door, matched by the same normalized path for serving and response ownership. */
|
|
97
|
+
export declare function worldDoorMatcher<Door extends {
|
|
98
|
+
method: string;
|
|
99
|
+
path: string;
|
|
100
|
+
}>(doors: ReadonlyArray<Door>): (method: string, path: string) => {
|
|
101
|
+
door: Door;
|
|
102
|
+
params: Record<string, string>;
|
|
103
|
+
} | undefined;
|
|
104
|
+
/** A request with its path's repeated slashes merged, as a front that merges them routes it (the manifest's
|
|
105
|
+
* `mergeSlashes`); one with none is itself. */
|
|
106
|
+
export declare function slashesMerged(request: Request): Request;
|
|
107
|
+
/** The selected unit's request transformation, shared by serving and response judgment. */
|
|
108
|
+
export declare function unitRequestRouter(m: Pick<DerivedManifest, 'pathPrefix' | 'basePathAlias' | 'pathFromHost' | 'hostParams' | 'headerParams'>, surface: DerivedSurface, routes: ReturnType<typeof compileSurface>): (request: Request) => Request;
|
|
109
|
+
/** A pack's HTTP surface, as the kernel serves it from the pack's parts. */
|
|
94
110
|
export declare function createPackFetch(parts: PackParts, options?: PackFetchOptions): DerivedFetch;
|
|
111
|
+
/** The lane selected by the vendor gateway, shared by serving and response judgment. */
|
|
112
|
+
export declare function laneSelector(decl: LanesDecl, lanes: Record<string, {
|
|
113
|
+
manifest: LanePart['manifest'];
|
|
114
|
+
fetch: Pick<LanePart['fetch'], 'serves' | 'contentHosts'>;
|
|
115
|
+
}>, rootServes?: (method: string, path: string) => boolean): (request: Request) => string | undefined;
|
|
95
116
|
export declare function createVendorFetch(manifest: VendorManifest, lanes: Record<string, LanePart>): DerivedFetch;
|
package/dist/src/pack-fetch.js
CHANGED
|
@@ -71,7 +71,7 @@ function hostMatcher(pattern) {
|
|
|
71
71
|
};
|
|
72
72
|
}
|
|
73
73
|
/** The host a request is for: the vendor host the World's injector names, else its own. */
|
|
74
|
-
const hostOf = (request) => readHostPort(request.headers.get('x-volter-twin-original-host') ?? request.headers.get('host') ?? new URL(request.url).host)?.host ?? '';
|
|
74
|
+
export const hostOf = (request) => readHostPort(request.headers.get('x-volter-twin-original-host') ?? request.headers.get('host') ?? new URL(request.url).host)?.host ?? '';
|
|
75
75
|
/** Where a workspace asked `?show=<type>:<id>` opens the record (architecture, "Workspace"): the built workspace page
|
|
76
76
|
* that shows that type, its parameter the id; else the one that lists the type; else the workspace's home (its root
|
|
77
77
|
* page). Every other parameter a page's path names is the asking page's, and a page that cannot be filled is passed
|
|
@@ -152,12 +152,26 @@ async function encoded(m, request, res) {
|
|
|
152
152
|
const buffered = new ReadableStream({ start(controller) { controller.enqueue(body); controller.close(); } });
|
|
153
153
|
return new Response(buffered, { status: res.status, statusText: res.statusText, headers });
|
|
154
154
|
}
|
|
155
|
-
/** A pack's HTTP surface, as the kernel serves it from the pack's parts. */
|
|
156
155
|
/** A door's path as it is served and listed: under `/_twin/`, whether the manifest writes the prefix or not. */
|
|
157
156
|
const doorPath = (path) => (path.startsWith('/_twin/') ? path : `/_twin${path.startsWith('/') ? '' : '/'}${path}`);
|
|
157
|
+
/** A declared World door, matched by the same normalized path for serving and response ownership. */
|
|
158
|
+
export function worldDoorMatcher(doors) {
|
|
159
|
+
const routes = doors.map((door) => ({ door, method: door.method.toUpperCase(), matches: pathMatcher(doorPath(door.path), false) }));
|
|
160
|
+
return (method, path) => {
|
|
161
|
+
const at = path.replace(/\/+$/, '') || '/';
|
|
162
|
+
for (const route of routes) {
|
|
163
|
+
if (route.method !== method.toUpperCase())
|
|
164
|
+
continue;
|
|
165
|
+
const hit = route.matches(at);
|
|
166
|
+
if (hit)
|
|
167
|
+
return { door: route.door, params: hit.params };
|
|
168
|
+
}
|
|
169
|
+
return undefined;
|
|
170
|
+
};
|
|
171
|
+
}
|
|
158
172
|
/** A request with its path's repeated slashes merged, as a front that merges them routes it (the manifest's
|
|
159
173
|
* `mergeSlashes`); one with none is itself. */
|
|
160
|
-
function slashesMerged(request) {
|
|
174
|
+
export function slashesMerged(request) {
|
|
161
175
|
const url = new URL(request.url);
|
|
162
176
|
const merged = url.pathname.replace(/\/{2,}/g, '/');
|
|
163
177
|
if (merged === url.pathname)
|
|
@@ -165,6 +179,63 @@ function slashesMerged(request) {
|
|
|
165
179
|
url.pathname = merged;
|
|
166
180
|
return new Request(url, request);
|
|
167
181
|
}
|
|
182
|
+
/** The selected unit's request transformation, shared by serving and response judgment. */
|
|
183
|
+
export function unitRequestRouter(m, surface, routes) {
|
|
184
|
+
const prefix = m.pathPrefix ? new RegExp(`^(?:${m.pathPrefix})(?=/)`) : undefined;
|
|
185
|
+
const base = surface.basePath ?? '';
|
|
186
|
+
const baseAlias = m.basePathAlias && base ? new RegExp(`^(?:${m.basePathAlias})(?=/)`) : undefined;
|
|
187
|
+
const hostPath = m.pathFromHost ? new RegExp(m.pathFromHost, 'i') : undefined;
|
|
188
|
+
const hostParams = m.hostParams ? new RegExp(m.hostParams, 'i') : undefined;
|
|
189
|
+
// each operation's spanning parameters, keyed by the operation and the parameter as the route compiles them (a whole
|
|
190
|
+
// segment's label whose name the surface or the manifest declares spanning), so one operation's spanning name never
|
|
191
|
+
// keeps another's slashes, nor a label that only part of a segment holds
|
|
192
|
+
const spanningNames = new Set(surface.spanning ?? []);
|
|
193
|
+
const spanningOf = new Set(surface.operations.flatMap((op) => ((op.basePath ?? base) + op.path).split('/').flatMap((seg) => {
|
|
194
|
+
const label = /^\{([^}]+)\}$/.exec(seg)?.[1];
|
|
195
|
+
return label !== undefined && spanningNames.has(label) ? [`${op.id}\u0000${label}`] : [];
|
|
196
|
+
})));
|
|
197
|
+
/** 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
|
|
198
|
+
* path label (pathFromHost) put in front. */
|
|
199
|
+
const rewritten = (pathname, host) => {
|
|
200
|
+
let path = pathname;
|
|
201
|
+
if (prefix)
|
|
202
|
+
path = path.replace(prefix, '') || '/';
|
|
203
|
+
// a base the vendor serves as its own: the spec's base in its place (a path already under the spec's base stays)
|
|
204
|
+
if (baseAlias && !(path === base || path.startsWith(`${base}/`)))
|
|
205
|
+
path = path.replace(baseAlias, base);
|
|
206
|
+
const label = hostPath?.exec(host)?.[1];
|
|
207
|
+
if (label)
|
|
208
|
+
path = `/${label}${path === '/' ? '' : path}`;
|
|
209
|
+
return path;
|
|
210
|
+
};
|
|
211
|
+
/** The request as its vendor routes it: duplicate slashes collapsed, unless the rewritten path matches an operation
|
|
212
|
+
* through one of that operation's spanning parameters (the surface's and the manifest's), whose value keeps its
|
|
213
|
+
* slashes; then rewritten. */
|
|
214
|
+
return (request) => {
|
|
215
|
+
const url = new URL(request.url);
|
|
216
|
+
const host = hostOf(request);
|
|
217
|
+
const kept = rewritten(url.pathname, host);
|
|
218
|
+
const matched = matchOperation(routes, request.method, kept);
|
|
219
|
+
const spanning = matched !== undefined && Object.keys(matched.params).some((name) => spanningOf.has(`${matched.operation.id}\u0000${name}`));
|
|
220
|
+
const source = spanning ? url.pathname : url.pathname.replace(/\/{2,}/g, '/');
|
|
221
|
+
// a prefix's named groups (Jira's `/ex/jira/(?<cloudId>[^/]+)`) and the host's (E2B's envd at
|
|
222
|
+
// `49983-(?<sandboxID>…).e2b.app`) join the operation's parameters
|
|
223
|
+
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]]; })) };
|
|
224
|
+
const path = spanning ? kept : rewritten(source, host);
|
|
225
|
+
if (path === url.pathname && !Object.keys(groups).length)
|
|
226
|
+
return request;
|
|
227
|
+
// the path the client sent, which a signature over the request (SigV4's canonical URI) covers
|
|
228
|
+
const headers = new Headers(request.headers);
|
|
229
|
+
// the path as first sent: a front before this (an attach bridge) may have named the one its client signed
|
|
230
|
+
if (!headers.has(ORIGINAL_PATH_HEADER))
|
|
231
|
+
headers.set(ORIGINAL_PATH_HEADER, url.pathname);
|
|
232
|
+
if (Object.keys(groups).length)
|
|
233
|
+
headers.set(PREFIX_PARAMS_HEADER, JSON.stringify(groups));
|
|
234
|
+
url.pathname = path;
|
|
235
|
+
return new Request(url, { method: request.method, headers, body: request.body, signal: request.signal, ...(request.body ? { duplex: 'half' } : {}) });
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
/** A pack's HTTP surface, as the kernel serves it from the pack's parts. */
|
|
168
239
|
export function createPackFetch(parts, options = {}) {
|
|
169
240
|
const m = parts.manifest;
|
|
170
241
|
const scope = {
|
|
@@ -267,12 +338,10 @@ export function createPackFetch(parts, options = {}) {
|
|
|
267
338
|
return Response.json(decision.result.body, { status: decision.result.status, headers: decision.result.headers ?? {} });
|
|
268
339
|
return api(withHeaders(request, new Headers([...request.headers, [SCENARIO_DECISION_HEADER, scenarioDecisionHeader(decision)]])));
|
|
269
340
|
};
|
|
270
|
-
const
|
|
271
|
-
|
|
272
|
-
if (!answer)
|
|
341
|
+
for (const d of m.doors ?? [])
|
|
342
|
+
if (!parts.doors?.[d.id])
|
|
273
343
|
throw new Error(`${m.vendor}: the manifest declares the door ${d.id}, and semantics/doors.ts exports no ${d.id}`);
|
|
274
|
-
|
|
275
|
-
});
|
|
344
|
+
const door = worldDoorMatcher(m.doors ?? []);
|
|
276
345
|
const screens = (m.screens ?? []).filter((s) => s.status === 'done' && parts.screens?.[s.id]).map((s) => {
|
|
277
346
|
const hosts = [s.host, ...(s.hosts ?? [])].map(hostMatcher);
|
|
278
347
|
return { decl: s, host: (h) => hosts.map((match) => match(h)).find(Boolean), path: pathMatcher(s.path, true), answer: parts.screens[s.id] };
|
|
@@ -304,59 +373,7 @@ export function createPackFetch(parts, options = {}) {
|
|
|
304
373
|
await runAsVendorMove(async () => parts.clock(await open(new Request(request.url, m.tenant ? { headers: request.headers } : {}), 'clock', 'POST', '/_twin/clock', {})));
|
|
305
374
|
};
|
|
306
375
|
const refusedReadOnly = () => vendorError(m, m.readOnly);
|
|
307
|
-
const
|
|
308
|
-
const base = surface.basePath ?? '';
|
|
309
|
-
const baseAlias = m.basePathAlias && base ? new RegExp(`^(?:${m.basePathAlias})(?=/)`) : undefined;
|
|
310
|
-
const hostPath = m.pathFromHost ? new RegExp(m.pathFromHost, 'i') : undefined;
|
|
311
|
-
const hostParams = m.hostParams ? new RegExp(m.hostParams, 'i') : undefined;
|
|
312
|
-
// each operation's spanning parameters, keyed by the operation and the parameter as the route compiles them (a whole
|
|
313
|
-
// segment's label whose name the surface or the manifest declares spanning), so one operation's spanning name never
|
|
314
|
-
// keeps another's slashes, nor a label that only part of a segment holds
|
|
315
|
-
const spanningNames = new Set(surface.spanning ?? []);
|
|
316
|
-
const spanningOf = new Set(surface.operations.flatMap((op) => ((op.basePath ?? base) + op.path).split('/').flatMap((seg) => {
|
|
317
|
-
const label = /^\{([^}]+)\}$/.exec(seg)?.[1];
|
|
318
|
-
return label !== undefined && spanningNames.has(label) ? [`${op.id}\u0000${label}`] : [];
|
|
319
|
-
})));
|
|
320
|
-
/** 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
|
|
321
|
-
* path label (pathFromHost) put in front. */
|
|
322
|
-
const rewritten = (pathname, host) => {
|
|
323
|
-
let path = pathname;
|
|
324
|
-
if (prefix)
|
|
325
|
-
path = path.replace(prefix, '') || '/';
|
|
326
|
-
// a base the vendor serves as its own: the spec's base in its place (a path already under the spec's base stays)
|
|
327
|
-
if (baseAlias && !(path === base || path.startsWith(`${base}/`)))
|
|
328
|
-
path = path.replace(baseAlias, base);
|
|
329
|
-
const label = hostPath?.exec(host)?.[1];
|
|
330
|
-
if (label)
|
|
331
|
-
path = `/${label}${path === '/' ? '' : path}`;
|
|
332
|
-
return path;
|
|
333
|
-
};
|
|
334
|
-
/** The request as its vendor routes it: duplicate slashes collapsed, unless the rewritten path matches an operation
|
|
335
|
-
* through one of that operation's spanning parameters (the surface's and the manifest's), whose value keeps its
|
|
336
|
-
* slashes; then rewritten. */
|
|
337
|
-
const routed = (request) => {
|
|
338
|
-
const url = new URL(request.url);
|
|
339
|
-
const host = hostOf(request);
|
|
340
|
-
const kept = rewritten(url.pathname, host);
|
|
341
|
-
const matched = matchOperation(routes, request.method, kept);
|
|
342
|
-
const spanning = matched !== undefined && Object.keys(matched.params).some((name) => spanningOf.has(`${matched.operation.id}\u0000${name}`));
|
|
343
|
-
const source = spanning ? url.pathname : url.pathname.replace(/\/{2,}/g, '/');
|
|
344
|
-
// a prefix's named groups (Jira's `/ex/jira/(?<cloudId>[^/]+)`) and the host's (E2B's envd at
|
|
345
|
-
// `49983-(?<sandboxID>…).e2b.app`) join the operation's parameters
|
|
346
|
-
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]]; })) };
|
|
347
|
-
const path = spanning ? kept : rewritten(source, host);
|
|
348
|
-
if (path === url.pathname && !Object.keys(groups).length)
|
|
349
|
-
return request;
|
|
350
|
-
// the path the client sent, which a signature over the request (SigV4's canonical URI) covers
|
|
351
|
-
const headers = new Headers(request.headers);
|
|
352
|
-
// the path as first sent: a front before this (an attach bridge) may have named the one its client signed
|
|
353
|
-
if (!headers.has(ORIGINAL_PATH_HEADER))
|
|
354
|
-
headers.set(ORIGINAL_PATH_HEADER, url.pathname);
|
|
355
|
-
if (Object.keys(groups).length)
|
|
356
|
-
headers.set(PREFIX_PARAMS_HEADER, JSON.stringify(groups));
|
|
357
|
-
url.pathname = path;
|
|
358
|
-
return new Request(url, { method: request.method, headers, body: request.body, signal: request.signal, ...(request.body ? { duplex: 'half' } : {}) });
|
|
359
|
-
};
|
|
376
|
+
const routed = unitRequestRouter(m, surface, routes);
|
|
360
377
|
const rootServes = (method, path) => Boolean(matchOperation(routes, method, path));
|
|
361
378
|
const lane = m.lanes ? laneRouter(m.vendor, m.lanes, parts.lanes ?? {}, rootServes) : undefined;
|
|
362
379
|
// Inspect lane-owned World doors directly, without routing them back through the vendor gateway.
|
|
@@ -455,7 +472,7 @@ export function createPackFetch(parts, options = {}) {
|
|
|
455
472
|
if (own)
|
|
456
473
|
return own;
|
|
457
474
|
// Root World doors belong to the pack at every routed host, including a lane's host.
|
|
458
|
-
const rootDoor =
|
|
475
|
+
const rootDoor = door(arrived.method, asked);
|
|
459
476
|
const toLane = discovery || rootDoor ? undefined : lane?.(arrived);
|
|
460
477
|
if (toLane)
|
|
461
478
|
return toLane(arrived);
|
|
@@ -531,15 +548,12 @@ export function createPackFetch(parts, options = {}) {
|
|
|
531
548
|
resetFakePool(m.service, options.root);
|
|
532
549
|
return Response.json({ kind }, { status: 201 });
|
|
533
550
|
}
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
continue;
|
|
537
|
-
const hit = d.path(path);
|
|
538
|
-
if (!hit)
|
|
539
|
-
continue;
|
|
551
|
+
const hit = door(request.method, path);
|
|
552
|
+
if (hit) {
|
|
540
553
|
if (readOnly && request.method !== 'GET')
|
|
541
554
|
return refusedReadOnly();
|
|
542
|
-
|
|
555
|
+
const d = hit.door;
|
|
556
|
+
return parts.doors[d.id](await open(request, d.id, d.method, d.path, hit.params));
|
|
543
557
|
}
|
|
544
558
|
return gapAnswer(m, request);
|
|
545
559
|
}
|
|
@@ -720,31 +734,16 @@ export function createPackFetch(parts, options = {}) {
|
|
|
720
734
|
const owners = () => flatOwners(ownersByUnit());
|
|
721
735
|
return derivedRequestScopes(m, Object.assign(served, { owners, ownersByUnit, serves, advanceClock: async (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);
|
|
722
736
|
}
|
|
723
|
-
/** The
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
const served = (name) => {
|
|
727
|
-
const part = lanes[name];
|
|
728
|
-
if (!part)
|
|
729
|
-
throw new Error(`${vendor}: the manifest routes to the lane ${name}, and the vendor's fetch is given no ${name}`);
|
|
730
|
-
// the lane is named on the request, so a write it makes records which lane's API made it (LANE_HEADER)
|
|
731
|
-
const named = (request) => {
|
|
732
|
-
const headers = new Headers(request.headers);
|
|
733
|
-
headers.set(LANE_HEADER, name);
|
|
734
|
-
return part.fetch(withHeaders(request, headers));
|
|
735
|
-
};
|
|
736
|
-
return decl.cors ? withCors(decl.cors, named) : named;
|
|
737
|
-
};
|
|
738
|
-
const doors = Object.entries(lanes).flatMap(([name, part]) => (part.manifest.doors ?? []).map((d) => ({
|
|
739
|
-
name, method: d.method.toUpperCase(), matches: pathMatcher(d.path.startsWith('/_twin/') ? d.path : `/_twin/${d.path.replace(/^\//, '')}`, false),
|
|
740
|
-
})));
|
|
737
|
+
/** The lane selected by the vendor gateway, shared by serving and response judgment. */
|
|
738
|
+
export function laneSelector(decl, lanes, rootServes = () => false) {
|
|
739
|
+
const door = worldDoorMatcher(Object.entries(lanes).flatMap(([name, part]) => (part.manifest.doors ?? []).map((d) => ({ ...d, name }))));
|
|
741
740
|
const laneRoutes = decl.routes.map((r) => {
|
|
742
741
|
const at = r.path?.replace(/\/+$/, '');
|
|
743
742
|
const host = r.host !== undefined ? new RegExp(r.host, 'i') : undefined;
|
|
744
743
|
const except = (r.exceptHosts ?? []).map((h) => h.toLowerCase());
|
|
745
744
|
const own = lanes[r.lane]?.fetch.serves;
|
|
746
745
|
return {
|
|
747
|
-
lane:
|
|
746
|
+
lane: r.lane,
|
|
748
747
|
holds: (request, path, name) => (at === undefined || path === at || path.startsWith(`${at}/`))
|
|
749
748
|
&& (host === undefined || host.test(name))
|
|
750
749
|
&& (r.header === undefined || (request.headers.get(r.header.name) ?? '').startsWith(r.header.prefix) !== (r.header.absent === true))
|
|
@@ -752,7 +751,6 @@ function laneRouter(vendor, decl, lanes, rootServes = () => false) {
|
|
|
752
751
|
&& !(r.unlessOnlyRoot && rootServes(request.method, path) && !(own?.(request.method, path) ?? false)),
|
|
753
752
|
};
|
|
754
753
|
});
|
|
755
|
-
const fallback = decl.default !== undefined ? served(decl.default) : undefined;
|
|
756
754
|
return (request) => {
|
|
757
755
|
// `<base>/@<host>/<path>` at a World's place, for a host a lane's built content screen names, goes to that lane, before
|
|
758
756
|
// any lane is picked by the request's host or path: its own fetch answers it by its content route, under the vendor's
|
|
@@ -760,15 +758,37 @@ function laneRouter(vendor, decl, lanes, rootServes = () => false) {
|
|
|
760
758
|
const address = request.headers.has(TWIN_PREFIX_HEADER) ? contentAddressOf(new URL(request.url).pathname) : undefined;
|
|
761
759
|
const content = address && Object.keys(lanes).find((l) => lanes[l].fetch.contentHosts?.includes(address.host));
|
|
762
760
|
if (content)
|
|
763
|
-
return
|
|
761
|
+
return content;
|
|
764
762
|
const path = new URL(request.url).pathname.replace(/\/{2,}/g, '/');
|
|
765
763
|
if (path.startsWith('/_twin/')) {
|
|
766
|
-
const
|
|
767
|
-
if (
|
|
768
|
-
return
|
|
764
|
+
const hit = door(request.method, path);
|
|
765
|
+
if (hit)
|
|
766
|
+
return hit.door.name;
|
|
769
767
|
}
|
|
770
768
|
const name = hostOf(request);
|
|
771
|
-
return laneRoutes.find((r) => r.holds(request, path, name))?.lane ??
|
|
769
|
+
return laneRoutes.find((r) => r.holds(request, path, name))?.lane ?? decl.default;
|
|
770
|
+
};
|
|
771
|
+
}
|
|
772
|
+
/** The vendor's gateway over its lanes: the lane a request goes to (a door to the lane that declares it, then the first
|
|
773
|
+
* route whose conditions all hold, then the default), behind the gateway's CORS; undefined when none takes it. */
|
|
774
|
+
function laneRouter(vendor, decl, lanes, rootServes = () => false) {
|
|
775
|
+
const served = (name) => {
|
|
776
|
+
const part = lanes[name];
|
|
777
|
+
if (!part)
|
|
778
|
+
throw new Error(`${vendor}: the manifest routes to the lane ${name}, and the vendor's fetch is given no ${name}`);
|
|
779
|
+
// the lane is named on the request, so a write it makes records which lane's API made it (LANE_HEADER)
|
|
780
|
+
const named = (request) => {
|
|
781
|
+
const headers = new Headers(request.headers);
|
|
782
|
+
headers.set(LANE_HEADER, name);
|
|
783
|
+
return part.fetch(withHeaders(request, headers));
|
|
784
|
+
};
|
|
785
|
+
return decl.cors ? withCors(decl.cors, named) : named;
|
|
786
|
+
};
|
|
787
|
+
const selected = laneSelector(decl, lanes, rootServes);
|
|
788
|
+
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)]));
|
|
789
|
+
return (request) => {
|
|
790
|
+
const name = selected(request);
|
|
791
|
+
return name === undefined ? undefined : handlers.get(name);
|
|
772
792
|
};
|
|
773
793
|
}
|
|
774
794
|
/** A vendor whose every API is a lane (Cloudflare's API v4 and R2, AWS's services), served from its manifest: the
|
|
@@ -204,6 +204,18 @@ export type TwinPack = {
|
|
|
204
204
|
fill: Record<string, string>;
|
|
205
205
|
account?: {
|
|
206
206
|
field: string;
|
|
207
|
+
} & ({
|
|
208
|
+
tenancy?: never;
|
|
209
|
+
source?: never;
|
|
210
|
+
} | {
|
|
211
|
+
tenancy: string;
|
|
212
|
+
source: string;
|
|
213
|
+
});
|
|
214
|
+
deployment?: {
|
|
215
|
+
field: string;
|
|
216
|
+
object: string;
|
|
217
|
+
source: string;
|
|
218
|
+
organizationOutputs?: string[];
|
|
207
219
|
};
|
|
208
220
|
};
|
|
209
221
|
/** Another pack of this vendor issues the API's credentials; architecture, descriptor World wiring. */
|
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
/**
|
|
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
|
|
4
|
+
* A pack's descriptor.maxRequestBodySize overrides both the transport and decoded-output limits. */
|
|
6
5
|
export declare const DEFAULT_MAX_DECODED_REQUEST_BODY_SIZE: number;
|
|
7
6
|
export declare class RequestBodyTooLargeError extends Error {
|
|
8
7
|
constructor();
|
package/dist/src/request-body.js
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
/**
|
|
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
|
|
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
|
export class RequestBodyTooLargeError extends Error {
|
|
8
7
|
constructor() { super('Payload Too Large'); this.name = 'RequestBodyTooLargeError'; }
|
package/dist/src/runtime.d.ts
CHANGED
|
@@ -7,11 +7,12 @@ export { ORIGINAL_PATH_HEADER } from './sigv4.js';
|
|
|
7
7
|
export { createTwinFetchFromHandler, placedCookies, TWIN_PREFIX_HEADER, TWIN_REQUEST_SCOPES, TWIN_SITES_HEADER, TWIN_SITE_HEADER, siteLabel, siteUrlOf, twinPublicBase, twinSiteUrl, twinVendorUrl, withRequestScopes } from './twin-fetch.js';
|
|
8
8
|
export { isReadOnlyRequest, READ_ONLY_REQUEST_HEADER, ReadOnlyRequestError, runAsReadOnlyRequest, runAsVendorMove, writesRefused } from './request-scope.js';
|
|
9
9
|
export { compileSurface, createDerivedFetch, matchOperation, observeDerivedDispatch } from './derived.js';
|
|
10
|
+
export { hostOf, laneSelector, slashesMerged, unitRequestRouter, worldDoorMatcher } from './pack-fetch.js';
|
|
10
11
|
export { currentTraceparent, deliveryTraceHeaders, newTraceparent, parseTraceparent, runWithRequestTrace, runWithTraceparent, TRACEPARENT_HEADER, traceparentForDelivery, validTraceparent } from './trace-context.js';
|
|
11
12
|
export type { Traceparent } from './trace-context.js';
|
|
12
13
|
export type { DerivedCall, DerivedCoreOutcome, DerivedDispatch, DerivedFetch, DerivedFetchOptions, DerivedHandler, DerivedOperation, DerivedOwner, DerivedSurface, BoardFrame } from './derived.js';
|
|
13
14
|
export { authRefusal, bindSemantics, coreFor, crossCutting, flagOf, derivedRequestScopes, observeTransitions, resourcesOfType as twinResourcesOfType, semanticsContext, stateOf, transitionFor, parseBracketForm, readParams, render as renderDerived, serveCore, sse, vendorError } from './derived-core.js';
|
|
14
|
-
export { deliverEvents, fillEnvelope, setDeliveryObserver, signEvent, signEventAll, verifyEvent } from './events.js';
|
|
15
|
+
export { deliverEvents, fillEnvelope, setDeliveryObserver, settleEventDeliveries, signEvent, signEventAll, verifyEvent } from './events.js';
|
|
15
16
|
export { equalSecrets, hmac, jwks, jwtDecode, jwtSign, jwtSignClaims, jwtVerify, jwtVerifySignature, keyId, lettersFrom, md5, sha256, SIGNING_ALGORITHMS, uuidFrom } from './signing.js';
|
|
16
17
|
export type { HandlerCrypto, Jwk, Jwks, JwtDecodeOptions, JwtSignatureOptions, JwtVerdict, JwtVerificationKey, SigningKey } from './signing.js';
|
|
17
18
|
export type { EventDelivery, EventRender, EventScheme, EventsDecl, EventTransport, EventWrite } from './events.js';
|
package/dist/src/runtime.js
CHANGED
|
@@ -15,9 +15,10 @@ export { ORIGINAL_PATH_HEADER } from "./sigv4.js";
|
|
|
15
15
|
export { createTwinFetchFromHandler, placedCookies, TWIN_PREFIX_HEADER, TWIN_REQUEST_SCOPES, TWIN_SITES_HEADER, TWIN_SITE_HEADER, siteLabel, siteUrlOf, twinPublicBase, twinSiteUrl, twinVendorUrl, withRequestScopes } from "./twin-fetch.js";
|
|
16
16
|
export { isReadOnlyRequest, READ_ONLY_REQUEST_HEADER, ReadOnlyRequestError, runAsReadOnlyRequest, runAsVendorMove, writesRefused } from "./request-scope.js";
|
|
17
17
|
export { compileSurface, createDerivedFetch, matchOperation, observeDerivedDispatch } from "./derived.js";
|
|
18
|
+
export { hostOf, laneSelector, slashesMerged, unitRequestRouter, worldDoorMatcher } from "./pack-fetch.js";
|
|
18
19
|
export { currentTraceparent, deliveryTraceHeaders, newTraceparent, parseTraceparent, runWithRequestTrace, runWithTraceparent, TRACEPARENT_HEADER, traceparentForDelivery, validTraceparent } from "./trace-context.js";
|
|
19
20
|
export { authRefusal, bindSemantics, coreFor, crossCutting, flagOf, derivedRequestScopes, observeTransitions, resourcesOfType as twinResourcesOfType, semanticsContext, stateOf, transitionFor, parseBracketForm, readParams, render as renderDerived, serveCore, sse, vendorError } from "./derived-core.js";
|
|
20
|
-
export { deliverEvents, fillEnvelope, setDeliveryObserver, signEvent, signEventAll, verifyEvent } from "./events.js";
|
|
21
|
+
export { deliverEvents, fillEnvelope, setDeliveryObserver, settleEventDeliveries, signEvent, signEventAll, verifyEvent } from "./events.js";
|
|
21
22
|
export { equalSecrets, hmac, jwks, jwtDecode, jwtSign, jwtSignClaims, jwtVerify, jwtVerifySignature, keyId, lettersFrom, md5, sha256, SIGNING_ALGORITHMS, uuidFrom } from "./signing.js";
|
|
22
23
|
export { emitTwinEvent, eventSubscriptionMatches, listEmittable, runEmitCli } from "./emit.js";
|
|
23
24
|
// The vendor-agnostic CLIENT-SIDE RATE BUDGET — the fail-closed backstop a pack's guarded client
|
package/dist/src/serve-http.d.ts
CHANGED
|
@@ -29,7 +29,7 @@ export type ServeHttpOptions = {
|
|
|
29
29
|
fetch: HttpHandler;
|
|
30
30
|
/** seconds a connection may idle (Bun honors it; Node's keep-alive timeout is set from it) */
|
|
31
31
|
idleTimeout?: number;
|
|
32
|
-
/** Explicit transport limit in bytes; absent,
|
|
32
|
+
/** Explicit transport limit in bytes; absent, 128 MiB on both runtimes. */
|
|
33
33
|
maxRequestBodySize?: number;
|
|
34
34
|
/** what a thrown handler answers with; absent, a 500 carrying the error (as Bun.serve) */
|
|
35
35
|
error?: (error: Error) => Response | Promise<Response>;
|