@volter/world-core 3.0.9 → 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/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 +37 -2
- package/dist/src/runtime.d.ts +1 -1
- package/package.json +1 -1
- package/src/derived.ts +24 -0
- package/src/index.ts +1 -1
- package/src/pack-fetch.ts +31 -4
- package/src/runtime.ts +1 -1
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);
|
|
@@ -400,7 +419,7 @@ export function createPackFetch(parts, options = {}) {
|
|
|
400
419
|
async (asked) => engineOf()?.serve(asked)) : undefined;
|
|
401
420
|
// what serves each operation: the pack's own, and each lane's
|
|
402
421
|
const owners = () => ({ ...api.owners(), ...lanesOwners(parts.lanes ?? {}) });
|
|
403
|
-
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 } : {}) }));
|
|
404
423
|
}
|
|
405
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
|
|
406
425
|
* route whose conditions all hold, then the default), behind the gateway's CORS; undefined when none takes it. */
|
|
@@ -456,6 +475,22 @@ export function createVendorFetch(manifest, lanes) {
|
|
|
456
475
|
const lane = laneRouter(manifest.vendor, manifest.lanes, lanes);
|
|
457
476
|
return Object.assign(async (request) => {
|
|
458
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
|
+
}
|
|
459
494
|
if (request.method === 'GET' && path === '/twin' && manifest.discovery) {
|
|
460
495
|
// the vendor's screens are its lanes' (a dashboard drawn by one lane is the vendor's)
|
|
461
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';
|
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
|
|
@@ -407,7 +421,7 @@ export function createPackFetch(parts: PackParts, options: PackFetchOptions = {}
|
|
|
407
421
|
async (asked) => engineOf()?.serve(asked)) : undefined;
|
|
408
422
|
// what serves each operation: the pack's own, and each lane's
|
|
409
423
|
const owners = (): Record<string, DerivedOwner> => ({ ...api.owners(), ...lanesOwners(parts.lanes ?? {}) });
|
|
410
|
-
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 } : {}) }));
|
|
411
425
|
}
|
|
412
426
|
|
|
413
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
|
|
@@ -468,6 +482,19 @@ export function createVendorFetch(manifest: VendorManifest, lanes: Record<string
|
|
|
468
482
|
const lane = laneRouter(manifest.vendor, manifest.lanes, lanes);
|
|
469
483
|
return Object.assign(async (request: Request): Promise<Response> => {
|
|
470
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
|
+
}
|
|
471
498
|
if (request.method === 'GET' && path === '/twin' && manifest.discovery) {
|
|
472
499
|
// the vendor's screens are its lanes' (a dashboard drawn by one lane is the vendor's)
|
|
473
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';
|