@volter/world-core 3.0.7 → 3.0.9
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 +23 -6
- 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 +25 -8
- 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 +23 -6
- package/package.json +1 -1
- package/src/derived.ts +5 -0
- package/src/index.ts +2 -0
- package/src/pack-fetch.ts +29 -9
- 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
|
},
|
|
@@ -969,9 +979,16 @@
|
|
|
969
979
|
},
|
|
970
980
|
{
|
|
971
981
|
"host": "github.com",
|
|
972
|
-
"pathPattern": "^/login|^/session|^/settings/|^/organizations/|^/apps/|^/[^/]+/[^/]+\\.git/"
|
|
982
|
+
"pathPattern": "^/login|^/session|^/settings/|^/organizations/|^/apps/|^/[^/]+/[^/]+\\.git/|^/[^/]+/[^/]+/settings/pages"
|
|
983
|
+
},
|
|
984
|
+
{
|
|
985
|
+
"suffix": ".github.io"
|
|
973
986
|
}
|
|
974
987
|
],
|
|
988
|
+
"hostsClaimed": {
|
|
989
|
+
"door": "/_twin/hosts",
|
|
990
|
+
"note": "GitHub Pages sites: each owner's github.io and each custom domain, answered with the publishing branch's files."
|
|
991
|
+
},
|
|
975
992
|
"archetype": "crud",
|
|
976
993
|
"protocol": {
|
|
977
994
|
"declared": "3",
|
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,9 +326,19 @@ 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(/\/+$/, '') === '';
|
|
334
|
+
// a request the World routed from a vendor's own hostname (its proxy, a site, a call inside the World) is answered by
|
|
335
|
+
// that host's screens alone: a page of github.com never answers at <owner>.github.io, nor Cloudflare's sign-in at a
|
|
336
|
+
// Worker's Custom Domain; a screen by its path alone is for the twin reached at its own address
|
|
337
|
+
const addressed = !placed && request.headers.has('x-volter-twin-original-host');
|
|
327
338
|
const byPath = hits.find((x) => x.onHost && exact(x.s))
|
|
328
|
-
?? (
|
|
329
|
-
|
|
339
|
+
?? (page ? hits.find((x) => !rooted(x)) ?? (path === '/' ? hits.find((x) => x.s.decl.kind === 'workspace') : undefined) : undefined)
|
|
340
|
+
?? (apiPath ? undefined : hits.find((x) => x.onHost) ?? (addressed ? undefined : hits.find((x) => !rooted(x))
|
|
341
|
+
?? (placed ? hits.find((x) => x.s.decl.kind === 'workspace') : undefined)));
|
|
330
342
|
if (byPath) {
|
|
331
343
|
if (readOnly && request.method !== 'GET' && request.method !== 'HEAD')
|
|
332
344
|
return refusedReadOnly();
|
|
@@ -388,7 +400,7 @@ export function createPackFetch(parts, options = {}) {
|
|
|
388
400
|
async (asked) => engineOf()?.serve(asked)) : undefined;
|
|
389
401
|
// what serves each operation: the pack's own, and each lane's
|
|
390
402
|
const owners = () => ({ ...api.owners(), ...lanesOwners(parts.lanes ?? {}) });
|
|
391
|
-
return derivedRequestScopes(m, Object.assign(served, { owners, serves, ...(upgrade ? { upgrade } : {}) }));
|
|
403
|
+
return derivedRequestScopes(m, Object.assign(served, { owners, serves, workspaces, ...(upgrade ? { upgrade } : {}) }));
|
|
392
404
|
}
|
|
393
405
|
/** 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
406
|
* route whose conditions all hold, then the default), behind the gateway's CORS; undefined when none takes it. */
|
|
@@ -438,13 +450,18 @@ function laneRouter(vendor, decl, lanes, rootServes = () => false) {
|
|
|
438
450
|
* discovery door, then its lanes as its gateway sends them; a request no lane takes is the vendor's not-found. */
|
|
439
451
|
/** What serves each of a vendor's lanes' operations (a handler, the core, or the gap), as one map. */
|
|
440
452
|
const lanesOwners = (lanes) => Object.assign({}, ...Object.values(lanes).map((l) => l.fetch.owners?.() ?? {}));
|
|
453
|
+
/** The workspaces a vendor's lanes draw, as one list. */
|
|
454
|
+
const lanesWorkspaces = (lanes) => Object.values(lanes).flatMap((l) => l.fetch.workspaces?.() ?? []);
|
|
441
455
|
export function createVendorFetch(manifest, lanes) {
|
|
442
456
|
const lane = laneRouter(manifest.vendor, manifest.lanes, lanes);
|
|
443
457
|
return Object.assign(async (request) => {
|
|
444
458
|
const path = new URL(request.url).pathname.replace(/\/+$/, '') || '/';
|
|
445
|
-
if (request.method === 'GET' && path === '/twin' && manifest.discovery)
|
|
446
|
-
|
|
459
|
+
if (request.method === 'GET' && path === '/twin' && manifest.discovery) {
|
|
460
|
+
// the vendor's screens are its lanes' (a dashboard drawn by one lane is the vendor's)
|
|
461
|
+
const screens = lanesWorkspaces(lanes);
|
|
462
|
+
return Response.json({ ...statefulTwinManifest({ vendor: manifest.vendor, ...manifest.discovery }), ...(screens.length ? { screens } : {}) });
|
|
463
|
+
}
|
|
447
464
|
const to = lane(request);
|
|
448
465
|
return to ? to(request) : Response.json({ error: `${manifest.vendor}: no lane serves ${request.method} ${path}` }, { status: 404 });
|
|
449
|
-
}, { owners: () => lanesOwners(lanes) });
|
|
466
|
+
}, { owners: () => lanesOwners(lanes), workspaces: () => lanesWorkspaces(lanes) });
|
|
450
467
|
}
|
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
|
},
|
|
@@ -969,9 +979,16 @@
|
|
|
969
979
|
},
|
|
970
980
|
{
|
|
971
981
|
"host": "github.com",
|
|
972
|
-
"pathPattern": "^/login|^/session|^/settings/|^/organizations/|^/apps/|^/[^/]+/[^/]+\\.git/"
|
|
982
|
+
"pathPattern": "^/login|^/session|^/settings/|^/organizations/|^/apps/|^/[^/]+/[^/]+\\.git/|^/[^/]+/[^/]+/settings/pages"
|
|
983
|
+
},
|
|
984
|
+
{
|
|
985
|
+
"suffix": ".github.io"
|
|
973
986
|
}
|
|
974
987
|
],
|
|
988
|
+
"hostsClaimed": {
|
|
989
|
+
"door": "/_twin/hosts",
|
|
990
|
+
"note": "GitHub Pages sites: each owner's github.io and each custom domain, answered with the publishing branch's files."
|
|
991
|
+
},
|
|
975
992
|
"archetype": "crud",
|
|
976
993
|
"protocol": {
|
|
977
994
|
"declared": "3",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@volter/world-core",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.9",
|
|
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,9 +344,19 @@ 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(/\/+$/, '') === '';
|
|
352
|
+
// a request the World routed from a vendor's own hostname (its proxy, a site, a call inside the World) is answered by
|
|
353
|
+
// that host's screens alone: a page of github.com never answers at <owner>.github.io, nor Cloudflare's sign-in at a
|
|
354
|
+
// Worker's Custom Domain; a screen by its path alone is for the twin reached at its own address
|
|
355
|
+
const addressed = !placed && request.headers.has('x-volter-twin-original-host');
|
|
345
356
|
const byPath = hits.find((x) => x.onHost && exact(x.s))
|
|
346
|
-
?? (
|
|
347
|
-
|
|
357
|
+
?? (page ? hits.find((x) => !rooted(x)) ?? (path === '/' ? hits.find((x) => x.s.decl.kind === 'workspace') : undefined) : undefined)
|
|
358
|
+
?? (apiPath ? undefined : hits.find((x) => x.onHost) ?? (addressed ? undefined : hits.find((x) => !rooted(x))
|
|
359
|
+
?? (placed ? hits.find((x) => x.s.decl.kind === 'workspace') : undefined)));
|
|
348
360
|
if (byPath) {
|
|
349
361
|
if (readOnly && request.method !== 'GET' && request.method !== 'HEAD') return refusedReadOnly();
|
|
350
362
|
return byPath.s.answer(await open(request, byPath.s.decl.id, request.method, byPath.s.decl.path, { ...(byPath.onHost?.params ?? {}), ...byPath.hit!.params }));
|
|
@@ -395,7 +407,7 @@ export function createPackFetch(parts: PackParts, options: PackFetchOptions = {}
|
|
|
395
407
|
async (asked) => engineOf()?.serve(asked)) : undefined;
|
|
396
408
|
// what serves each operation: the pack's own, and each lane's
|
|
397
409
|
const owners = (): Record<string, DerivedOwner> => ({ ...api.owners(), ...lanesOwners(parts.lanes ?? {}) });
|
|
398
|
-
return derivedRequestScopes(m, Object.assign(served, { owners, serves, ...(upgrade ? { upgrade } : {}) }));
|
|
410
|
+
return derivedRequestScopes(m, Object.assign(served, { owners, serves, workspaces, ...(upgrade ? { upgrade } : {}) }));
|
|
399
411
|
}
|
|
400
412
|
|
|
401
413
|
/** 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 +460,20 @@ function laneRouter(vendor: string, decl: LanesDecl, lanes: Record<string, LaneP
|
|
|
448
460
|
const lanesOwners = (lanes: Record<string, LanePart>): Record<string, DerivedOwner> =>
|
|
449
461
|
Object.assign({}, ...Object.values(lanes).map((l) => (l.fetch as { owners?: () => Record<string, DerivedOwner> }).owners?.() ?? {}));
|
|
450
462
|
|
|
451
|
-
|
|
463
|
+
/** The workspaces a vendor's lanes draw, as one list. */
|
|
464
|
+
const lanesWorkspaces = (lanes: Record<string, LanePart>): Workspace[] =>
|
|
465
|
+
Object.values(lanes).flatMap((l) => (l.fetch as { workspaces?: () => Workspace[] }).workspaces?.() ?? []);
|
|
466
|
+
|
|
467
|
+
export function createVendorFetch(manifest: VendorManifest, lanes: Record<string, LanePart>): ((request: Request) => Promise<Response>) & { owners: () => Record<string, DerivedOwner>; workspaces: () => Workspace[] } {
|
|
452
468
|
const lane = laneRouter(manifest.vendor, manifest.lanes, lanes);
|
|
453
469
|
return Object.assign(async (request: Request): Promise<Response> => {
|
|
454
470
|
const path = new URL(request.url).pathname.replace(/\/+$/, '') || '/';
|
|
455
|
-
if (request.method === 'GET' && path === '/twin' && manifest.discovery)
|
|
471
|
+
if (request.method === 'GET' && path === '/twin' && manifest.discovery) {
|
|
472
|
+
// the vendor's screens are its lanes' (a dashboard drawn by one lane is the vendor's)
|
|
473
|
+
const screens = lanesWorkspaces(lanes);
|
|
474
|
+
return Response.json({ ...statefulTwinManifest({ vendor: manifest.vendor, ...manifest.discovery }), ...(screens.length ? { screens } : {}) });
|
|
475
|
+
}
|
|
456
476
|
const to = lane(request);
|
|
457
477
|
return to ? to(request) : Response.json({ error: `${manifest.vendor}: no lane serves ${request.method} ${path}` }, { status: 404 });
|
|
458
|
-
}, { owners: () => lanesOwners(lanes) });
|
|
478
|
+
}, { owners: () => lanesOwners(lanes), workspaces: () => lanesWorkspaces(lanes) });
|
|
459
479
|
}
|
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;
|