@volter/world-core 3.0.17 → 3.0.19
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/content-route.d.ts +8 -0
- package/dist/src/content-route.js +27 -0
- package/dist/src/derived-core.d.ts +17 -1
- package/dist/src/derived-core.js +51 -15
- package/dist/src/derived.d.ts +3 -0
- package/dist/src/events.d.ts +13 -2
- package/dist/src/events.js +14 -2
- package/dist/src/file-response.d.ts +4 -3
- package/dist/src/file-response.js +7 -4
- package/dist/src/index.d.ts +1 -1
- package/dist/src/index.js +1 -1
- package/dist/src/pack-fetch.d.ts +5 -0
- package/dist/src/pack-fetch.js +99 -13
- package/dist/src/runtime.d.ts +1 -1
- package/dist/src/runtime.js +1 -1
- package/dist/src/twin-fetch.d.ts +17 -2
- package/dist/src/twin-fetch.js +53 -4
- package/dist/vendor-hosts.cjs +35 -10
- package/dist/vendor-hosts.d.cts +3 -0
- package/package.json +1 -1
- package/src/content-route.ts +24 -0
- package/src/derived-core.ts +67 -19
- package/src/derived.ts +3 -0
- package/src/events.ts +19 -2
- package/src/file-response.ts +7 -4
- package/src/index.ts +1 -1
- package/src/pack-fetch.ts +104 -15
- package/src/runtime.ts +1 -1
- package/src/twin-fetch.ts +45 -4
- package/vendor-hosts.cjs +35 -10
- package/vendor-hosts.d.cts +3 -0
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/** A `/@<host>/<path>` address: the host normalised, the path under it (slashes collapsed), and why the spelling cannot
|
|
2
|
+
* be the host's content (`refused`: a port other than the default), when it cannot. */
|
|
3
|
+
export type ContentAddress = {
|
|
4
|
+
host: string;
|
|
5
|
+
path: string;
|
|
6
|
+
refused?: string;
|
|
7
|
+
};
|
|
8
|
+
export declare function contentAddressOf(pathname: string): ContentAddress | undefined;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// THE CONTENT ROUTE'S ADDRESS (architecture, "Screens", "A page at a World's place"): at a World's place, what a page
|
|
2
|
+
// reaches on another of the pack's hosts that a content screen serves is `<base>/@<host>/<path>`. This reads that
|
|
3
|
+
// address as a browser would have written it: a doubled slash collapsed, `%40` for `@`, the host's case, a trailing dot
|
|
4
|
+
// and a default port (80, 443) normalised away. What it reads is a candidate only: the route is taken when the host is
|
|
5
|
+
// one a built content screen names (pack-fetch.ts), and a request naming no such host is general routing's, as before.
|
|
6
|
+
export function contentAddressOf(pathname) {
|
|
7
|
+
const collapsed = pathname.replace(/\/{2,}/g, '/');
|
|
8
|
+
const named = /^\/(?:@|%40)([^/]+)(\/.*)?$/i.exec(collapsed);
|
|
9
|
+
if (!named)
|
|
10
|
+
return undefined;
|
|
11
|
+
let token;
|
|
12
|
+
try {
|
|
13
|
+
token = decodeURIComponent(named[1]).toLowerCase();
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
return undefined;
|
|
17
|
+
}
|
|
18
|
+
const at = /^([^:]*)(?::(\d*))?$/.exec(token);
|
|
19
|
+
if (!at)
|
|
20
|
+
return undefined;
|
|
21
|
+
const host = at[1].replace(/\.$/, '');
|
|
22
|
+
if (!/^[a-z0-9_-]+(?:\.[a-z0-9_-]+)+$/.test(host))
|
|
23
|
+
return undefined;
|
|
24
|
+
const port = at[2];
|
|
25
|
+
const refused = port && port !== '80' && port !== '443' ? `port ${port} is not the host's` : undefined;
|
|
26
|
+
return { host, path: named[2] ?? '/', ...(refused ? { refused } : {}) };
|
|
27
|
+
}
|
|
@@ -3,7 +3,7 @@ import { GitObjectStore } from './git/objects.js';
|
|
|
3
3
|
import { GitRefs } from './git/refs.js';
|
|
4
4
|
import { type SocketDecl, type SocketSession } from './sockets.js';
|
|
5
5
|
import { type MachinePool } from './machines.js';
|
|
6
|
-
import { type ApplicationAnswer, type EventRender, type EventScheme, type EventsDecl, type EventValues } from './events.js';
|
|
6
|
+
import { type ApplicationAnswer, type ApplicationStandIn, type EventRender, type EventScheme, type EventsDecl, type EventValues } from './events.js';
|
|
7
7
|
import type { RateBudgetDeclaration } from './rateBudget.js';
|
|
8
8
|
import { type HandlerCrypto } from './signing.js';
|
|
9
9
|
import { type ManagedDatabase } from './managed-database.js';
|
|
@@ -268,6 +268,8 @@ export type VendorManifest = {
|
|
|
268
268
|
descriptor: Omit<PackDescriptor, 'vendor'>;
|
|
269
269
|
ingest?: IngestDecl;
|
|
270
270
|
rateBudget?: RateBudgetDeclaration;
|
|
271
|
+
/** the vendor has no vendor-backed half (architecture, "The descriptor"): packOf copies it, and derives no state system */
|
|
272
|
+
vendorBacked?: DerivedManifest['vendorBacked'];
|
|
271
273
|
};
|
|
272
274
|
/** `code` as the vendor writes it: a string, or a number (Discord's JSON error codes are integers) */
|
|
273
275
|
export type ErrorSpec = {
|
|
@@ -293,6 +295,15 @@ export type ScreenDecl = {
|
|
|
293
295
|
demand: string;
|
|
294
296
|
status: 'done' | 'todo';
|
|
295
297
|
controls?: string[];
|
|
298
|
+
/** wherever this screen would answer (its host, a World's place, its path alone), a request the pack's API names for
|
|
299
|
+
* its method is the operation's, not this screen's: a site whose pages draw while its forms and links act on
|
|
300
|
+
* operations the spec writes at the same paths (Hacker News's /login page posts to POST /login; its front page at `/`
|
|
301
|
+
* would otherwise take GET /vote) (architecture, "Screens") */
|
|
302
|
+
yieldsToApi?: true;
|
|
303
|
+
/** a content screen that takes a form at a World's place (`<base>/@<host>/…`), beside GET and HEAD: the methods it
|
|
304
|
+
* takes, with its demand (an upload bucket a page posts a lease's form to: Reddit's S3 hosts) (architecture, "A page
|
|
305
|
+
* at a World's place") */
|
|
306
|
+
takes?: ReadonlyArray<'POST'>;
|
|
296
307
|
/** the vendor's documentation of the round trip or the screen */
|
|
297
308
|
source: string;
|
|
298
309
|
};
|
|
@@ -651,6 +662,9 @@ export declare function flagOf(m: Pick<DerivedManifest, 'booleans'>, value: unkn
|
|
|
651
662
|
* text: a field the operation's spec types as a number or a boolean (`scalars`, by bracket path, written by
|
|
652
663
|
* world-tooling's formScalarsOf) is read as one when its text is that literal; every other field stays the text sent
|
|
653
664
|
* (`metadata[order]=007`, `name=2024`). */
|
|
665
|
+
/** A field set as the object's own, whatever its name (`__proto__`, `constructor` and `prototype` are data a caller
|
|
666
|
+
* may name: Stripe's metadata keys are the caller's), never through what the object inherits. */
|
|
667
|
+
export declare function ownField(node: Record<string, unknown> | unknown[], key: string, value: unknown): void;
|
|
654
668
|
export declare function parseBracketForm(text: string, scalars?: Readonly<Record<string, string | undefined>>): Record<string, unknown>;
|
|
655
669
|
/** A request's parameters, its query's and its body's. With the manifest's `body.form.coerce`, a form's fields the
|
|
656
670
|
* operation's spec types as numbers or booleans are read as them (parseBracketForm); without an operation (a twin door)
|
|
@@ -686,6 +700,8 @@ export type CoreScope = {
|
|
|
686
700
|
readOnly?: boolean;
|
|
687
701
|
/** `semantics/tenant.ts`: the tenant the caller acts in (the manifest's `tenant`), or undefined for none */
|
|
688
702
|
tenant?: (ctx: HandlerContext) => string | undefined | Promise<string | undefined>;
|
|
703
|
+
/** the application a walk stands in for: what the vendor asks it (`ctx.ask`) is answered by it (events.ts) */
|
|
704
|
+
application?: ApplicationStandIn;
|
|
689
705
|
};
|
|
690
706
|
/** The lane a vendor's router sent a request to (pack-fetch's laneRouter sets it). */
|
|
691
707
|
export declare const LANE_HEADER = "x-volter-lane";
|
package/dist/src/derived-core.js
CHANGED
|
@@ -124,6 +124,13 @@ export function flagOf(m, value) {
|
|
|
124
124
|
* text: a field the operation's spec types as a number or a boolean (`scalars`, by bracket path, written by
|
|
125
125
|
* world-tooling's formScalarsOf) is read as one when its text is that literal; every other field stays the text sent
|
|
126
126
|
* (`metadata[order]=007`, `name=2024`). */
|
|
127
|
+
/** A field set as the object's own, whatever its name (`__proto__`, `constructor` and `prototype` are data a caller
|
|
128
|
+
* may name: Stripe's metadata keys are the caller's), never through what the object inherits. */
|
|
129
|
+
export function ownField(node, key, value) {
|
|
130
|
+
Object.defineProperty(node, key, { value, writable: true, enumerable: true, configurable: true });
|
|
131
|
+
}
|
|
132
|
+
/** A field the object holds as its own, never one it inherits (`constructor` is every object's). */
|
|
133
|
+
const ownValue = (node, key) => (Object.hasOwn(node, key) ? node[key] : undefined);
|
|
127
134
|
export function parseBracketForm(text, scalars) {
|
|
128
135
|
const out = {};
|
|
129
136
|
for (const [rawKey, raw] of new URLSearchParams(text)) {
|
|
@@ -133,22 +140,35 @@ export function parseBracketForm(text, scalars) {
|
|
|
133
140
|
: kind === 'integer' ? (/^-?\d+$/.test(raw) ? Number(raw) : raw)
|
|
134
141
|
: kind === 'number' ? (/^-?\d+(\.\d+)?$/.test(raw) ? Number(raw) : raw)
|
|
135
142
|
: raw;
|
|
143
|
+
// every field is the node's own, read and written as such: a key naming an object's machinery (`__proto__[x]`,
|
|
144
|
+
// `constructor[prototype][x]`) is a field of that name, never a walk into what every object inherits
|
|
136
145
|
let node = out;
|
|
137
|
-
parts.
|
|
146
|
+
for (const [i, key] of parts.entries()) {
|
|
147
|
+
// an array holds its items by index (`a[]`, `a[0]`): a named key under one (`a[]=1&a[length]=-1`) has nowhere to
|
|
148
|
+
// go, as a key under text has none (`a=1&a[b]=2`): the first shape stands
|
|
149
|
+
if (Array.isArray(node) && key !== '' && !/^\d+$/.test(key))
|
|
150
|
+
break;
|
|
138
151
|
if (i === parts.length - 1) {
|
|
139
152
|
if (key === '') {
|
|
140
153
|
if (Array.isArray(node))
|
|
141
154
|
node.push(value);
|
|
142
155
|
}
|
|
143
156
|
else
|
|
144
|
-
node
|
|
145
|
-
|
|
157
|
+
ownField(node, key, value);
|
|
158
|
+
break;
|
|
146
159
|
}
|
|
147
160
|
const next = parts[i + 1];
|
|
148
|
-
if (
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
161
|
+
if (key === '')
|
|
162
|
+
break;
|
|
163
|
+
let child = ownValue(node, key);
|
|
164
|
+
if (child === undefined) {
|
|
165
|
+
child = next === '' || /^\d+$/.test(next) ? [] : {};
|
|
166
|
+
ownField(node, key, child);
|
|
167
|
+
}
|
|
168
|
+
if (child === null || typeof child !== 'object')
|
|
169
|
+
break;
|
|
170
|
+
node = child;
|
|
171
|
+
}
|
|
152
172
|
}
|
|
153
173
|
return out;
|
|
154
174
|
}
|
|
@@ -186,24 +206,32 @@ export async function readParams(manifest, request, operation) {
|
|
|
186
206
|
const parts = boundary === null ? [] : multipartParts(new Uint8Array(await request.arrayBuffer()), boundary);
|
|
187
207
|
const out = { ...query };
|
|
188
208
|
for (const part of parts) {
|
|
209
|
+
// each part a field of its own name, whatever the name (`__proto__` included: ownField)
|
|
189
210
|
if (part.filename === null) {
|
|
190
|
-
out
|
|
211
|
+
ownField(out, part.name, new TextDecoder().decode(part.body));
|
|
191
212
|
continue;
|
|
192
213
|
}
|
|
193
214
|
const bytes = part.body;
|
|
194
215
|
const parsed = { name: part.filename, type: part.type ?? '', size: bytes.byteLength, content: new TextDecoder().decode(bytes) };
|
|
195
|
-
out
|
|
216
|
+
ownField(out, part.name, Object.defineProperty(parsed, 'bytes', { value: bytes, enumerable: false }));
|
|
196
217
|
}
|
|
197
218
|
return out;
|
|
198
219
|
}
|
|
220
|
+
// NDJSON (`application/x-ndjson`) is lines of JSON, not a JSON document: the handler reads its text
|
|
221
|
+
const media = (type.split(';')[0] ?? '').trim().toLowerCase();
|
|
222
|
+
const isJson = media.includes('json') && !media.includes('ndjson');
|
|
223
|
+
// a form is a body labelled one (application/x-www-form-urlencoded); a body of any other type (an image, a video, an
|
|
224
|
+
// octet-stream PUT to an upload URL, a text/plain SQL statement, an NDJSON batch) is no form: its parameters are the
|
|
225
|
+
// query's, and the handler reads its text or bytes itself
|
|
226
|
+
const isForm = media === 'application/x-www-form-urlencoded';
|
|
227
|
+
const asJson = isJson || manifest.body.json === 'always';
|
|
228
|
+
if (!isForm && !asJson)
|
|
229
|
+
return query;
|
|
199
230
|
const text = await bodyTextOf(request);
|
|
200
231
|
if (!text)
|
|
201
232
|
return query;
|
|
202
|
-
// NDJSON (`application/x-ndjson`) is lines of JSON, not a JSON document: the handler reads its text
|
|
203
|
-
const isJson = type.includes('json') && !type.includes('ndjson');
|
|
204
|
-
const asJson = isJson || manifest.body.json === 'always';
|
|
205
233
|
// `json: 'always'` reads a body labelled otherwise as JSON (GitHub reads curl -d's); a body that is not JSON is read
|
|
206
|
-
// as
|
|
234
|
+
// as a form only when it is labelled one (a sign-in page's own form, posted to the vendor's page), never a crash
|
|
207
235
|
let body;
|
|
208
236
|
if (asJson) {
|
|
209
237
|
try {
|
|
@@ -212,6 +240,8 @@ export async function readParams(manifest, request, operation) {
|
|
|
212
240
|
catch {
|
|
213
241
|
if (isJson)
|
|
214
242
|
throw new SyntaxError('malformed JSON body');
|
|
243
|
+
if (!isForm)
|
|
244
|
+
return query;
|
|
215
245
|
body = parseBracketForm(text, scalars);
|
|
216
246
|
}
|
|
217
247
|
}
|
|
@@ -598,6 +628,8 @@ function recordedInput(m, call, merged) {
|
|
|
598
628
|
return { door: id, ...asSent };
|
|
599
629
|
return { operationId: id, ...asSent };
|
|
600
630
|
}
|
|
631
|
+
/** The scope each call is served in (serveCore, contextFor), for the contexts a write opens for its hooks. */
|
|
632
|
+
const scopeOfCall = new WeakMap();
|
|
601
633
|
async function writeDetailed(m, call, resource, id, fields, operation, params, root, occurredAt) {
|
|
602
634
|
const withFiles = async (input) => { const files = input.operationId ? await keptFiles(m, call, root) : undefined; return files ? { ...input, files } : input; };
|
|
603
635
|
const { resource: row, result } = await applyTwinWrite(m.service,
|
|
@@ -609,7 +641,9 @@ async function writeDetailed(m, call, resource, id, fields, operation, params, r
|
|
|
609
641
|
: { operation, subjectType: storedType(m, resource), subjectId: id, fields, input: await withFiles(recordedInput(m, call, params)), occurredAt, actor: { kind: 'agent', ...(m.identity ? { id: m.identity(call.request.headers.get('authorization'), root) } : {}) } }, root);
|
|
610
642
|
await keepCounts(m, call, resource, row, params, root, occurredAt);
|
|
611
643
|
const body = view(m, resource, row);
|
|
612
|
-
|
|
644
|
+
// the write's own context for its hooks, in the scope the call was served in (a walk's application answers, a tenant,
|
|
645
|
+
// the managed database), at the write's moment
|
|
646
|
+
const context = () => contextFor(m, { ...call, request: call.request.clone() }, { ...(scopeOfCall.get(call) ?? {}), ...(root !== undefined ? { root } : {}), clock: () => occurredAt });
|
|
613
647
|
if (m.onWrite)
|
|
614
648
|
await m.onWrite({ operation, storedType: storedType(m, resource), body, ...(root !== undefined ? { root } : {}), occurredAt, request: call.request, context });
|
|
615
649
|
if (m.events) {
|
|
@@ -940,6 +974,7 @@ export async function serveCore(m, call, scope = {}) {
|
|
|
940
974
|
if (!resource || !decl)
|
|
941
975
|
return { unmodeled: `no manifest entry for resource ${resource ?? '(none)'}` };
|
|
942
976
|
call = adoptedPath(m, call, root);
|
|
977
|
+
scopeOfCall.set(call, scope);
|
|
943
978
|
// the body is read from a copy, so the call's own request is still unread for the write hook's context
|
|
944
979
|
const params = await boundaryParams(m, { ...call, request: call.request.clone() }, root);
|
|
945
980
|
const idParam = subjectOf(m, resource, call);
|
|
@@ -1094,6 +1129,7 @@ export async function contextFor(m, call, scope) {
|
|
|
1094
1129
|
const root = scope.root;
|
|
1095
1130
|
const asAsked = call;
|
|
1096
1131
|
call = adoptedPath(m, call, root);
|
|
1132
|
+
scopeOfCall.set(call, scope);
|
|
1097
1133
|
const at = (scope.clock ?? worldNow)();
|
|
1098
1134
|
// the guard's parse when it made one (taken once), else this context's own
|
|
1099
1135
|
const parsed = takeParsedBody(call.request);
|
|
@@ -1134,7 +1170,7 @@ export async function contextFor(m, call, scope) {
|
|
|
1134
1170
|
publicBase: twinPublicBase(call.request),
|
|
1135
1171
|
scenario: scenarioDecisionOf(call.request),
|
|
1136
1172
|
deliver: (url, init) => deliverToApplication(url, init),
|
|
1137
|
-
ask: (url, init, within) => askApplication(url, init, within),
|
|
1173
|
+
ask: (url, init, within) => askApplication(url, init, within, scope.application),
|
|
1138
1174
|
worldEnv: (name) => worldEnvValue(name),
|
|
1139
1175
|
asVendor: (fn) => runAsVendorMove(fn),
|
|
1140
1176
|
git: (name) => {
|
package/dist/src/derived.d.ts
CHANGED
|
@@ -117,6 +117,9 @@ export type DerivedFetch = ((request: Request) => Promise<Response>) & {
|
|
|
117
117
|
boards?: boolean;
|
|
118
118
|
/** Whether a built screen of it takes a URL: a board frame it draws. */
|
|
119
119
|
draws?(url: string): boolean;
|
|
120
|
+
/** The hosts its built content screens name: a vendor's lane router sends `<base>/@<host>/<path>` for one of them to
|
|
121
|
+
* this fetch (pack-fetch.ts). */
|
|
122
|
+
contentHosts?: ReadonlyArray<string>;
|
|
120
123
|
};
|
|
121
124
|
/** A workspace screen a pack draws, as `GET /twin` lists it: its id and path (Viewing a World). */
|
|
122
125
|
export type Workspace = {
|
package/dist/src/events.d.ts
CHANGED
|
@@ -198,9 +198,20 @@ export type ApplicationAnswer = {
|
|
|
198
198
|
body: string;
|
|
199
199
|
missed?: 'timeout' | 'unreachable';
|
|
200
200
|
};
|
|
201
|
+
/** Who answers what the vendor asks the application (`ctx.ask`) before the World's application route does, given to a
|
|
202
|
+
* pack's fetch (`PackFetchOptions.application`) and belonging to that fetch alone: an answer it gives is the
|
|
203
|
+
* application's; `'unreachable'` is a question nobody answers (a walk's life, a sealed World, takes no other); undefined
|
|
204
|
+
* is a question it does not take, which goes on as any other does: the application route, the World's egress rule, the
|
|
205
|
+
* network. A walk's stand-in answers every question (its life's answers, else unreachable); a hosted World's router
|
|
206
|
+
* answers only what it can answer truthfully (WorldDoors.askInWorld) and leaves the rest. */
|
|
207
|
+
export type ApplicationStandIn = (url: string, init: RequestInit) => Promise<{
|
|
208
|
+
status: number;
|
|
209
|
+
body: string;
|
|
210
|
+
} | 'unreachable' | undefined>;
|
|
201
211
|
/** A message the vendor sends the application's server and decides by its answer (Stripe's real-time authorization
|
|
202
|
-
* request, answered within its window): over the same transport, waited on for at most `within` milliseconds
|
|
203
|
-
|
|
212
|
+
* request, answered within its window): over the same transport, waited on for at most `within` milliseconds, the
|
|
213
|
+
* fetch's stand-in first when it was given one (its window the same). */
|
|
214
|
+
export declare function askApplication(url: string, init: RequestInit, within: number, standIn?: ApplicationStandIn): Promise<ApplicationAnswer>;
|
|
204
215
|
/** Every event a write sends, rendered, stored when the vendor keeps its events, signed, delivered to each endpoint
|
|
205
216
|
* that takes it, and recorded. */
|
|
206
217
|
export declare function deliverEvents(service: string, decl: EventsDecl, write: EventWrite, data: (type: string) => Promise<Record<string, unknown>>, transport?: EventTransport, values?: (type: string) => Promise<Record<string, unknown>>, onAnswer?: (answer: DeliveryAnswer) => Promise<void>): Promise<EventDelivery[]>;
|
package/dist/src/events.js
CHANGED
|
@@ -223,8 +223,20 @@ export async function deliverToApplication(url, init) {
|
|
|
223
223
|
}
|
|
224
224
|
}
|
|
225
225
|
/** A message the vendor sends the application's server and decides by its answer (Stripe's real-time authorization
|
|
226
|
-
* request, answered within its window): over the same transport, waited on for at most `within` milliseconds
|
|
227
|
-
|
|
226
|
+
* request, answered within its window): over the same transport, waited on for at most `within` milliseconds, the
|
|
227
|
+
* fetch's stand-in first when it was given one (its window the same). */
|
|
228
|
+
export async function askApplication(url, init, within, standIn) {
|
|
229
|
+
if (standIn) {
|
|
230
|
+
let timer;
|
|
231
|
+
const late = new Promise((done) => { timer = setTimeout(() => done('late'), within); });
|
|
232
|
+
const stood = await Promise.race([standIn(url, init), late]).finally(() => clearTimeout(timer));
|
|
233
|
+
if (stood === 'late')
|
|
234
|
+
return { status: 0, body: '', missed: 'timeout' };
|
|
235
|
+
if (stood === 'unreachable')
|
|
236
|
+
return { status: 0, body: '', missed: 'unreachable' };
|
|
237
|
+
if (stood !== undefined)
|
|
238
|
+
return stood;
|
|
239
|
+
}
|
|
228
240
|
const [{ appDestination, appFetch }, { worldEgressRefusal }] = await Promise.all([
|
|
229
241
|
import('../app-route.cjs'),
|
|
230
242
|
import('../network-policy.cjs'),
|
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
export declare function contentTypeOf(path: string): string;
|
|
3
3
|
export declare function fileResponse(path: string, init?: ResponseInit): Response;
|
|
4
4
|
/**
|
|
5
|
-
* Stored bytes as a response, honouring a single `Range: bytes=` as RFC 9110 §14 has it:
|
|
6
|
-
*
|
|
7
|
-
*
|
|
5
|
+
* Stored bytes as a response, honouring a single `Range: bytes=` as RFC 9110 §14 has it: every satisfiable range is a
|
|
6
|
+
* 206 with its Content-Range, `bytes=0-` (the whole body) included; a range that does not parse, a first-byte-pos past
|
|
7
|
+
* its last-byte-pos (`bytes=5-2`) included, is ignored and the whole body is a 200; a well-formed range that starts past
|
|
8
|
+
* the end (any range of an empty body) is a 416. `nosniff` stops a browser second-guessing the content type the vendor gave.
|
|
8
9
|
*/
|
|
9
10
|
export declare function bytesResponse(request: Request, bytes: Uint8Array, contentType: string, cacheControl?: string): Response;
|
|
@@ -21,9 +21,10 @@ export function fileResponse(path, init) {
|
|
|
21
21
|
return new Response(readFileSync(path), { ...init, headers });
|
|
22
22
|
}
|
|
23
23
|
/**
|
|
24
|
-
* Stored bytes as a response, honouring a single `Range: bytes=` as RFC 9110 §14 has it:
|
|
25
|
-
*
|
|
26
|
-
*
|
|
24
|
+
* Stored bytes as a response, honouring a single `Range: bytes=` as RFC 9110 §14 has it: every satisfiable range is a
|
|
25
|
+
* 206 with its Content-Range, `bytes=0-` (the whole body) included; a range that does not parse, a first-byte-pos past
|
|
26
|
+
* its last-byte-pos (`bytes=5-2`) included, is ignored and the whole body is a 200; a well-formed range that starts past
|
|
27
|
+
* the end (any range of an empty body) is a 416. `nosniff` stops a browser second-guessing the content type the vendor gave.
|
|
27
28
|
*/
|
|
28
29
|
export function bytesResponse(request, bytes, contentType, cacheControl = 'public, max-age=604800, immutable') {
|
|
29
30
|
const total = bytes.length;
|
|
@@ -43,7 +44,9 @@ export function bytesResponse(request, bytes, contentType, cacheControl = 'publi
|
|
|
43
44
|
if (start >= total || (range[1] === '' && Number(range[2]) === 0))
|
|
44
45
|
return new Response(null, { status: 416, headers: { ...base, 'content-range': `bytes */${total}` } });
|
|
45
46
|
}
|
|
46
|
-
|
|
47
|
+
// every satisfiable range is answered as one, the whole body's `bytes=0-` included: a media player (Safari's) reads a
|
|
48
|
+
// 206 with its Content-Range as the server taking ranges, and a 200 as one it cannot seek in
|
|
49
|
+
const partial = valid;
|
|
47
50
|
const body = bytes.slice(start, end + 1);
|
|
48
51
|
return new Response(request.method === 'HEAD' ? null : new Blob([body]), {
|
|
49
52
|
status: partial ? 206 : 200,
|
package/dist/src/index.d.ts
CHANGED
|
@@ -36,7 +36,7 @@ export { VendorUnreachableError } from './vendor-call.js';
|
|
|
36
36
|
export { isReadOnlyRequest, READ_ONLY_REQUEST_HEADER } from './request-scope.js';
|
|
37
37
|
export { serveHttp, serveStream, type HttpHandler, type HttpServer, type ServeHttpOptions } from './serve-http.js';
|
|
38
38
|
export type { TwinStream, TwinStreamConnection, TwinStreamSink } from './twin-fetch.js';
|
|
39
|
-
export { twinSiteUrl } from './twin-fetch.js';
|
|
39
|
+
export { siteLabel, siteUrlOf, twinSiteUrl } from './twin-fetch.js';
|
|
40
40
|
export { statefulTwinManifest, twinManifest } from './scenario.js';
|
|
41
41
|
export { assetContentType, packAsset } from './pack-assets.js';
|
|
42
42
|
export { bytesResponse, contentTypeOf, fileResponse } from './file-response.js';
|
package/dist/src/index.js
CHANGED
|
@@ -29,7 +29,7 @@ export { isReadOnlyRequest, READ_ONLY_REQUEST_HEADER } from "./request-scope.js"
|
|
|
29
29
|
// ── the serve factory ──────────────────────────────────────────────────────────────────────────
|
|
30
30
|
export { serveHttp, serveStream } from "./serve-http.js";
|
|
31
31
|
// where a page links a hostname its twin answers, inside the World that shows it
|
|
32
|
-
export { twinSiteUrl } from "./twin-fetch.js";
|
|
32
|
+
export { siteLabel, siteUrlOf, twinSiteUrl } from "./twin-fetch.js";
|
|
33
33
|
export { statefulTwinManifest, twinManifest } from "./scenario.js";
|
|
34
34
|
export { assetContentType, packAsset } from "./pack-assets.js";
|
|
35
35
|
export { bytesResponse, contentTypeOf, fileResponse } from "./file-response.js";
|
package/dist/src/pack-fetch.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { type DerivedManifest, type Handler, type HandlerContext, type VendorManifest } from './derived-core.js';
|
|
2
2
|
import { type DerivedFetch, type DerivedOwner, type DerivedSurface, type Workspace, type BoardFrame } from './derived.js';
|
|
3
3
|
import { type PackScenarioAdapter } from './scenario.js';
|
|
4
|
+
import type { ApplicationStandIn } from './events.js';
|
|
4
5
|
import { type SocketEngine } from './sockets.js';
|
|
5
6
|
import { type GraphqlPart } from './graphql-wire.js';
|
|
6
7
|
/** What a pack declares and writes, and nothing it assembles itself. */
|
|
@@ -56,6 +57,8 @@ export type LanePart = {
|
|
|
56
57
|
serves?: (method: string, path: string) => boolean;
|
|
57
58
|
boards?: boolean;
|
|
58
59
|
draws?: (url: string) => boolean;
|
|
60
|
+
/** the hosts the lane's built content screens name: its vendor's router sends `<base>/@<host>/` there */
|
|
61
|
+
contentHosts?: ReadonlyArray<string>;
|
|
59
62
|
};
|
|
60
63
|
manifest: {
|
|
61
64
|
doors?: ReadonlyArray<{
|
|
@@ -73,6 +76,8 @@ export type PackFetchOptions = {
|
|
|
73
76
|
clock?: () => string;
|
|
74
77
|
database?: string;
|
|
75
78
|
scenarioPath?: string;
|
|
79
|
+
/** the application a walk stands in for (a life's `application` answers): this fetch's `ctx.ask` is answered by it */
|
|
80
|
+
application?: ApplicationStandIn;
|
|
76
81
|
};
|
|
77
82
|
/** A pack's HTTP surface, as the kernel serves it from the pack's parts. */
|
|
78
83
|
export declare function createPackFetch(parts: PackParts, options?: PackFetchOptions): DerivedFetch;
|
package/dist/src/pack-fetch.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
// one of them is answered over the contract's context, which the kernel opens: a pack opens none, reads the tree and
|
|
7
7
|
// writes it only through one.
|
|
8
8
|
import { ORIGINAL_PATH_HEADER } from "./sigv4.js";
|
|
9
|
-
import { TWIN_PREFIX_HEADER } from "./twin-fetch.js";
|
|
9
|
+
import { placedCookies, TWIN_PREFIX_HEADER } from "./twin-fetch.js";
|
|
10
10
|
import { PREFIX_PARAMS_HEADER } from "./derived.js";
|
|
11
11
|
import { withHeaders } from "./with-headers.js";
|
|
12
12
|
import { authRefusal, bindSemantics, contextFor, LANE_HEADER, SCENARIO_DECISION_HEADER, coreFor, crossCutting, derivedRequestScopes, vendorError } from "./derived-core.js";
|
|
@@ -17,6 +17,9 @@ import { getActiveWorldStore } from "./world-store.js";
|
|
|
17
17
|
import { runWithCorrelationId } from "./actions.js";
|
|
18
18
|
import { deferPerform, performRequestGroup, RefusedWriteError, VendorWriteError } from "./head.js";
|
|
19
19
|
import { withCors } from "./cors.js";
|
|
20
|
+
import { contentAddressOf } from "./content-route.js";
|
|
21
|
+
import { getPack } from "./packRegistry.js";
|
|
22
|
+
import { hostRules } from '../vendor-hosts.cjs';
|
|
20
23
|
import { socketUpgrade } from "./sockets.js";
|
|
21
24
|
import { MACHINE_POOL, POOL_KINDS } from "./machines.js";
|
|
22
25
|
import { applyTwinWrite } from "./serve.js";
|
|
@@ -112,6 +115,7 @@ export function createPackFetch(parts, options = {}) {
|
|
|
112
115
|
...(options.clock ? { clock: options.clock } : {}),
|
|
113
116
|
...(options.database !== undefined ? { database: options.database, readOnly: options.readOnly ?? false } : {}),
|
|
114
117
|
...(parts.tenant ? { tenant: parts.tenant } : {}),
|
|
118
|
+
...(options.application ? { application: options.application } : {}),
|
|
115
119
|
};
|
|
116
120
|
// a command table's surface (Redis's, a vendor that carries Redis over HTTP) names no HTTP operation: its wire is the
|
|
117
121
|
// pack's front (its `around`), and the table is the denominator the grade counts
|
|
@@ -267,6 +271,78 @@ export function createPackFetch(parts, options = {}) {
|
|
|
267
271
|
};
|
|
268
272
|
const rootServes = (method, path) => Boolean(matchOperation(routes, method, path));
|
|
269
273
|
const lane = m.lanes ? laneRouter(m.vendor, m.lanes, parts.lanes ?? {}, rootServes) : undefined;
|
|
274
|
+
// at a World's place, a page's asset or upload on another of the pack's content hosts is `<base>/@<host>/<path>`
|
|
275
|
+
// (architecture, "A page at a World's place"): answered by the built content screen that names that host exactly,
|
|
276
|
+
// routed from it, and by nothing else; a request naming no such host is general routing's
|
|
277
|
+
const contentScreens = screens.filter((s) => s.decl.kind === 'content');
|
|
278
|
+
for (const s of contentScreens)
|
|
279
|
+
if (s.decl.yieldsToApi)
|
|
280
|
+
throw new Error(`${m.vendor}: the content screen ${s.decl.id} declares yieldsToApi; a content screen answers its host's requests alone (architecture, "Screens")`);
|
|
281
|
+
const contentHosts = [...new Set(contentScreens.flatMap((s) => [s.decl.host, ...(s.decl.hosts ?? [])]).filter((h) => !/[{<]/.test(h)).map((h) => h.toLowerCase()))];
|
|
282
|
+
const contentNotFound = (host, path) => {
|
|
283
|
+
const nf = m.notFound;
|
|
284
|
+
return vendorError(m, { status: nf.status, message: nf.message.replace(/\{object\}/g, host).replace(/\{id\}/g, path), ...(nf.code !== undefined ? { code: nf.code } : {}), ...(nf.kind !== undefined ? { kind: nf.kind } : {}) });
|
|
285
|
+
};
|
|
286
|
+
/** A request at a World's place for `/@<host>/<path>`, `<host>` one a built content screen of this unit names exactly:
|
|
287
|
+
* that screen's answer, or the pack's not-found for what the host or the screen does not take. The path is held to
|
|
288
|
+
* the descriptor's host rules (`rules`: this unit's, or its vendor's for a lane), read as the injector reads them.
|
|
289
|
+
* Undefined for any other request. */
|
|
290
|
+
// the descriptor's host rules: this unit's, or, for a lane (which declares none), its vendor's as registered
|
|
291
|
+
const descriptorHosts = () => m.descriptor?.hosts ?? getPack(m.vendor)?.hosts;
|
|
292
|
+
const atContentHost = async (request, rules = descriptorHosts()) => {
|
|
293
|
+
if (!request.headers.has(TWIN_PREFIX_HEADER))
|
|
294
|
+
return undefined;
|
|
295
|
+
const url = new URL(request.url);
|
|
296
|
+
const address = contentAddressOf(url.pathname);
|
|
297
|
+
if (!address || !contentHosts.includes(address.host))
|
|
298
|
+
return undefined;
|
|
299
|
+
const { host, path: inner } = address;
|
|
300
|
+
if (address.refused)
|
|
301
|
+
return contentNotFound(host, inner);
|
|
302
|
+
const declared = hostRules(rules);
|
|
303
|
+
if (declared.names(host) && !declared.takes(host, inner))
|
|
304
|
+
return contentNotFound(host, inner);
|
|
305
|
+
const onHost = contentScreens.filter((s) => [s.decl.host, ...(s.decl.hosts ?? [])].some((h) => h.toLowerCase() === host));
|
|
306
|
+
const method = request.method.toUpperCase();
|
|
307
|
+
const allowed = (s) => method === 'GET' || method === 'HEAD' || (method === 'POST' && (s.decl.takes ?? []).includes('POST'));
|
|
308
|
+
// the request as it reaches the content host, through the pack's own routing of a host (its path prefix and labels)
|
|
309
|
+
url.pathname = inner;
|
|
310
|
+
const headers = new Headers(request.headers);
|
|
311
|
+
headers.set('x-volter-twin-original-host', host);
|
|
312
|
+
const sent = method === 'POST' && request.body ? { body: request.body, duplex: 'half' } : {};
|
|
313
|
+
const there = routed(new Request(url, { method, headers, signal: request.signal, ...sent }));
|
|
314
|
+
const path = new URL(there.url).pathname.replace(/\/+$/, '') || '/';
|
|
315
|
+
const hit = onHost.map((s) => ({ s, hit: s.path(path) })).find((x) => x.hit && allowed(x.s));
|
|
316
|
+
if (!hit)
|
|
317
|
+
return contentNotFound(host, inner);
|
|
318
|
+
const asked = options.readOnly && !isReadOnlyRequest(there) ? withHeaders(there, new Headers([...there.headers, [READ_ONLY_REQUEST_HEADER, '1']])) : there;
|
|
319
|
+
if (method === 'POST' && ((options.readOnly ?? false) || isReadOnlyRequest(asked)))
|
|
320
|
+
return refusedReadOnly();
|
|
321
|
+
if (parts.clock)
|
|
322
|
+
await runAsVendorMove(async () => parts.clock(await open(new Request(asked.url, m.tenant ? { headers: asked.headers } : {}), 'clock', 'POST', '/_twin/clock', {})));
|
|
323
|
+
return hit.s.answer(await open(asked, hit.s.decl.id, method, hit.s.decl.path, { ...(hit.s.host(host)?.params ?? {}), ...hit.hit.params }));
|
|
324
|
+
};
|
|
325
|
+
/** A request as the kernel reads it: the headers only the kernel names taken off, its body decoded (undefined when it
|
|
326
|
+
* does not decode as its Content-Encoding says). */
|
|
327
|
+
const prepared = async (arrived) => {
|
|
328
|
+
if (arrived.headers.has(SCENARIO_DECISION_HEADER) || arrived.headers.has(PREFIX_PARAMS_HEADER)) {
|
|
329
|
+
const headers = new Headers(arrived.headers);
|
|
330
|
+
headers.delete(SCENARIO_DECISION_HEADER);
|
|
331
|
+
headers.delete(PREFIX_PARAMS_HEADER);
|
|
332
|
+
arrived = new Request(arrived, { headers });
|
|
333
|
+
}
|
|
334
|
+
return decoded(m, arrived);
|
|
335
|
+
};
|
|
336
|
+
const undecodable = (arrived) => vendorError(m, m.encodings?.undecodable ?? { status: 400, message: `the body does not decode as its Content-Encoding (${arrived.headers.get('content-encoding')}) says` });
|
|
337
|
+
/** The content route at the request's arrival: the answer, or undefined when this unit's content screens name no such
|
|
338
|
+
* host. */
|
|
339
|
+
const answerContent = async (arrived) => {
|
|
340
|
+
const address = arrived.headers.has(TWIN_PREFIX_HEADER) ? contentAddressOf(new URL(arrived.url).pathname) : undefined;
|
|
341
|
+
if (!address || !contentHosts.includes(address.host))
|
|
342
|
+
return undefined;
|
|
343
|
+
const readable = await prepared(arrived);
|
|
344
|
+
return readable ? atContentHost(readable) : undecodable(arrived);
|
|
345
|
+
};
|
|
270
346
|
const fetch = async (arrived) => {
|
|
271
347
|
// the discovery door is the pack's own, whichever lane would take its path
|
|
272
348
|
const asked = new URL(arrived.url).pathname.replace(/\/+$/, '') || '/';
|
|
@@ -280,19 +356,18 @@ export function createPackFetch(parts, options = {}) {
|
|
|
280
356
|
const frames = await parts.board(await open(request, 'board', 'GET', '/twin/board', {}));
|
|
281
357
|
return Response.json({ frames: frames.map((f) => ({ ...f, drawn: drawsUrl(f.url) })) });
|
|
282
358
|
}
|
|
359
|
+
// `<base>/@<host>/<path>` at a World's place, for a host a built content screen of this pack names, is that screen's
|
|
360
|
+
// alone (only the kernel names a scenario's decision or a path prefix's parameters: `prepared` takes them off); for
|
|
361
|
+
// one a lane's names, the lane router sends it to that lane's own fetch, wrapped as any request to it is
|
|
362
|
+
const own = await answerContent(arrived);
|
|
363
|
+
if (own)
|
|
364
|
+
return own;
|
|
283
365
|
const toLane = discovery ? undefined : lane?.(arrived);
|
|
284
366
|
if (toLane)
|
|
285
367
|
return toLane(arrived);
|
|
286
|
-
|
|
287
|
-
if (arrived.headers.has(SCENARIO_DECISION_HEADER) || arrived.headers.has(PREFIX_PARAMS_HEADER)) {
|
|
288
|
-
const headers = new Headers(arrived.headers);
|
|
289
|
-
headers.delete(SCENARIO_DECISION_HEADER);
|
|
290
|
-
headers.delete(PREFIX_PARAMS_HEADER);
|
|
291
|
-
arrived = new Request(arrived, { headers });
|
|
292
|
-
}
|
|
293
|
-
const readable = await decoded(m, arrived);
|
|
368
|
+
const readable = await prepared(arrived);
|
|
294
369
|
if (!readable)
|
|
295
|
-
return
|
|
370
|
+
return undecodable(arrived);
|
|
296
371
|
const incoming = routed(readable);
|
|
297
372
|
// a twin started read-only marks every request so, so every context it opens knows (isReadOnlyRequest)
|
|
298
373
|
const request = options.readOnly && !isReadOnlyRequest(incoming) ? withHeaders(incoming, new Headers([...incoming.headers, [READ_ONLY_REQUEST_HEADER, '1']])) : incoming;
|
|
@@ -355,8 +430,10 @@ export function createPackFetch(parts, options = {}) {
|
|
|
355
430
|
// whichever host the twin is reached (its links carry the twin's own base), never a root screen off its own host
|
|
356
431
|
const host = hostOf(request);
|
|
357
432
|
const exact = (s) => [s.decl.host, ...(s.decl.hosts ?? [])].some((h) => !/[{<]/.test(h) && h.toLowerCase() === host);
|
|
358
|
-
const hits = screens.map((s) => ({ s, hit: s.path(path), onHost: s.host(host) })).filter((x) => x.hit);
|
|
359
433
|
const apiPath = Boolean(matchOperation(routes, request.method, url.pathname, url.searchParams, request.headers));
|
|
434
|
+
// a screen that yields to the API draws only what no operation names, wherever it would answer (architecture,
|
|
435
|
+
// "Screens": Hacker News's /login page, whose form posts to POST /login)
|
|
436
|
+
const hits = screens.map((s) => ({ s, hit: s.path(path), onHost: s.host(host) })).filter((x) => x.hit && !(apiPath && x.s.decl.yieldsToApi));
|
|
360
437
|
// reached at a World's place for the twin (`x-forwarded-prefix`, served-world's wire), the host is the World's: a
|
|
361
438
|
// workspace is shown there by its path alone, its root too (the console frames it so)
|
|
362
439
|
const placed = request.headers.has(TWIN_PREFIX_HEADER);
|
|
@@ -421,7 +498,9 @@ export function createPackFetch(parts, options = {}) {
|
|
|
421
498
|
headers.set('content-type', m.jsonContentType);
|
|
422
499
|
return new Response(res.body, { status: res.status, statusText: res.statusText, headers });
|
|
423
500
|
};
|
|
424
|
-
const
|
|
501
|
+
const cors = m.cors ? withCors(m.cors, labelled) : labelled;
|
|
502
|
+
// at a World's place every cookie the twin sets is kept to the twin's base path (twin-fetch.ts, placedCookies)
|
|
503
|
+
const served = async (request) => placedCookies(request, await cors(request));
|
|
425
504
|
// which paths this pack's API serves, for a vendor's lane router that asks (unlessOnlyRoot)
|
|
426
505
|
const serves = (method, path) => Boolean(matchOperation(routes, method, path));
|
|
427
506
|
// the vendor's sockets: a request to a declared socket's path opens a session of its engine, over a context of the
|
|
@@ -434,7 +513,7 @@ export function createPackFetch(parts, options = {}) {
|
|
|
434
513
|
async (asked) => engineOf()?.serve(asked)) : undefined;
|
|
435
514
|
// what serves each operation: the pack's own, and each lane's
|
|
436
515
|
const owners = () => ({ ...api.owners(), ...lanesOwners(parts.lanes ?? {}) });
|
|
437
|
-
return derivedRequestScopes(m, Object.assign(served, { owners, serves, workspaces, boards: Boolean(parts.board), draws: drawsUrl, ...(upgrade ? { upgrade } : {}) }));
|
|
516
|
+
return derivedRequestScopes(m, Object.assign(served, { owners, serves, workspaces, boards: Boolean(parts.board), draws: drawsUrl, contentHosts, ...(upgrade ? { upgrade } : {}) }));
|
|
438
517
|
}
|
|
439
518
|
/** The vendor's gateway over its lanes: the lane a request goes to (a door to the lane that declares it, then the first
|
|
440
519
|
* route whose conditions all hold, then the default), behind the gateway's CORS; undefined when none takes it. */
|
|
@@ -470,6 +549,13 @@ function laneRouter(vendor, decl, lanes, rootServes = () => false) {
|
|
|
470
549
|
});
|
|
471
550
|
const fallback = decl.default !== undefined ? served(decl.default) : undefined;
|
|
472
551
|
return (request) => {
|
|
552
|
+
// `<base>/@<host>/<path>` at a World's place, for a host a lane's built content screen names, goes to that lane, before
|
|
553
|
+
// any lane is picked by the request's host or path: its own fetch answers it by its content route, under the vendor's
|
|
554
|
+
// host rules (architecture, "A page at a World's place")
|
|
555
|
+
const address = request.headers.has(TWIN_PREFIX_HEADER) ? contentAddressOf(new URL(request.url).pathname) : undefined;
|
|
556
|
+
const content = address && Object.keys(lanes).find((l) => lanes[l].fetch.contentHosts?.includes(address.host));
|
|
557
|
+
if (content)
|
|
558
|
+
return served(content);
|
|
473
559
|
const path = new URL(request.url).pathname.replace(/\/{2,}/g, '/');
|
|
474
560
|
if (path.startsWith('/_twin/')) {
|
|
475
561
|
const door = doors.find((d) => d.method === request.method && d.matches(path));
|
package/dist/src/runtime.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ export { parseScenarioDocument, scenarioFaultResult, ScenarioEngine, ScenarioErr
|
|
|
4
4
|
export { WORLD_CLOCK_ENV, worldNow } from './world-clock.js';
|
|
5
5
|
export { WORLD_ENV_NAMES_ENV, worldEnvValue } from './world-env.js';
|
|
6
6
|
export { ORIGINAL_PATH_HEADER } from './sigv4.js';
|
|
7
|
-
export { createTwinFetchFromHandler, TWIN_PREFIX_HEADER, TWIN_REQUEST_SCOPES, TWIN_SITES_HEADER, twinPublicBase, twinSiteUrl, withRequestScopes } from './twin-fetch.js';
|
|
7
|
+
export { createTwinFetchFromHandler, TWIN_PREFIX_HEADER, TWIN_REQUEST_SCOPES, TWIN_SITES_HEADER, siteLabel, siteUrlOf, twinPublicBase, twinSiteUrl, 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 } from './derived.js';
|
|
10
10
|
export { currentTraceparent, deliveryTraceHeaders, newTraceparent, parseTraceparent, runWithRequestTrace, runWithTraceparent, TRACEPARENT_HEADER, traceparentForDelivery, validTraceparent } from './trace-context.js';
|
package/dist/src/runtime.js
CHANGED
|
@@ -12,7 +12,7 @@ export { parseScenarioDocument, scenarioFaultResult, ScenarioEngine, ScenarioErr
|
|
|
12
12
|
export { WORLD_CLOCK_ENV, worldNow } from "./world-clock.js";
|
|
13
13
|
export { WORLD_ENV_NAMES_ENV, worldEnvValue } from "./world-env.js";
|
|
14
14
|
export { ORIGINAL_PATH_HEADER } from "./sigv4.js";
|
|
15
|
-
export { createTwinFetchFromHandler, TWIN_PREFIX_HEADER, TWIN_REQUEST_SCOPES, TWIN_SITES_HEADER, twinPublicBase, twinSiteUrl, withRequestScopes } from "./twin-fetch.js";
|
|
15
|
+
export { createTwinFetchFromHandler, TWIN_PREFIX_HEADER, TWIN_REQUEST_SCOPES, TWIN_SITES_HEADER, siteLabel, siteUrlOf, twinPublicBase, twinSiteUrl, 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 } from "./derived.js";
|
|
18
18
|
export { currentTraceparent, deliveryTraceHeaders, newTraceparent, parseTraceparent, runWithRequestTrace, runWithTraceparent, TRACEPARENT_HEADER, traceparentForDelivery, validTraceparent } from "./trace-context.js";
|