@volter/world-core 3.0.6 → 3.0.8
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/generated/pack-facts.json +15 -5
- package/dist/src/derived.d.ts +7 -0
- package/dist/src/index.d.ts +1 -0
- package/dist/src/index.js +2 -0
- package/dist/src/pack-fetch.d.ts +2 -1
- package/dist/src/pack-fetch.js +20 -7
- package/dist/src/runtime.d.ts +1 -1
- package/dist/src/runtime.js +1 -1
- package/dist/src/twin-fetch.d.ts +8 -0
- package/dist/src/twin-fetch.js +12 -0
- package/generated/pack-facts.json +15 -5
- package/package.json +1 -1
- package/src/derived.ts +5 -0
- package/src/index.ts +2 -0
- package/src/pack-fetch.ts +24 -8
- package/src/runtime.ts +1 -1
- package/src/twin-fetch.ts +14 -0
|
@@ -337,10 +337,13 @@
|
|
|
337
337
|
],
|
|
338
338
|
"sdks": [
|
|
339
339
|
"cloudflare",
|
|
340
|
-
"aws4fetch"
|
|
340
|
+
"aws4fetch",
|
|
341
|
+
"react-turnstile",
|
|
342
|
+
"@marsidev/react-turnstile"
|
|
341
343
|
],
|
|
342
344
|
"envStems": [
|
|
343
|
-
"CLOUDFLARE"
|
|
345
|
+
"CLOUDFLARE",
|
|
346
|
+
"TURNSTILE"
|
|
344
347
|
],
|
|
345
348
|
"tools": [
|
|
346
349
|
{
|
|
@@ -355,7 +358,11 @@
|
|
|
355
358
|
},
|
|
356
359
|
{
|
|
357
360
|
"host": "dash.cloudflare.com",
|
|
358
|
-
"pathPattern": "^/(login|
|
|
361
|
+
"pathPattern": "^/($|login|profile/api-tokens|_twin/users/|[0-9a-f]{32}/(r2/api-tokens|workers-and-pages))"
|
|
362
|
+
},
|
|
363
|
+
{
|
|
364
|
+
"host": "challenges.cloudflare.com",
|
|
365
|
+
"pathPattern": "^/turnstile/v0/"
|
|
359
366
|
},
|
|
360
367
|
{
|
|
361
368
|
"suffix": ".r2.cloudflarestorage.com"
|
|
@@ -363,7 +370,7 @@
|
|
|
363
370
|
],
|
|
364
371
|
"hostsClaimed": {
|
|
365
372
|
"door": "/_twin/hosts",
|
|
366
|
-
"note": "R2 custom domains registered to a bucket
|
|
373
|
+
"note": "R2 custom domains registered to a bucket (public read still requires enabled and active ownership/TLS), and Workers Custom Domains, answered with the Worker static assets."
|
|
367
374
|
},
|
|
368
375
|
"archetype": "crud",
|
|
369
376
|
"protocol": {
|
|
@@ -380,8 +387,8 @@
|
|
|
380
387
|
"worker_deployment",
|
|
381
388
|
"worker_secret",
|
|
382
389
|
"zone",
|
|
383
|
-
"dns_record",
|
|
384
390
|
"custom_hostname",
|
|
391
|
+
"turnstile_widget",
|
|
385
392
|
"r2_bucket",
|
|
386
393
|
"r2_object",
|
|
387
394
|
"r2_multipart",
|
|
@@ -401,9 +408,12 @@
|
|
|
401
408
|
},
|
|
402
409
|
"fill": {
|
|
403
410
|
"CLOUDFLARE_API_TOKEN": "api_token",
|
|
411
|
+
"CLOUDFLARE_API_KEY": "api_token",
|
|
404
412
|
"CLOUDFLARE_ACCOUNT_ID": "account_id",
|
|
405
413
|
"R2_ACCESS_KEY_ID": "r2_access_key_id",
|
|
406
414
|
"R2_SECRET_ACCESS_KEY": "r2_secret_access_key",
|
|
415
|
+
"CLOUDFLARE_ACCESS_KEY": "r2_access_key_id",
|
|
416
|
+
"CLOUDFLARE_SECRET_ACCESS_KEY": "r2_secret_access_key",
|
|
407
417
|
"R2_ENDPOINT": "r2_endpoint"
|
|
408
418
|
}
|
|
409
419
|
},
|
package/dist/src/derived.d.ts
CHANGED
|
@@ -111,6 +111,13 @@ export type DerivedFetch = ((request: Request) => Promise<Response>) & {
|
|
|
111
111
|
owners(): Record<string, DerivedOwner>;
|
|
112
112
|
/** The vendor's sockets (sockets.ts), for the serve seam to upgrade to, when its manifest declares any. */
|
|
113
113
|
upgrade?: WebSocketUpgrade;
|
|
114
|
+
/** The workspace screens it draws, its lanes' too (`GET /twin`'s `screens`). */
|
|
115
|
+
workspaces?(): Workspace[];
|
|
116
|
+
};
|
|
117
|
+
/** A workspace screen a pack draws, as `GET /twin` lists it: its id and path (Viewing a World). */
|
|
118
|
+
export type Workspace = {
|
|
119
|
+
id: string;
|
|
120
|
+
path: string;
|
|
114
121
|
};
|
|
115
122
|
export declare function createDerivedFetch(options: DerivedFetchOptions): DerivedFetch;
|
|
116
123
|
export {};
|
package/dist/src/index.d.ts
CHANGED
|
@@ -36,6 +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
40
|
export { statefulTwinManifest, twinManifest } from './scenario.js';
|
|
40
41
|
export { assetContentType, packAsset } from './pack-assets.js';
|
|
41
42
|
export { bytesResponse, contentTypeOf, fileResponse } from './file-response.js';
|
package/dist/src/index.js
CHANGED
|
@@ -28,6 +28,8 @@ export { VendorUnreachableError } from "./vendor-call.js";
|
|
|
28
28
|
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
|
+
// where a page links a hostname its twin answers, inside the World that shows it
|
|
32
|
+
export { twinSiteUrl } from "./twin-fetch.js";
|
|
31
33
|
export { statefulTwinManifest, twinManifest } from "./scenario.js";
|
|
32
34
|
export { assetContentType, packAsset } from "./pack-assets.js";
|
|
33
35
|
export { bytesResponse, contentTypeOf, fileResponse } from "./file-response.js";
|
package/dist/src/pack-fetch.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type DerivedManifest, type Handler, type HandlerContext, type VendorManifest } from './derived-core.js';
|
|
2
|
-
import { type DerivedFetch, type DerivedOwner, type DerivedSurface } from './derived.js';
|
|
2
|
+
import { type DerivedFetch, type DerivedOwner, type DerivedSurface, type Workspace } from './derived.js';
|
|
3
3
|
import { type PackScenarioAdapter } from './scenario.js';
|
|
4
4
|
import { type SocketEngine } from './sockets.js';
|
|
5
5
|
import { type GraphqlPart } from './graphql-wire.js';
|
|
@@ -74,4 +74,5 @@ export type PackFetchOptions = {
|
|
|
74
74
|
export declare function createPackFetch(parts: PackParts, options?: PackFetchOptions): DerivedFetch;
|
|
75
75
|
export declare function createVendorFetch(manifest: VendorManifest, lanes: Record<string, LanePart>): ((request: Request) => Promise<Response>) & {
|
|
76
76
|
owners: () => Record<string, DerivedOwner>;
|
|
77
|
+
workspaces: () => Workspace[];
|
|
77
78
|
};
|
package/dist/src/pack-fetch.js
CHANGED
|
@@ -208,6 +208,9 @@ export function createPackFetch(parts, options = {}) {
|
|
|
208
208
|
const hosts = [s.host, ...(s.hosts ?? [])].map(hostMatcher);
|
|
209
209
|
return { decl: s, host: (h) => hosts.map((match) => match(h)).find(Boolean), path: pathMatcher(s.path, true), answer: parts.screens[s.id] };
|
|
210
210
|
});
|
|
211
|
+
// the workspaces a person can be shown (Viewing a World): each built workspace screen's id and path, the pack's own
|
|
212
|
+
// and its lanes'
|
|
213
|
+
const workspaces = () => [...(m.screens ?? []).filter((s) => s.kind === 'workspace' && s.status === 'done' && parts.screens?.[s.id]).map((s) => ({ id: s.id, path: s.path })), ...lanesWorkspaces(parts.lanes ?? {})];
|
|
211
214
|
// a context the kernel opens for what no surface operation names: a door, a screen, the clock
|
|
212
215
|
const open = (request, id, method, path, params) => contextFor(m, { request, operation: { id, method, path, class: method === 'GET' ? 'retrieve' : 'action' }, params }, scope);
|
|
213
216
|
const refusedReadOnly = () => vendorError(m, m.readOnly);
|
|
@@ -265,8 +268,7 @@ export function createPackFetch(parts, options = {}) {
|
|
|
265
268
|
if (request.method === 'GET' && path === '/twin' && m.discovery) {
|
|
266
269
|
const d = m.discovery;
|
|
267
270
|
// the workspaces a person can be shown (Viewing a World): each built workspace screen's id and path
|
|
268
|
-
const
|
|
269
|
-
const shown = workspaces.length ? { screens: workspaces } : {};
|
|
271
|
+
const shown = workspaces().length ? { screens: workspaces() } : {};
|
|
270
272
|
// a model vendor's: its state, and what its scenario scripts, with the scenario's live counts
|
|
271
273
|
if (parts.scenario && d.behavior) {
|
|
272
274
|
return Response.json({ ...shown, ...twinManifest({
|
|
@@ -324,8 +326,14 @@ export function createPackFetch(parts, options = {}) {
|
|
|
324
326
|
// reached at a World's place for the twin (`x-forwarded-prefix`, served-world's wire), the host is the World's: a
|
|
325
327
|
// workspace is shown there by its path alone, its root too (the console frames it so)
|
|
326
328
|
const placed = request.headers.has(TWIN_PREFIX_HEADER);
|
|
329
|
+
// a person's browser opening a page there (a navigation, a form, the console's frame) is asking for a screen: one
|
|
330
|
+
// whose path names it is taken before an API path of the same shape (a dashboard's /<account>/workers-and-pages
|
|
331
|
+
// over R2's /{Bucket}/{Key}), the root workspace at the root alone
|
|
332
|
+
const page = placed && ['document', 'iframe'].includes(request.headers.get('sec-fetch-dest') ?? '');
|
|
333
|
+
const rooted = (x) => x.s.decl.path.replace(/\/+$/, '') === '';
|
|
327
334
|
const byPath = hits.find((x) => x.onHost && exact(x.s))
|
|
328
|
-
?? (
|
|
335
|
+
?? (page ? hits.find((x) => !rooted(x)) ?? (path === '/' ? hits.find((x) => x.s.decl.kind === 'workspace') : undefined) : undefined)
|
|
336
|
+
?? (apiPath ? undefined : hits.find((x) => x.onHost) ?? hits.find((x) => !rooted(x))
|
|
329
337
|
?? (placed ? hits.find((x) => x.s.decl.kind === 'workspace') : undefined));
|
|
330
338
|
if (byPath) {
|
|
331
339
|
if (readOnly && request.method !== 'GET' && request.method !== 'HEAD')
|
|
@@ -388,7 +396,7 @@ export function createPackFetch(parts, options = {}) {
|
|
|
388
396
|
async (asked) => engineOf()?.serve(asked)) : undefined;
|
|
389
397
|
// what serves each operation: the pack's own, and each lane's
|
|
390
398
|
const owners = () => ({ ...api.owners(), ...lanesOwners(parts.lanes ?? {}) });
|
|
391
|
-
return derivedRequestScopes(m, Object.assign(served, { owners, serves, ...(upgrade ? { upgrade } : {}) }));
|
|
399
|
+
return derivedRequestScopes(m, Object.assign(served, { owners, serves, workspaces, ...(upgrade ? { upgrade } : {}) }));
|
|
392
400
|
}
|
|
393
401
|
/** The vendor's gateway over its lanes: the lane a request goes to (a door to the lane that declares it, then the first
|
|
394
402
|
* route whose conditions all hold, then the default), behind the gateway's CORS; undefined when none takes it. */
|
|
@@ -438,13 +446,18 @@ function laneRouter(vendor, decl, lanes, rootServes = () => false) {
|
|
|
438
446
|
* discovery door, then its lanes as its gateway sends them; a request no lane takes is the vendor's not-found. */
|
|
439
447
|
/** What serves each of a vendor's lanes' operations (a handler, the core, or the gap), as one map. */
|
|
440
448
|
const lanesOwners = (lanes) => Object.assign({}, ...Object.values(lanes).map((l) => l.fetch.owners?.() ?? {}));
|
|
449
|
+
/** The workspaces a vendor's lanes draw, as one list. */
|
|
450
|
+
const lanesWorkspaces = (lanes) => Object.values(lanes).flatMap((l) => l.fetch.workspaces?.() ?? []);
|
|
441
451
|
export function createVendorFetch(manifest, lanes) {
|
|
442
452
|
const lane = laneRouter(manifest.vendor, manifest.lanes, lanes);
|
|
443
453
|
return Object.assign(async (request) => {
|
|
444
454
|
const path = new URL(request.url).pathname.replace(/\/+$/, '') || '/';
|
|
445
|
-
if (request.method === 'GET' && path === '/twin' && manifest.discovery)
|
|
446
|
-
|
|
455
|
+
if (request.method === 'GET' && path === '/twin' && manifest.discovery) {
|
|
456
|
+
// the vendor's screens are its lanes' (a dashboard drawn by one lane is the vendor's)
|
|
457
|
+
const screens = lanesWorkspaces(lanes);
|
|
458
|
+
return Response.json({ ...statefulTwinManifest({ vendor: manifest.vendor, ...manifest.discovery }), ...(screens.length ? { screens } : {}) });
|
|
459
|
+
}
|
|
447
460
|
const to = lane(request);
|
|
448
461
|
return to ? to(request) : Response.json({ error: `${manifest.vendor}: no lane serves ${request.method} ${path}` }, { status: 404 });
|
|
449
|
-
}, { owners: () => lanesOwners(lanes) });
|
|
462
|
+
}, { owners: () => lanesOwners(lanes), workspaces: () => lanesWorkspaces(lanes) });
|
|
450
463
|
}
|
package/dist/src/runtime.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ export type { PackTransport, PullPosture, PullTrigger, PullVendor, RoundTripWrit
|
|
|
3
3
|
export { parseScenarioDocument, scenarioFaultResult, ScenarioEngine, ScenarioError, statefulTwinManifest, twinManifest } from './scenario.js';
|
|
4
4
|
export { WORLD_CLOCK_ENV, worldNow } from './world-clock.js';
|
|
5
5
|
export { WORLD_ENV_NAMES_ENV, worldEnvValue } from './world-env.js';
|
|
6
|
-
export { createTwinFetchFromHandler, TWIN_PREFIX_HEADER, TWIN_REQUEST_SCOPES, twinPublicBase, withRequestScopes } from './twin-fetch.js';
|
|
6
|
+
export { createTwinFetchFromHandler, TWIN_PREFIX_HEADER, TWIN_REQUEST_SCOPES, TWIN_SITES_HEADER, twinPublicBase, twinSiteUrl, withRequestScopes } from './twin-fetch.js';
|
|
7
7
|
export { isReadOnlyRequest, READ_ONLY_REQUEST_HEADER, ReadOnlyRequestError, runAsReadOnlyRequest, runAsVendorMove, writesRefused } from './request-scope.js';
|
|
8
8
|
export { compileSurface, createDerivedFetch, matchOperation } from './derived.js';
|
|
9
9
|
export { currentTraceparent, deliveryTraceHeaders, newTraceparent, parseTraceparent, runWithRequestTrace, runWithTraceparent, TRACEPARENT_HEADER, traceparentForDelivery, validTraceparent } from './trace-context.js';
|
package/dist/src/runtime.js
CHANGED
|
@@ -11,7 +11,7 @@ export { clearRegistry, getPack, hasPack, listPacks, packOf, registerPack, pullP
|
|
|
11
11
|
export { parseScenarioDocument, scenarioFaultResult, ScenarioEngine, ScenarioError, statefulTwinManifest, twinManifest } from "./scenario.js";
|
|
12
12
|
export { WORLD_CLOCK_ENV, worldNow } from "./world-clock.js";
|
|
13
13
|
export { WORLD_ENV_NAMES_ENV, worldEnvValue } from "./world-env.js";
|
|
14
|
-
export { createTwinFetchFromHandler, TWIN_PREFIX_HEADER, TWIN_REQUEST_SCOPES, twinPublicBase, withRequestScopes } from "./twin-fetch.js";
|
|
14
|
+
export { createTwinFetchFromHandler, TWIN_PREFIX_HEADER, TWIN_REQUEST_SCOPES, TWIN_SITES_HEADER, twinPublicBase, twinSiteUrl, withRequestScopes } from "./twin-fetch.js";
|
|
15
15
|
export { isReadOnlyRequest, READ_ONLY_REQUEST_HEADER, ReadOnlyRequestError, runAsReadOnlyRequest, runAsVendorMove, writesRefused } from "./request-scope.js";
|
|
16
16
|
export { compileSurface, createDerivedFetch, matchOperation } from "./derived.js";
|
|
17
17
|
export { currentTraceparent, deliveryTraceHeaders, newTraceparent, parseTraceparent, runWithRequestTrace, runWithTraceparent, TRACEPARENT_HEADER, traceparentForDelivery, validTraceparent } from "./trace-context.js";
|
package/dist/src/twin-fetch.d.ts
CHANGED
|
@@ -7,6 +7,14 @@ export declare const TWIN_PREFIX_HEADER = "x-forwarded-prefix";
|
|
|
7
7
|
* the twin under. A pack mints every live URL it hands back (an upload target, a file link, a
|
|
8
8
|
* callback) from this, never from the bare origin, so the URL works behind a served World. */
|
|
9
9
|
export declare function twinPublicBase(request: Request): string;
|
|
10
|
+
/** Where a World shows a hostname its twins answer (a Workers Custom Domain, an R2 public domain): the address a person
|
|
11
|
+
* opens it at, `{host}` standing for the hostname (`http://{host}.<world>--<org>.localhost:<port>`). A World that routes
|
|
12
|
+
* its sites to a browser sends it on every request it forwards to a twin. */
|
|
13
|
+
export declare const TWIN_SITES_HEADER = "x-volter-world-sites";
|
|
14
|
+
/** Where a page links a hostname the twin answers: the World's address for it when the World shows sites, else the
|
|
15
|
+
* hostname itself (reached through the World's proxy, or the vendor). A pack's screen links a site from this, as it
|
|
16
|
+
* mints its own URLs from twinPublicBase. */
|
|
17
|
+
export declare function twinSiteUrl(request: Request, hostname: string): string;
|
|
10
18
|
/** A twin's BYTE-STREAM door: a pack whose clients speak a TCP protocol (SMTP, the MySQL wire) serves
|
|
11
19
|
* it over whatever carries the bytes — a local listener's socket, or a WebSocket to a hosted World.
|
|
12
20
|
* The pack owns framing and protocol; the host owns the transport. `open` greets through the sink;
|
package/dist/src/twin-fetch.js
CHANGED
|
@@ -40,6 +40,18 @@ export function twinPublicBase(request) {
|
|
|
40
40
|
const origin = host && /^[A-Za-z0-9.-]+(:\d+)?$/.test(host) && (proto === 'http' || proto === 'https') ? `${proto}://${host}` : new URL(request.url).origin;
|
|
41
41
|
return `${origin}${prefix.replace(/\/+$/, '')}`;
|
|
42
42
|
}
|
|
43
|
+
/** Where a World shows a hostname its twins answer (a Workers Custom Domain, an R2 public domain): the address a person
|
|
44
|
+
* opens it at, `{host}` standing for the hostname (`http://{host}.<world>--<org>.localhost:<port>`). A World that routes
|
|
45
|
+
* its sites to a browser sends it on every request it forwards to a twin. */
|
|
46
|
+
export const TWIN_SITES_HEADER = 'x-volter-world-sites';
|
|
47
|
+
/** Where a page links a hostname the twin answers: the World's address for it when the World shows sites, else the
|
|
48
|
+
* hostname itself (reached through the World's proxy, or the vendor). A pack's screen links a site from this, as it
|
|
49
|
+
* mints its own URLs from twinPublicBase. */
|
|
50
|
+
export function twinSiteUrl(request, hostname) {
|
|
51
|
+
const at = request.headers.get(TWIN_SITES_HEADER);
|
|
52
|
+
const host = hostname.toLowerCase();
|
|
53
|
+
return at && /^https?:\/\/\{host\}\.[A-Za-z0-9.-]+(:\d+)?$/.test(at) && /^[a-z0-9.-]+$/.test(host) ? at.replace('{host}', host) : `https://${host}`;
|
|
54
|
+
}
|
|
43
55
|
/** The request scopes a pack wrapped in `withRequestScopes` enforces, advertised on its `GET /twin` as
|
|
44
56
|
* `requestScopes`: `read` — a request carrying `x-volter-read-only: 1` (request-scope.ts) has every
|
|
45
57
|
* write it attempts refused at the kernel's write seam, whatever operation it names. A World's doors
|
|
@@ -337,10 +337,13 @@
|
|
|
337
337
|
],
|
|
338
338
|
"sdks": [
|
|
339
339
|
"cloudflare",
|
|
340
|
-
"aws4fetch"
|
|
340
|
+
"aws4fetch",
|
|
341
|
+
"react-turnstile",
|
|
342
|
+
"@marsidev/react-turnstile"
|
|
341
343
|
],
|
|
342
344
|
"envStems": [
|
|
343
|
-
"CLOUDFLARE"
|
|
345
|
+
"CLOUDFLARE",
|
|
346
|
+
"TURNSTILE"
|
|
344
347
|
],
|
|
345
348
|
"tools": [
|
|
346
349
|
{
|
|
@@ -355,7 +358,11 @@
|
|
|
355
358
|
},
|
|
356
359
|
{
|
|
357
360
|
"host": "dash.cloudflare.com",
|
|
358
|
-
"pathPattern": "^/(login|
|
|
361
|
+
"pathPattern": "^/($|login|profile/api-tokens|_twin/users/|[0-9a-f]{32}/(r2/api-tokens|workers-and-pages))"
|
|
362
|
+
},
|
|
363
|
+
{
|
|
364
|
+
"host": "challenges.cloudflare.com",
|
|
365
|
+
"pathPattern": "^/turnstile/v0/"
|
|
359
366
|
},
|
|
360
367
|
{
|
|
361
368
|
"suffix": ".r2.cloudflarestorage.com"
|
|
@@ -363,7 +370,7 @@
|
|
|
363
370
|
],
|
|
364
371
|
"hostsClaimed": {
|
|
365
372
|
"door": "/_twin/hosts",
|
|
366
|
-
"note": "R2 custom domains registered to a bucket
|
|
373
|
+
"note": "R2 custom domains registered to a bucket (public read still requires enabled and active ownership/TLS), and Workers Custom Domains, answered with the Worker static assets."
|
|
367
374
|
},
|
|
368
375
|
"archetype": "crud",
|
|
369
376
|
"protocol": {
|
|
@@ -380,8 +387,8 @@
|
|
|
380
387
|
"worker_deployment",
|
|
381
388
|
"worker_secret",
|
|
382
389
|
"zone",
|
|
383
|
-
"dns_record",
|
|
384
390
|
"custom_hostname",
|
|
391
|
+
"turnstile_widget",
|
|
385
392
|
"r2_bucket",
|
|
386
393
|
"r2_object",
|
|
387
394
|
"r2_multipart",
|
|
@@ -401,9 +408,12 @@
|
|
|
401
408
|
},
|
|
402
409
|
"fill": {
|
|
403
410
|
"CLOUDFLARE_API_TOKEN": "api_token",
|
|
411
|
+
"CLOUDFLARE_API_KEY": "api_token",
|
|
404
412
|
"CLOUDFLARE_ACCOUNT_ID": "account_id",
|
|
405
413
|
"R2_ACCESS_KEY_ID": "r2_access_key_id",
|
|
406
414
|
"R2_SECRET_ACCESS_KEY": "r2_secret_access_key",
|
|
415
|
+
"CLOUDFLARE_ACCESS_KEY": "r2_access_key_id",
|
|
416
|
+
"CLOUDFLARE_SECRET_ACCESS_KEY": "r2_secret_access_key",
|
|
407
417
|
"R2_ENDPOINT": "r2_endpoint"
|
|
408
418
|
}
|
|
409
419
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@volter/world-core",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.8",
|
|
4
4
|
"description": "The kernel of Volter World: one log per twin, branches as pointers, checkpoints, the fold that keeps a twin current, the head that performs a write against the vendor, references, and the git plane. A twin package builds on it; the runtime serves it.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"twin",
|
package/src/derived.ts
CHANGED
|
@@ -196,8 +196,13 @@ export type DerivedFetch = ((request: Request) => Promise<Response>) & {
|
|
|
196
196
|
owners(): Record<string, DerivedOwner>;
|
|
197
197
|
/** The vendor's sockets (sockets.ts), for the serve seam to upgrade to, when its manifest declares any. */
|
|
198
198
|
upgrade?: WebSocketUpgrade;
|
|
199
|
+
/** The workspace screens it draws, its lanes' too (`GET /twin`'s `screens`). */
|
|
200
|
+
workspaces?(): Workspace[];
|
|
199
201
|
};
|
|
200
202
|
|
|
203
|
+
/** A workspace screen a pack draws, as `GET /twin` lists it: its id and path (Viewing a World). */
|
|
204
|
+
export type Workspace = { id: string; path: string };
|
|
205
|
+
|
|
201
206
|
export function createDerivedFetch(options: DerivedFetchOptions): DerivedFetch {
|
|
202
207
|
const handlers = options.handlers ?? {};
|
|
203
208
|
const known = new Set(options.surface.operations.map((o) => o.id));
|
package/src/index.ts
CHANGED
|
@@ -51,6 +51,8 @@ export { isReadOnlyRequest, READ_ONLY_REQUEST_HEADER } from './request-scope.ts'
|
|
|
51
51
|
// ── the serve factory ──────────────────────────────────────────────────────────────────────────
|
|
52
52
|
export { serveHttp, serveStream, type HttpHandler, type HttpServer, type ServeHttpOptions } from './serve-http.ts';
|
|
53
53
|
export type { TwinStream, TwinStreamConnection, TwinStreamSink } from './twin-fetch.ts';
|
|
54
|
+
// where a page links a hostname its twin answers, inside the World that shows it
|
|
55
|
+
export { twinSiteUrl } from './twin-fetch.ts';
|
|
54
56
|
export { statefulTwinManifest, twinManifest } from './scenario.ts';
|
|
55
57
|
export { assetContentType, packAsset } from './pack-assets.ts';
|
|
56
58
|
export { bytesResponse, contentTypeOf, fileResponse } from './file-response.ts';
|
package/src/pack-fetch.ts
CHANGED
|
@@ -10,7 +10,7 @@ import { TWIN_PREFIX_HEADER } from './twin-fetch.ts';
|
|
|
10
10
|
import { PREFIX_PARAMS_HEADER } from './derived.ts';
|
|
11
11
|
import { withHeaders } from './with-headers.ts';
|
|
12
12
|
import { authRefusal, bindSemantics, contextFor, LANE_HEADER, SCENARIO_DECISION_HEADER, coreFor, crossCutting, derivedRequestScopes, vendorError, type CoreScope, type DerivedManifest, type ErrorSpec, type Handler, type HandlerContext, type LanesDecl, type VendorManifest } from './derived-core.ts';
|
|
13
|
-
import { compileSurface, createDerivedFetch, matchOperation, type DerivedFetch, type DerivedOperation, type DerivedOwner, type DerivedSurface } from './derived.ts';
|
|
13
|
+
import { compileSurface, createDerivedFetch, matchOperation, type DerivedFetch, type DerivedOperation, type DerivedOwner, type DerivedSurface, type Workspace } from './derived.ts';
|
|
14
14
|
import { isReadOnlyRequest, READ_ONLY_REQUEST_HEADER, runAsVendorMove } from './request-scope.ts';
|
|
15
15
|
import { parseScenarioDocument, ScenarioEngine, ScenarioError, statefulTwinManifest, twinManifest, type PackScenarioAdapter } from './scenario.ts';
|
|
16
16
|
import { getActiveWorldStore } from './world-store.ts';
|
|
@@ -242,6 +242,9 @@ export function createPackFetch(parts: PackParts, options: PackFetchOptions = {}
|
|
|
242
242
|
const hosts = [s.host, ...(s.hosts ?? [])].map(hostMatcher);
|
|
243
243
|
return { decl: s, host: (h: string) => hosts.map((match) => match(h)).find(Boolean), path: pathMatcher(s.path, true), answer: parts.screens![s.id]! };
|
|
244
244
|
});
|
|
245
|
+
// the workspaces a person can be shown (Viewing a World): each built workspace screen's id and path, the pack's own
|
|
246
|
+
// and its lanes'
|
|
247
|
+
const workspaces = (): Workspace[] => [...(m.screens ?? []).filter((s) => s.kind === 'workspace' && s.status === 'done' && parts.screens?.[s.id]).map((s) => ({ id: s.id, path: s.path })), ...lanesWorkspaces(parts.lanes ?? {})];
|
|
245
248
|
// a context the kernel opens for what no surface operation names: a door, a screen, the clock
|
|
246
249
|
const open = (request: Request, id: string, method: string, path: string, params: Record<string, string>): Promise<HandlerContext> =>
|
|
247
250
|
contextFor(m, { request, operation: { id, method, path, class: method === 'GET' ? 'retrieve' : 'action' } as DerivedOperation, params }, scope);
|
|
@@ -295,8 +298,7 @@ export function createPackFetch(parts: PackParts, options: PackFetchOptions = {}
|
|
|
295
298
|
if (request.method === 'GET' && path === '/twin' && m.discovery) {
|
|
296
299
|
const d = m.discovery;
|
|
297
300
|
// the workspaces a person can be shown (Viewing a World): each built workspace screen's id and path
|
|
298
|
-
const
|
|
299
|
-
const shown = workspaces.length ? { screens: workspaces } : {};
|
|
301
|
+
const shown = workspaces().length ? { screens: workspaces() } : {};
|
|
300
302
|
// a model vendor's: its state, and what its scenario scripts, with the scenario's live counts
|
|
301
303
|
if (parts.scenario && d.behavior) {
|
|
302
304
|
return Response.json({ ...shown, ...twinManifest({
|
|
@@ -342,8 +344,14 @@ export function createPackFetch(parts: PackParts, options: PackFetchOptions = {}
|
|
|
342
344
|
// reached at a World's place for the twin (`x-forwarded-prefix`, served-world's wire), the host is the World's: a
|
|
343
345
|
// workspace is shown there by its path alone, its root too (the console frames it so)
|
|
344
346
|
const placed = request.headers.has(TWIN_PREFIX_HEADER);
|
|
347
|
+
// a person's browser opening a page there (a navigation, a form, the console's frame) is asking for a screen: one
|
|
348
|
+
// whose path names it is taken before an API path of the same shape (a dashboard's /<account>/workers-and-pages
|
|
349
|
+
// over R2's /{Bucket}/{Key}), the root workspace at the root alone
|
|
350
|
+
const page = placed && ['document', 'iframe'].includes(request.headers.get('sec-fetch-dest') ?? '');
|
|
351
|
+
const rooted = (x: (typeof hits)[number]): boolean => x.s.decl.path.replace(/\/+$/, '') === '';
|
|
345
352
|
const byPath = hits.find((x) => x.onHost && exact(x.s))
|
|
346
|
-
?? (
|
|
353
|
+
?? (page ? hits.find((x) => !rooted(x)) ?? (path === '/' ? hits.find((x) => x.s.decl.kind === 'workspace') : undefined) : undefined)
|
|
354
|
+
?? (apiPath ? undefined : hits.find((x) => x.onHost) ?? hits.find((x) => !rooted(x))
|
|
347
355
|
?? (placed ? hits.find((x) => x.s.decl.kind === 'workspace') : undefined));
|
|
348
356
|
if (byPath) {
|
|
349
357
|
if (readOnly && request.method !== 'GET' && request.method !== 'HEAD') return refusedReadOnly();
|
|
@@ -395,7 +403,7 @@ export function createPackFetch(parts: PackParts, options: PackFetchOptions = {}
|
|
|
395
403
|
async (asked) => engineOf()?.serve(asked)) : undefined;
|
|
396
404
|
// what serves each operation: the pack's own, and each lane's
|
|
397
405
|
const owners = (): Record<string, DerivedOwner> => ({ ...api.owners(), ...lanesOwners(parts.lanes ?? {}) });
|
|
398
|
-
return derivedRequestScopes(m, Object.assign(served, { owners, serves, ...(upgrade ? { upgrade } : {}) }));
|
|
406
|
+
return derivedRequestScopes(m, Object.assign(served, { owners, serves, workspaces, ...(upgrade ? { upgrade } : {}) }));
|
|
399
407
|
}
|
|
400
408
|
|
|
401
409
|
/** The vendor's gateway over its lanes: the lane a request goes to (a door to the lane that declares it, then the first
|
|
@@ -448,12 +456,20 @@ function laneRouter(vendor: string, decl: LanesDecl, lanes: Record<string, LaneP
|
|
|
448
456
|
const lanesOwners = (lanes: Record<string, LanePart>): Record<string, DerivedOwner> =>
|
|
449
457
|
Object.assign({}, ...Object.values(lanes).map((l) => (l.fetch as { owners?: () => Record<string, DerivedOwner> }).owners?.() ?? {}));
|
|
450
458
|
|
|
451
|
-
|
|
459
|
+
/** The workspaces a vendor's lanes draw, as one list. */
|
|
460
|
+
const lanesWorkspaces = (lanes: Record<string, LanePart>): Workspace[] =>
|
|
461
|
+
Object.values(lanes).flatMap((l) => (l.fetch as { workspaces?: () => Workspace[] }).workspaces?.() ?? []);
|
|
462
|
+
|
|
463
|
+
export function createVendorFetch(manifest: VendorManifest, lanes: Record<string, LanePart>): ((request: Request) => Promise<Response>) & { owners: () => Record<string, DerivedOwner>; workspaces: () => Workspace[] } {
|
|
452
464
|
const lane = laneRouter(manifest.vendor, manifest.lanes, lanes);
|
|
453
465
|
return Object.assign(async (request: Request): Promise<Response> => {
|
|
454
466
|
const path = new URL(request.url).pathname.replace(/\/+$/, '') || '/';
|
|
455
|
-
if (request.method === 'GET' && path === '/twin' && manifest.discovery)
|
|
467
|
+
if (request.method === 'GET' && path === '/twin' && manifest.discovery) {
|
|
468
|
+
// the vendor's screens are its lanes' (a dashboard drawn by one lane is the vendor's)
|
|
469
|
+
const screens = lanesWorkspaces(lanes);
|
|
470
|
+
return Response.json({ ...statefulTwinManifest({ vendor: manifest.vendor, ...manifest.discovery }), ...(screens.length ? { screens } : {}) });
|
|
471
|
+
}
|
|
456
472
|
const to = lane(request);
|
|
457
473
|
return to ? to(request) : Response.json({ error: `${manifest.vendor}: no lane serves ${request.method} ${path}` }, { status: 404 });
|
|
458
|
-
}, { owners: () => lanesOwners(lanes) });
|
|
474
|
+
}, { owners: () => lanesOwners(lanes), workspaces: () => lanesWorkspaces(lanes) });
|
|
459
475
|
}
|
package/src/runtime.ts
CHANGED
|
@@ -12,7 +12,7 @@ export type { PackTransport, PullPosture, PullTrigger, PullVendor, RoundTripWrit
|
|
|
12
12
|
export { parseScenarioDocument, scenarioFaultResult, ScenarioEngine, ScenarioError, statefulTwinManifest, twinManifest } from './scenario.ts';
|
|
13
13
|
export { WORLD_CLOCK_ENV, worldNow } from './world-clock.ts';
|
|
14
14
|
export { WORLD_ENV_NAMES_ENV, worldEnvValue } from './world-env.ts';
|
|
15
|
-
export { createTwinFetchFromHandler, TWIN_PREFIX_HEADER, TWIN_REQUEST_SCOPES, twinPublicBase, withRequestScopes } from './twin-fetch.ts';
|
|
15
|
+
export { createTwinFetchFromHandler, TWIN_PREFIX_HEADER, TWIN_REQUEST_SCOPES, TWIN_SITES_HEADER, twinPublicBase, twinSiteUrl, withRequestScopes } from './twin-fetch.ts';
|
|
16
16
|
export { isReadOnlyRequest, READ_ONLY_REQUEST_HEADER, ReadOnlyRequestError, runAsReadOnlyRequest, runAsVendorMove, writesRefused } from './request-scope.ts';
|
|
17
17
|
export { compileSurface, createDerivedFetch, matchOperation } from './derived.ts';
|
|
18
18
|
export { currentTraceparent, deliveryTraceHeaders, newTraceparent, parseTraceparent, runWithRequestTrace, runWithTraceparent, TRACEPARENT_HEADER, traceparentForDelivery, validTraceparent } from './trace-context.ts';
|
package/src/twin-fetch.ts
CHANGED
|
@@ -43,6 +43,20 @@ export function twinPublicBase(request: Request): string {
|
|
|
43
43
|
return `${origin}${prefix.replace(/\/+$/, '')}`;
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
+
/** Where a World shows a hostname its twins answer (a Workers Custom Domain, an R2 public domain): the address a person
|
|
47
|
+
* opens it at, `{host}` standing for the hostname (`http://{host}.<world>--<org>.localhost:<port>`). A World that routes
|
|
48
|
+
* its sites to a browser sends it on every request it forwards to a twin. */
|
|
49
|
+
export const TWIN_SITES_HEADER = 'x-volter-world-sites';
|
|
50
|
+
|
|
51
|
+
/** Where a page links a hostname the twin answers: the World's address for it when the World shows sites, else the
|
|
52
|
+
* hostname itself (reached through the World's proxy, or the vendor). A pack's screen links a site from this, as it
|
|
53
|
+
* mints its own URLs from twinPublicBase. */
|
|
54
|
+
export function twinSiteUrl(request: Request, hostname: string): string {
|
|
55
|
+
const at = request.headers.get(TWIN_SITES_HEADER);
|
|
56
|
+
const host = hostname.toLowerCase();
|
|
57
|
+
return at && /^https?:\/\/\{host\}\.[A-Za-z0-9.-]+(:\d+)?$/.test(at) && /^[a-z0-9.-]+$/.test(host) ? at.replace('{host}', host) : `https://${host}`;
|
|
58
|
+
}
|
|
59
|
+
|
|
46
60
|
/** A twin's BYTE-STREAM door: a pack whose clients speak a TCP protocol (SMTP, the MySQL wire) serves
|
|
47
61
|
* it over whatever carries the bytes — a local listener's socket, or a WebSocket to a hosted World.
|
|
48
62
|
* The pack owns framing and protocol; the host owns the transport. `open` greets through the sink;
|