@volter/world-core 3.0.8 → 3.0.10
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 +8 -1
- package/dist/src/derived.d.ts +30 -0
- package/dist/src/index.d.ts +1 -1
- package/dist/src/pack-fetch.d.ts +5 -1
- package/dist/src/pack-fetch.js +43 -4
- package/dist/src/runtime.d.ts +1 -1
- package/generated/pack-facts.json +8 -1
- package/package.json +1 -1
- package/src/derived.ts +24 -0
- package/src/index.ts +1 -1
- package/src/pack-fetch.ts +37 -6
- package/src/runtime.ts +1 -1
|
@@ -979,9 +979,16 @@
|
|
|
979
979
|
},
|
|
980
980
|
{
|
|
981
981
|
"host": "github.com",
|
|
982
|
-
"pathPattern": "^/login|^/session|^/settings/|^/organizations/|^/apps/|^/[^/]+/[^/]+\\.git/"
|
|
982
|
+
"pathPattern": "^/login|^/session|^/settings/|^/organizations/|^/apps/|^/[^/]+/[^/]+\\.git/|^/[^/]+/[^/]+/settings/pages"
|
|
983
|
+
},
|
|
984
|
+
{
|
|
985
|
+
"suffix": ".github.io"
|
|
983
986
|
}
|
|
984
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
|
+
},
|
|
985
992
|
"archetype": "crud",
|
|
986
993
|
"protocol": {
|
|
987
994
|
"declared": "3",
|
package/dist/src/derived.d.ts
CHANGED
|
@@ -113,11 +113,41 @@ export type DerivedFetch = ((request: Request) => Promise<Response>) & {
|
|
|
113
113
|
upgrade?: WebSocketUpgrade;
|
|
114
114
|
/** The workspace screens it draws, its lanes' too (`GET /twin`'s `screens`). */
|
|
115
115
|
workspaces?(): Workspace[];
|
|
116
|
+
/** Whether it lays out frames of the World's board (`semantics/board.ts`). */
|
|
117
|
+
boards?: boolean;
|
|
118
|
+
/** Whether a built screen of it takes a URL: a board frame it draws. */
|
|
119
|
+
draws?(url: string): boolean;
|
|
116
120
|
};
|
|
117
121
|
/** A workspace screen a pack draws, as `GET /twin` lists it: its id and path (Viewing a World). */
|
|
118
122
|
export type Workspace = {
|
|
119
123
|
id: string;
|
|
120
124
|
path: string;
|
|
121
125
|
};
|
|
126
|
+
/** A frame of the World's board (architecture, "The board"): one thing of the vendor's where a person sees it, as the
|
|
127
|
+
* vendor's pack lays out its default board (`semantics/board.ts`), the editorial choice of which views of the vendor a
|
|
128
|
+
* person keeps open (a Worker's site, a repository's page, a Jira board). `GET /twin/board` lists them. */
|
|
129
|
+
export type BoardFrame = {
|
|
130
|
+
/** stable within the vendor, whatever the World's ids: what a saved board names the frame by */
|
|
131
|
+
id: string;
|
|
132
|
+
/** what it is, in the vendor's own word (a site, a repository, a board) */
|
|
133
|
+
kind: string;
|
|
134
|
+
title: string;
|
|
135
|
+
/** the vendor's container it is grouped under (an account, an organization, a project); `aka`, the ids it was known
|
|
136
|
+
* by before (an account a deploy adopted), so a board that included it under one still holds it */
|
|
137
|
+
section: {
|
|
138
|
+
id: string;
|
|
139
|
+
title: string;
|
|
140
|
+
aka?: string[];
|
|
141
|
+
};
|
|
142
|
+
/** where the vendor shows it: an absolute URL on the vendor's host, or on a hostname the vendor serves for its customer */
|
|
143
|
+
url: string;
|
|
144
|
+
/** the records whose changes are this frame's changes */
|
|
145
|
+
covers: Array<{
|
|
146
|
+
type: string;
|
|
147
|
+
id: string;
|
|
148
|
+
}>;
|
|
149
|
+
/** set by the kernel, never the pack: whether a built screen of the vendor's (any of its lanes') draws the url */
|
|
150
|
+
drawn?: boolean;
|
|
151
|
+
};
|
|
122
152
|
export declare function createDerivedFetch(options: DerivedFetchOptions): DerivedFetch;
|
|
123
153
|
export {};
|
package/dist/src/index.d.ts
CHANGED
|
@@ -28,7 +28,7 @@ export type { MultipartPart } from './multipart.js';
|
|
|
28
28
|
export declare const bindSemantics: (m: Parameters<typeof bindAny>[0], handlers: Record<string, Handler>, scope?: CoreScope) => Record<string, DerivedHandler>;
|
|
29
29
|
export { createPackFetch, createVendorFetch, type LanePart, type PackFetchOptions, type PackParts, type PackScenario } from './pack-fetch.js';
|
|
30
30
|
export { createDerivedFetch } from './derived.js';
|
|
31
|
-
export type { DerivedCall, DerivedFetch, DerivedFetchOptions, DerivedHandler, DerivedOperation, DerivedSurface } from './derived.js';
|
|
31
|
+
export type { DerivedCall, DerivedFetch, DerivedFetchOptions, DerivedHandler, DerivedOperation, DerivedSurface, BoardFrame } from './derived.js';
|
|
32
32
|
export { coreFor, crossCutting, derivedRequestScopes, vendorError } from './derived-core.js';
|
|
33
33
|
export { answerVendorErrors, RefusedWriteError } from './head.js';
|
|
34
34
|
export { GraphqlError, type GraphqlPart, type GraphqlResolver } from './graphql-wire.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, type Workspace } from './derived.js';
|
|
2
|
+
import { type DerivedFetch, type DerivedOwner, type DerivedSurface, type Workspace, type BoardFrame } 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';
|
|
@@ -39,6 +39,8 @@ export type PackParts = {
|
|
|
39
39
|
};
|
|
40
40
|
/** `semantics/sockets.ts`: each socket's engine, by the id the manifest's `sockets` gives it (sockets.ts). */
|
|
41
41
|
sockets?: Record<string, SocketEngine<HandlerContext>>;
|
|
42
|
+
/** `semantics/board.ts`: the vendor's frames on the World's board, read from its state (`GET /twin/board`). */
|
|
43
|
+
board?: (ctx: HandlerContext) => BoardFrame[] | Promise<BoardFrame[]>;
|
|
42
44
|
};
|
|
43
45
|
/** A pack's scenario (`semantics/scenario.ts` exports `scenario`): the adapter the kernel's engine reads the World's
|
|
44
46
|
* scenario document with, the operations it decides a turn of, and the request an operation is to it (undefined when
|
|
@@ -52,6 +54,8 @@ export type PackScenario<Req = any> = {
|
|
|
52
54
|
export type LanePart = {
|
|
53
55
|
fetch: ((request: Request) => Promise<Response>) & {
|
|
54
56
|
serves?: (method: string, path: string) => boolean;
|
|
57
|
+
boards?: boolean;
|
|
58
|
+
draws?: (url: string) => boolean;
|
|
55
59
|
};
|
|
56
60
|
manifest: {
|
|
57
61
|
doors?: ReadonlyArray<{
|
package/dist/src/pack-fetch.js
CHANGED
|
@@ -211,7 +211,17 @@ export function createPackFetch(parts, options = {}) {
|
|
|
211
211
|
// the workspaces a person can be shown (Viewing a World): each built workspace screen's id and path, the pack's own
|
|
212
212
|
// and its lanes'
|
|
213
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 ?? {})];
|
|
214
|
-
|
|
214
|
+
/** Whether a built screen of this pack takes a url: its host one the screen names, its path under the screen's. */
|
|
215
|
+
const drawsUrl = (at) => {
|
|
216
|
+
try {
|
|
217
|
+
const u = new URL(at);
|
|
218
|
+
return screens.some((s) => Boolean(s.host(u.hostname.toLowerCase())) && Boolean(s.path(u.pathname.replace(/\/+$/, '') || '/')));
|
|
219
|
+
}
|
|
220
|
+
catch {
|
|
221
|
+
return false;
|
|
222
|
+
}
|
|
223
|
+
};
|
|
224
|
+
// a context the kernel opens for what no surface operation names: a door, a screen, the clock, the board
|
|
215
225
|
const open = (request, id, method, path, params) => contextFor(m, { request, operation: { id, method, path, class: method === 'GET' ? 'retrieve' : 'action' }, params }, scope);
|
|
216
226
|
const refusedReadOnly = () => vendorError(m, m.readOnly);
|
|
217
227
|
const prefix = m.pathPrefix ? new RegExp(`^(?:${m.pathPrefix})(?=/)`) : undefined;
|
|
@@ -246,6 +256,15 @@ export function createPackFetch(parts, options = {}) {
|
|
|
246
256
|
// the discovery door is the pack's own, whichever lane would take its path
|
|
247
257
|
const asked = new URL(arrived.url).pathname.replace(/\/+$/, '') || '/';
|
|
248
258
|
const discovery = arrived.method === 'GET' && m.discovery !== undefined && (asked === '/twin' || asked === '/twin/scenario');
|
|
259
|
+
// the board is read from the state as it stands, before anything else runs (the clock, a lane, the API): a read,
|
|
260
|
+
// whoever asks, each frame drawn when a built screen takes its url; a pack that lays out none has an empty board
|
|
261
|
+
if (arrived.method === 'GET' && asked === '/twin/board') {
|
|
262
|
+
if (!parts.board)
|
|
263
|
+
return Response.json({ frames: [] });
|
|
264
|
+
const request = withHeaders(arrived, new Headers([...arrived.headers, [READ_ONLY_REQUEST_HEADER, '1']]));
|
|
265
|
+
const frames = await parts.board(await open(request, 'board', 'GET', '/twin/board', {}));
|
|
266
|
+
return Response.json({ frames: frames.map((f) => ({ ...f, drawn: drawsUrl(f.url) })) });
|
|
267
|
+
}
|
|
249
268
|
const toLane = discovery ? undefined : lane?.(arrived);
|
|
250
269
|
if (toLane)
|
|
251
270
|
return toLane(arrived);
|
|
@@ -331,10 +350,14 @@ export function createPackFetch(parts, options = {}) {
|
|
|
331
350
|
// over R2's /{Bucket}/{Key}), the root workspace at the root alone
|
|
332
351
|
const page = placed && ['document', 'iframe'].includes(request.headers.get('sec-fetch-dest') ?? '');
|
|
333
352
|
const rooted = (x) => x.s.decl.path.replace(/\/+$/, '') === '';
|
|
353
|
+
// a request the World routed from a vendor's own hostname (its proxy, a site, a call inside the World) is answered by
|
|
354
|
+
// that host's screens alone: a page of github.com never answers at <owner>.github.io, nor Cloudflare's sign-in at a
|
|
355
|
+
// Worker's Custom Domain; a screen by its path alone is for the twin reached at its own address
|
|
356
|
+
const addressed = !placed && request.headers.has('x-volter-twin-original-host');
|
|
334
357
|
const byPath = hits.find((x) => x.onHost && exact(x.s))
|
|
335
358
|
?? (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))
|
|
337
|
-
?? (placed ? hits.find((x) => x.s.decl.kind === 'workspace') : undefined));
|
|
359
|
+
?? (apiPath ? undefined : hits.find((x) => x.onHost) ?? (addressed ? undefined : hits.find((x) => !rooted(x))
|
|
360
|
+
?? (placed ? hits.find((x) => x.s.decl.kind === 'workspace') : undefined)));
|
|
338
361
|
if (byPath) {
|
|
339
362
|
if (readOnly && request.method !== 'GET' && request.method !== 'HEAD')
|
|
340
363
|
return refusedReadOnly();
|
|
@@ -396,7 +419,7 @@ export function createPackFetch(parts, options = {}) {
|
|
|
396
419
|
async (asked) => engineOf()?.serve(asked)) : undefined;
|
|
397
420
|
// what serves each operation: the pack's own, and each lane's
|
|
398
421
|
const owners = () => ({ ...api.owners(), ...lanesOwners(parts.lanes ?? {}) });
|
|
399
|
-
return derivedRequestScopes(m, Object.assign(served, { owners, serves, workspaces, ...(upgrade ? { upgrade } : {}) }));
|
|
422
|
+
return derivedRequestScopes(m, Object.assign(served, { owners, serves, workspaces, boards: Boolean(parts.board), draws: drawsUrl, ...(upgrade ? { upgrade } : {}) }));
|
|
400
423
|
}
|
|
401
424
|
/** The vendor's gateway over its lanes: the lane a request goes to (a door to the lane that declares it, then the first
|
|
402
425
|
* route whose conditions all hold, then the default), behind the gateway's CORS; undefined when none takes it. */
|
|
@@ -452,6 +475,22 @@ export function createVendorFetch(manifest, lanes) {
|
|
|
452
475
|
const lane = laneRouter(manifest.vendor, manifest.lanes, lanes);
|
|
453
476
|
return Object.assign(async (request) => {
|
|
454
477
|
const path = new URL(request.url).pathname.replace(/\/+$/, '') || '/';
|
|
478
|
+
// the vendor's board is the frames of the lanes that lay one out, each drawn when any lane's screen takes its url
|
|
479
|
+
if (request.method === 'GET' && path === '/twin/board') {
|
|
480
|
+
const frames = [];
|
|
481
|
+
for (const l of Object.values(lanes)) {
|
|
482
|
+
if (!l.fetch.boards)
|
|
483
|
+
continue;
|
|
484
|
+
try {
|
|
485
|
+
const answer = await l.fetch(new Request(request.url, { headers: request.headers }));
|
|
486
|
+
if (answer.ok)
|
|
487
|
+
frames.push(...((await answer.json()).frames ?? []));
|
|
488
|
+
}
|
|
489
|
+
catch { /* a lane whose board does not answer lays out nothing */ }
|
|
490
|
+
}
|
|
491
|
+
const draws = (url) => Object.values(lanes).some((l) => l.fetch.draws?.(url) ?? false);
|
|
492
|
+
return Response.json({ frames: frames.map((f) => ({ ...f, drawn: draws(f.url) })) });
|
|
493
|
+
}
|
|
455
494
|
if (request.method === 'GET' && path === '/twin' && manifest.discovery) {
|
|
456
495
|
// the vendor's screens are its lanes' (a dashboard drawn by one lane is the vendor's)
|
|
457
496
|
const screens = lanesWorkspaces(lanes);
|
package/dist/src/runtime.d.ts
CHANGED
|
@@ -8,7 +8,7 @@ export { isReadOnlyRequest, READ_ONLY_REQUEST_HEADER, ReadOnlyRequestError, runA
|
|
|
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';
|
|
10
10
|
export type { Traceparent } from './trace-context.js';
|
|
11
|
-
export type { DerivedCall, DerivedCoreOutcome, DerivedFetch, DerivedFetchOptions, DerivedHandler, DerivedOperation, DerivedOwner, DerivedSurface } from './derived.js';
|
|
11
|
+
export type { DerivedCall, DerivedCoreOutcome, DerivedFetch, DerivedFetchOptions, DerivedHandler, DerivedOperation, DerivedOwner, DerivedSurface, BoardFrame } from './derived.js';
|
|
12
12
|
export { authRefusal, bindSemantics, coreFor, crossCutting, flagOf, derivedRequestScopes, observeTransitions, resourcesOfType as twinResourcesOfType, semanticsContext, stateOf, transitionFor, parseBracketForm, readParams, render as renderDerived, serveCore, sse, vendorError } from './derived-core.js';
|
|
13
13
|
export { deliverEvents, fillEnvelope, signEvent, signEventAll, verifyEvent } from './events.js';
|
|
14
14
|
export { equalSecrets, hmac, jwks, jwtDecode, jwtSign, jwtVerify, keyId, lettersFrom, md5, sha256, SIGNING_ALGORITHMS, uuidFrom } from './signing.js';
|
|
@@ -979,9 +979,16 @@
|
|
|
979
979
|
},
|
|
980
980
|
{
|
|
981
981
|
"host": "github.com",
|
|
982
|
-
"pathPattern": "^/login|^/session|^/settings/|^/organizations/|^/apps/|^/[^/]+/[^/]+\\.git/"
|
|
982
|
+
"pathPattern": "^/login|^/session|^/settings/|^/organizations/|^/apps/|^/[^/]+/[^/]+\\.git/|^/[^/]+/[^/]+/settings/pages"
|
|
983
|
+
},
|
|
984
|
+
{
|
|
985
|
+
"suffix": ".github.io"
|
|
983
986
|
}
|
|
984
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
|
+
},
|
|
985
992
|
"archetype": "crud",
|
|
986
993
|
"protocol": {
|
|
987
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.10",
|
|
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
|
@@ -198,11 +198,35 @@ export type DerivedFetch = ((request: Request) => Promise<Response>) & {
|
|
|
198
198
|
upgrade?: WebSocketUpgrade;
|
|
199
199
|
/** The workspace screens it draws, its lanes' too (`GET /twin`'s `screens`). */
|
|
200
200
|
workspaces?(): Workspace[];
|
|
201
|
+
/** Whether it lays out frames of the World's board (`semantics/board.ts`). */
|
|
202
|
+
boards?: boolean;
|
|
203
|
+
/** Whether a built screen of it takes a URL: a board frame it draws. */
|
|
204
|
+
draws?(url: string): boolean;
|
|
201
205
|
};
|
|
202
206
|
|
|
203
207
|
/** A workspace screen a pack draws, as `GET /twin` lists it: its id and path (Viewing a World). */
|
|
204
208
|
export type Workspace = { id: string; path: string };
|
|
205
209
|
|
|
210
|
+
/** A frame of the World's board (architecture, "The board"): one thing of the vendor's where a person sees it, as the
|
|
211
|
+
* vendor's pack lays out its default board (`semantics/board.ts`), the editorial choice of which views of the vendor a
|
|
212
|
+
* person keeps open (a Worker's site, a repository's page, a Jira board). `GET /twin/board` lists them. */
|
|
213
|
+
export type BoardFrame = {
|
|
214
|
+
/** stable within the vendor, whatever the World's ids: what a saved board names the frame by */
|
|
215
|
+
id: string;
|
|
216
|
+
/** what it is, in the vendor's own word (a site, a repository, a board) */
|
|
217
|
+
kind: string;
|
|
218
|
+
title: string;
|
|
219
|
+
/** the vendor's container it is grouped under (an account, an organization, a project); `aka`, the ids it was known
|
|
220
|
+
* by before (an account a deploy adopted), so a board that included it under one still holds it */
|
|
221
|
+
section: { id: string; title: string; aka?: string[] };
|
|
222
|
+
/** where the vendor shows it: an absolute URL on the vendor's host, or on a hostname the vendor serves for its customer */
|
|
223
|
+
url: string;
|
|
224
|
+
/** the records whose changes are this frame's changes */
|
|
225
|
+
covers: Array<{ type: string; id: string }>;
|
|
226
|
+
/** set by the kernel, never the pack: whether a built screen of the vendor's (any of its lanes') draws the url */
|
|
227
|
+
drawn?: boolean;
|
|
228
|
+
};
|
|
229
|
+
|
|
206
230
|
export function createDerivedFetch(options: DerivedFetchOptions): DerivedFetch {
|
|
207
231
|
const handlers = options.handlers ?? {};
|
|
208
232
|
const known = new Set(options.surface.operations.map((o) => o.id));
|
package/src/index.ts
CHANGED
|
@@ -39,7 +39,7 @@ export const bindSemantics = (m: Parameters<typeof bindAny>[0], handlers: Record
|
|
|
39
39
|
// ── the derived fetch ──────────────────────────────────────────────────────────────────────────
|
|
40
40
|
export { createPackFetch, createVendorFetch, type LanePart, type PackFetchOptions, type PackParts, type PackScenario } from './pack-fetch.ts';
|
|
41
41
|
export { createDerivedFetch } from './derived.ts';
|
|
42
|
-
export type { DerivedCall, DerivedFetch, DerivedFetchOptions, DerivedHandler, DerivedOperation, DerivedSurface } from './derived.ts';
|
|
42
|
+
export type { DerivedCall, DerivedFetch, DerivedFetchOptions, DerivedHandler, DerivedOperation, DerivedSurface, BoardFrame } from './derived.ts';
|
|
43
43
|
export { coreFor, crossCutting, derivedRequestScopes, vendorError } from './derived-core.ts';
|
|
44
44
|
export { answerVendorErrors, RefusedWriteError } from './head.ts';
|
|
45
45
|
// a GraphQL API's resolvers (`semantics/graphql.ts`), and the error one answers with the vendor's type
|
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, type Workspace } from './derived.ts';
|
|
13
|
+
import { compileSurface, createDerivedFetch, matchOperation, type DerivedFetch, type DerivedOperation, type DerivedOwner, type DerivedSurface, type Workspace, type BoardFrame } 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';
|
|
@@ -57,6 +57,8 @@ export type PackParts = {
|
|
|
57
57
|
graphql?: GraphqlPart<HandlerContext> & { sdl: string };
|
|
58
58
|
/** `semantics/sockets.ts`: each socket's engine, by the id the manifest's `sockets` gives it (sockets.ts). */
|
|
59
59
|
sockets?: Record<string, SocketEngine<HandlerContext>>;
|
|
60
|
+
/** `semantics/board.ts`: the vendor's frames on the World's board, read from its state (`GET /twin/board`). */
|
|
61
|
+
board?: (ctx: HandlerContext) => BoardFrame[] | Promise<BoardFrame[]>;
|
|
60
62
|
};
|
|
61
63
|
|
|
62
64
|
/** A pack's scenario (`semantics/scenario.ts` exports `scenario`): the adapter the kernel's engine reads the World's
|
|
@@ -66,7 +68,7 @@ export type PackParts = {
|
|
|
66
68
|
export type PackScenario<Req = any> = { adapter: PackScenarioAdapter<Req>; operations: ReadonlyArray<string>; request: (ctx: HandlerContext) => Req | undefined };
|
|
67
69
|
|
|
68
70
|
/** A lane as its vendor's fetch is given it: its own pack fetch, and its manifest (the doors it declares). */
|
|
69
|
-
export type LanePart = { fetch: ((request: Request) => Promise<Response>) & { serves?: (method: string, path: string) => boolean }; manifest: { doors?: ReadonlyArray<{ method: string; path: string }> } };
|
|
71
|
+
export type LanePart = { fetch: ((request: Request) => Promise<Response>) & { serves?: (method: string, path: string) => boolean; boards?: boolean; draws?: (url: string) => boolean }; manifest: { doors?: ReadonlyArray<{ method: string; path: string }> } };
|
|
70
72
|
|
|
71
73
|
/** Where a pack serves: its World's root, whether it was started read-only, the World instant when a caller pins it, the
|
|
72
74
|
* World's managed Postgres the runtime binds a pack declaring `managedDatabase` to (a handler's `ctx.engine`), and the
|
|
@@ -245,7 +247,11 @@ export function createPackFetch(parts: PackParts, options: PackFetchOptions = {}
|
|
|
245
247
|
// the workspaces a person can be shown (Viewing a World): each built workspace screen's id and path, the pack's own
|
|
246
248
|
// and its lanes'
|
|
247
249
|
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 ?? {})];
|
|
248
|
-
|
|
250
|
+
/** Whether a built screen of this pack takes a url: its host one the screen names, its path under the screen's. */
|
|
251
|
+
const drawsUrl = (at: string): boolean => {
|
|
252
|
+
try { const u = new URL(at); return screens.some((s) => Boolean(s.host(u.hostname.toLowerCase())) && Boolean(s.path(u.pathname.replace(/\/+$/, '') || '/'))); } catch { return false; }
|
|
253
|
+
};
|
|
254
|
+
// a context the kernel opens for what no surface operation names: a door, a screen, the clock, the board
|
|
249
255
|
const open = (request: Request, id: string, method: string, path: string, params: Record<string, string>): Promise<HandlerContext> =>
|
|
250
256
|
contextFor(m, { request, operation: { id, method, path, class: method === 'GET' ? 'retrieve' : 'action' } as DerivedOperation, params }, scope);
|
|
251
257
|
const refusedReadOnly = (): Response => vendorError(m, m.readOnly);
|
|
@@ -278,6 +284,14 @@ export function createPackFetch(parts: PackParts, options: PackFetchOptions = {}
|
|
|
278
284
|
// the discovery door is the pack's own, whichever lane would take its path
|
|
279
285
|
const asked = new URL(arrived.url).pathname.replace(/\/+$/, '') || '/';
|
|
280
286
|
const discovery = arrived.method === 'GET' && m.discovery !== undefined && (asked === '/twin' || asked === '/twin/scenario');
|
|
287
|
+
// the board is read from the state as it stands, before anything else runs (the clock, a lane, the API): a read,
|
|
288
|
+
// whoever asks, each frame drawn when a built screen takes its url; a pack that lays out none has an empty board
|
|
289
|
+
if (arrived.method === 'GET' && asked === '/twin/board') {
|
|
290
|
+
if (!parts.board) return Response.json({ frames: [] });
|
|
291
|
+
const request = withHeaders(arrived, new Headers([...arrived.headers, [READ_ONLY_REQUEST_HEADER, '1']]));
|
|
292
|
+
const frames = await parts.board(await open(request, 'board', 'GET', '/twin/board', {}));
|
|
293
|
+
return Response.json({ frames: frames.map((f) => ({ ...f, drawn: drawsUrl(f.url) })) });
|
|
294
|
+
}
|
|
281
295
|
const toLane = discovery ? undefined : lane?.(arrived);
|
|
282
296
|
if (toLane) return toLane(arrived);
|
|
283
297
|
// only the kernel names a scenario's decision, or a path prefix's parameters
|
|
@@ -349,10 +363,14 @@ export function createPackFetch(parts: PackParts, options: PackFetchOptions = {}
|
|
|
349
363
|
// over R2's /{Bucket}/{Key}), the root workspace at the root alone
|
|
350
364
|
const page = placed && ['document', 'iframe'].includes(request.headers.get('sec-fetch-dest') ?? '');
|
|
351
365
|
const rooted = (x: (typeof hits)[number]): boolean => x.s.decl.path.replace(/\/+$/, '') === '';
|
|
366
|
+
// a request the World routed from a vendor's own hostname (its proxy, a site, a call inside the World) is answered by
|
|
367
|
+
// that host's screens alone: a page of github.com never answers at <owner>.github.io, nor Cloudflare's sign-in at a
|
|
368
|
+
// Worker's Custom Domain; a screen by its path alone is for the twin reached at its own address
|
|
369
|
+
const addressed = !placed && request.headers.has('x-volter-twin-original-host');
|
|
352
370
|
const byPath = hits.find((x) => x.onHost && exact(x.s))
|
|
353
371
|
?? (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))
|
|
355
|
-
?? (placed ? hits.find((x) => x.s.decl.kind === 'workspace') : undefined));
|
|
372
|
+
?? (apiPath ? undefined : hits.find((x) => x.onHost) ?? (addressed ? undefined : hits.find((x) => !rooted(x))
|
|
373
|
+
?? (placed ? hits.find((x) => x.s.decl.kind === 'workspace') : undefined)));
|
|
356
374
|
if (byPath) {
|
|
357
375
|
if (readOnly && request.method !== 'GET' && request.method !== 'HEAD') return refusedReadOnly();
|
|
358
376
|
return byPath.s.answer(await open(request, byPath.s.decl.id, request.method, byPath.s.decl.path, { ...(byPath.onHost?.params ?? {}), ...byPath.hit!.params }));
|
|
@@ -403,7 +421,7 @@ export function createPackFetch(parts: PackParts, options: PackFetchOptions = {}
|
|
|
403
421
|
async (asked) => engineOf()?.serve(asked)) : undefined;
|
|
404
422
|
// what serves each operation: the pack's own, and each lane's
|
|
405
423
|
const owners = (): Record<string, DerivedOwner> => ({ ...api.owners(), ...lanesOwners(parts.lanes ?? {}) });
|
|
406
|
-
return derivedRequestScopes(m, Object.assign(served, { owners, serves, workspaces, ...(upgrade ? { upgrade } : {}) }));
|
|
424
|
+
return derivedRequestScopes(m, Object.assign(served, { owners, serves, workspaces, boards: Boolean(parts.board), draws: drawsUrl, ...(upgrade ? { upgrade } : {}) }));
|
|
407
425
|
}
|
|
408
426
|
|
|
409
427
|
/** The vendor's gateway over its lanes: the lane a request goes to (a door to the lane that declares it, then the first
|
|
@@ -464,6 +482,19 @@ export function createVendorFetch(manifest: VendorManifest, lanes: Record<string
|
|
|
464
482
|
const lane = laneRouter(manifest.vendor, manifest.lanes, lanes);
|
|
465
483
|
return Object.assign(async (request: Request): Promise<Response> => {
|
|
466
484
|
const path = new URL(request.url).pathname.replace(/\/+$/, '') || '/';
|
|
485
|
+
// the vendor's board is the frames of the lanes that lay one out, each drawn when any lane's screen takes its url
|
|
486
|
+
if (request.method === 'GET' && path === '/twin/board') {
|
|
487
|
+
const frames: BoardFrame[] = [];
|
|
488
|
+
for (const l of Object.values(lanes)) {
|
|
489
|
+
if (!l.fetch.boards) continue;
|
|
490
|
+
try {
|
|
491
|
+
const answer = await l.fetch(new Request(request.url, { headers: request.headers }));
|
|
492
|
+
if (answer.ok) frames.push(...(((await answer.json()) as { frames?: BoardFrame[] }).frames ?? []));
|
|
493
|
+
} catch { /* a lane whose board does not answer lays out nothing */ }
|
|
494
|
+
}
|
|
495
|
+
const draws = (url: string): boolean => Object.values(lanes).some((l) => l.fetch.draws?.(url) ?? false);
|
|
496
|
+
return Response.json({ frames: frames.map((f) => ({ ...f, drawn: draws(f.url) })) });
|
|
497
|
+
}
|
|
467
498
|
if (request.method === 'GET' && path === '/twin' && manifest.discovery) {
|
|
468
499
|
// the vendor's screens are its lanes' (a dashboard drawn by one lane is the vendor's)
|
|
469
500
|
const screens = lanesWorkspaces(lanes);
|
package/src/runtime.ts
CHANGED
|
@@ -17,7 +17,7 @@ export { isReadOnlyRequest, READ_ONLY_REQUEST_HEADER, ReadOnlyRequestError, runA
|
|
|
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';
|
|
19
19
|
export type { Traceparent } from './trace-context.ts';
|
|
20
|
-
export type { DerivedCall, DerivedCoreOutcome, DerivedFetch, DerivedFetchOptions, DerivedHandler, DerivedOperation, DerivedOwner, DerivedSurface } from './derived.ts';
|
|
20
|
+
export type { DerivedCall, DerivedCoreOutcome, DerivedFetch, DerivedFetchOptions, DerivedHandler, DerivedOperation, DerivedOwner, DerivedSurface, BoardFrame } from './derived.ts';
|
|
21
21
|
export { authRefusal, bindSemantics, coreFor, crossCutting, flagOf, derivedRequestScopes, observeTransitions, resourcesOfType as twinResourcesOfType, semanticsContext, stateOf, transitionFor, parseBracketForm, readParams, render as renderDerived, serveCore, sse, vendorError } from './derived-core.ts';
|
|
22
22
|
export { deliverEvents, fillEnvelope, signEvent, signEventAll, verifyEvent } from './events.ts';
|
|
23
23
|
export { equalSecrets, hmac, jwks, jwtDecode, jwtSign, jwtVerify, keyId, lettersFrom, md5, sha256, SIGNING_ALGORITHMS, uuidFrom } from './signing.ts';
|