@volter/twin-planetscale 0.1.0
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/LICENSE +202 -0
- package/README.md +473 -0
- package/api/src/fetch.ts +50 -0
- package/api/src/generated/surface.gen.json +1 -0
- package/api/src/generated/ui.gen.json +1 -0
- package/api/src/index.ts +19 -0
- package/api/src/manifest.ts +136 -0
- package/api/src/screens/deploy-request.tsx +111 -0
- package/api/src/screens/service-tokens.tsx +141 -0
- package/api/src/screens/session.tsx +117 -0
- package/api/src/semantics/audit.ts +82 -0
- package/api/src/semantics/backups.ts +258 -0
- package/api/src/semantics/branches.ts +201 -0
- package/api/src/semantics/deploy-requests.ts +493 -0
- package/api/src/semantics/index.ts +371 -0
- package/api/src/semantics/shared.ts +141 -0
- package/api/src/semantics/time.ts +77 -0
- package/api/src/token-gate.ts +96 -0
- package/dist/api/src/fetch.d.ts +8 -0
- package/dist/api/src/fetch.js +51 -0
- package/dist/api/src/fetch.ts +50 -0
- package/dist/api/src/generated/surface.gen.json +1 -0
- package/dist/api/src/generated/ui.gen.json +1 -0
- package/dist/api/src/index.ts +19 -0
- package/dist/api/src/manifest.d.ts +2 -0
- package/dist/api/src/manifest.js +113 -0
- package/dist/api/src/manifest.ts +136 -0
- package/dist/api/src/screens/deploy-request.d.ts +7 -0
- package/dist/api/src/screens/deploy-request.js +106 -0
- package/dist/api/src/screens/deploy-request.tsx +111 -0
- package/dist/api/src/screens/service-tokens.d.ts +3 -0
- package/dist/api/src/screens/service-tokens.js +134 -0
- package/dist/api/src/screens/service-tokens.tsx +141 -0
- package/dist/api/src/screens/session.d.ts +11 -0
- package/dist/api/src/screens/session.js +108 -0
- package/dist/api/src/screens/session.tsx +117 -0
- package/dist/api/src/semantics/audit.d.ts +31 -0
- package/dist/api/src/semantics/audit.js +80 -0
- package/dist/api/src/semantics/audit.ts +82 -0
- package/dist/api/src/semantics/backups.d.ts +37 -0
- package/dist/api/src/semantics/backups.js +264 -0
- package/dist/api/src/semantics/backups.ts +258 -0
- package/dist/api/src/semantics/branches.d.ts +53 -0
- package/dist/api/src/semantics/branches.js +197 -0
- package/dist/api/src/semantics/branches.ts +201 -0
- package/dist/api/src/semantics/deploy-requests.d.ts +47 -0
- package/dist/api/src/semantics/deploy-requests.js +491 -0
- package/dist/api/src/semantics/deploy-requests.ts +493 -0
- package/dist/api/src/semantics/index.d.ts +20 -0
- package/dist/api/src/semantics/index.js +381 -0
- package/dist/api/src/semantics/index.ts +371 -0
- package/dist/api/src/semantics/shared.d.ts +36 -0
- package/dist/api/src/semantics/shared.js +132 -0
- package/dist/api/src/semantics/shared.ts +141 -0
- package/dist/api/src/semantics/time.d.ts +2 -0
- package/dist/api/src/semantics/time.js +81 -0
- package/dist/api/src/semantics/time.ts +77 -0
- package/dist/api/src/token-gate.d.ts +9 -0
- package/dist/api/src/token-gate.js +97 -0
- package/dist/api/src/token-gate.ts +96 -0
- package/dist/src/cli.d.ts +2 -0
- package/dist/src/cli.js +61 -0
- package/dist/src/generated/surface.gen.json +1 -0
- package/dist/src/generated/ui.gen.json +1 -0
- package/dist/src/index.d.ts +26 -0
- package/dist/src/index.js +156 -0
- package/dist/src/manifest.d.ts +2 -0
- package/dist/src/manifest.js +41 -0
- package/dist/src/planetscale-budget.d.ts +78 -0
- package/dist/src/planetscale-budget.js +305 -0
- package/dist/src/planetscale-capabilities.d.ts +10 -0
- package/dist/src/planetscale-capabilities.js +3977 -0
- package/dist/src/planetscale-collation-weights.gen.d.ts +4 -0
- package/dist/src/planetscale-collation-weights.gen.js +12 -0
- package/dist/src/planetscale-collation.d.ts +70 -0
- package/dist/src/planetscale-collation.js +391 -0
- package/dist/src/planetscale-conformance.d.ts +8 -0
- package/dist/src/planetscale-conformance.js +213 -0
- package/dist/src/planetscale-connector.d.ts +150 -0
- package/dist/src/planetscale-connector.js +532 -0
- package/dist/src/planetscale-deploy.d.ts +26 -0
- package/dist/src/planetscale-deploy.js +235 -0
- package/dist/src/planetscale-information-schema.d.ts +32 -0
- package/dist/src/planetscale-information-schema.js +299 -0
- package/dist/src/planetscale-mysql.d.ts +33 -0
- package/dist/src/planetscale-mysql.js +547 -0
- package/dist/src/planetscale-roles.d.ts +11 -0
- package/dist/src/planetscale-roles.js +60 -0
- package/dist/src/planetscale-row.d.ts +12 -0
- package/dist/src/planetscale-row.js +39 -0
- package/dist/src/planetscale-server.d.ts +42 -0
- package/dist/src/planetscale-server.js +137 -0
- package/dist/src/planetscale-sql.d.ts +701 -0
- package/dist/src/planetscale-sql.js +7167 -0
- package/dist/src/planetscale-store.d.ts +126 -0
- package/dist/src/planetscale-store.js +827 -0
- package/dist/src/planetscale-twin.d.ts +48 -0
- package/dist/src/planetscale-twin.js +290 -0
- package/dist/src/planetscale-values.d.ts +139 -0
- package/dist/src/planetscale-values.js +719 -0
- package/dist/src/planetscale-wire.d.ts +110 -0
- package/dist/src/planetscale-wire.js +188 -0
- package/dist/src/semantics/psdb.d.ts +18 -0
- package/dist/src/semantics/psdb.js +30 -0
- package/package.json +58 -0
- package/src/cli.ts +58 -0
- package/src/generated/surface.gen.json +1 -0
- package/src/generated/ui.gen.json +1 -0
- package/src/index.ts +267 -0
- package/src/manifest.ts +60 -0
- package/src/planetscale-budget.ts +347 -0
- package/src/planetscale-capabilities.ts +3862 -0
- package/src/planetscale-collation-weights.gen.ts +13 -0
- package/src/planetscale-collation.ts +378 -0
- package/src/planetscale-conformance.ts +237 -0
- package/src/planetscale-connector.ts +571 -0
- package/src/planetscale-deploy.ts +197 -0
- package/src/planetscale-information-schema.ts +322 -0
- package/src/planetscale-mysql.ts +339 -0
- package/src/planetscale-roles.ts +71 -0
- package/src/planetscale-row.ts +43 -0
- package/src/planetscale-server.ts +162 -0
- package/src/planetscale-sql.ts +5957 -0
- package/src/planetscale-store.ts +869 -0
- package/src/planetscale-twin.ts +338 -0
- package/src/planetscale-values.ts +572 -0
- package/src/planetscale-wire.ts +274 -0
- package/src/semantics/psdb.ts +57 -0
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
// The management lane's service-token gate, in front of its dispatch. PlanetScale reads a service token as
|
|
2
|
+
// `Authorization: <SERVICE_TOKEN_ID>:<SERVICE_TOKEN>` (https://planetscale.com/docs/api/reference/service-tokens), and each
|
|
3
|
+
// operation names, in its spec's "Authorization" section, the service token accesses any one of which lets a token call
|
|
4
|
+
// it (`ACCESSES` below, copied from those sections for the operations this lane serves).
|
|
5
|
+
//
|
|
6
|
+
// Where the reference stops and the twin decides: a request without a well-formed token is 401 (the spec lists 401 for
|
|
7
|
+
// every operation), a token this lane made answers 401 when its secret is wrong and 403 when it holds none of the
|
|
8
|
+
// operation's accesses, and a token id no service token of this lane has is the World's own organization token, allowed
|
|
9
|
+
// everything (as the World's own psdb credential is): an application pointed at the World (Dub's backup cron) carries
|
|
10
|
+
// whatever token its own configuration holds.
|
|
11
|
+
import { createHash } from 'node:crypto';
|
|
12
|
+
import type { SemanticsContext } from '@volter/world-core';
|
|
13
|
+
|
|
14
|
+
const ACCESSES: Record<string, string[]> = {
|
|
15
|
+
create_database: ['create_databases'],
|
|
16
|
+
get_database: ['read_database', 'delete_database', 'write_database', 'read_branch', 'delete_branch', 'create_branch', 'connect_branch', 'connect_production_branch', 'connect_production_read_only_branch', 'read_backups', 'write_backups', 'delete_backups'],
|
|
17
|
+
delete_database: ['delete_database'],
|
|
18
|
+
create_password: ['connect_production_branch', 'connect_production_read_only_branch', 'connect_branch'],
|
|
19
|
+
list_passwords: ['read_branch', 'delete_branch', 'create_branch', 'connect_production_branch', 'connect_production_read_only_branch', 'connect_branch'],
|
|
20
|
+
get_password: ['read_branch', 'delete_branch', 'create_branch', 'connect_production_branch', 'connect_production_read_only_branch', 'connect_branch'],
|
|
21
|
+
delete_password: ['delete_production_branch_password', 'delete_production_read_only_branch_password', 'delete_branch_password'],
|
|
22
|
+
create_backup: ['write_backups'],
|
|
23
|
+
list_backups: ['read_backups'],
|
|
24
|
+
get_backup: ['read_backups'],
|
|
25
|
+
delete_backup: ['delete_backups', 'delete_production_branch_backups'],
|
|
26
|
+
create_branch: ['create_branch', 'restore_production_branch_backup', 'restore_backup'],
|
|
27
|
+
get_branch: ['read_branch', 'delete_branch', 'create_branch', 'connect_production_branch', 'connect_branch'],
|
|
28
|
+
list_branches: ['read_branch', 'delete_branch', 'create_branch', 'connect_production_branch', 'connect_branch'],
|
|
29
|
+
delete_branch: ['delete_branch'],
|
|
30
|
+
// the spec's enable/disable_safe_migrations name no access; the twin asks what changing a database's setup asks
|
|
31
|
+
enable_safe_migrations: ['write_database'],
|
|
32
|
+
disable_safe_migrations: ['write_database'],
|
|
33
|
+
update_database_settings: ['write_database'],
|
|
34
|
+
create_deploy_request: ['create_deploy_request'],
|
|
35
|
+
get_deploy_request: ['read_deploy_request'],
|
|
36
|
+
list_deploy_requests: ['read_deploy_request'],
|
|
37
|
+
get_deployment: ['read_deploy_request'],
|
|
38
|
+
list_deploy_operations: ['read_deploy_request'],
|
|
39
|
+
list_deploy_request_reviews: ['read_deploy_request'],
|
|
40
|
+
review_deploy_request: ['approve_deploy_request', 'review_deploy_request'],
|
|
41
|
+
queue_deploy_request: ['create_deploy_request'],
|
|
42
|
+
close_deploy_request: ['create_deploy_request'],
|
|
43
|
+
complete_revert: ['create_deploy_request'],
|
|
44
|
+
list_audit_logs: ['read_audit_logs'],
|
|
45
|
+
create_backup_policy: ['write_backups'],
|
|
46
|
+
list_backup_policies: ['read_backups'],
|
|
47
|
+
get_backup_policy: ['read_backups'],
|
|
48
|
+
create_service_token: ['write_service_tokens'],
|
|
49
|
+
list_service_tokens: ['read_service_tokens'],
|
|
50
|
+
delete_service_token: ['delete_service_tokens'],
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const refuse = (status: number, code: string, message: string): Response => Response.json({ code, message }, { status });
|
|
54
|
+
|
|
55
|
+
/** The accesses the calling token holds on `database` (its organization accesses count everywhere), or undefined for a
|
|
56
|
+
* token this lane did not make (the World's own, allowed everything). A handler asks it where one operation needs a
|
|
57
|
+
* different access by what it acts on (a production branch's password, a restore from a production backup). */
|
|
58
|
+
export function heldAccesses(ctx: SemanticsContext, database: string): Set<string> | undefined {
|
|
59
|
+
const id = /^([^:\s]+):/.exec(ctx.call.request.headers.get('authorization') ?? '')?.[1];
|
|
60
|
+
const token = id === undefined ? undefined : ctx.rowsRaw('ServiceToken').find((t) => t.id === id);
|
|
61
|
+
if (token === undefined) return undefined;
|
|
62
|
+
return new Set(((token.service_token_accesses ?? []) as Array<{ access: string; resource_type: string; resource_name: string }>)
|
|
63
|
+
.filter((a) => a.resource_type === 'Organization' || (a.resource_type === 'Database' && a.resource_name === database)).map((a) => a.access));
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** PlanetScale's 403 for a token that lacks every one of `needed`, or undefined when it holds one. */
|
|
67
|
+
export function lacks(ctx: SemanticsContext, database: string, needed: string[]): Response | undefined {
|
|
68
|
+
const held = heldAccesses(ctx, database);
|
|
69
|
+
if (held === undefined || needed.some((a) => held.has(a))) return undefined;
|
|
70
|
+
return refuse(403, 'forbidden', `Forbidden: this service token needs one of these accesses: ${needed.join(', ')}`);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** The gate's answer for a request, or undefined when it may pass. `operation` is the operation it matched, if any. */
|
|
74
|
+
export function tokenGate(ctx: SemanticsContext, request: Request, operation: string | undefined): Response | undefined {
|
|
75
|
+
const header = request.headers.get('authorization') ?? '';
|
|
76
|
+
const m = /^([^:\s]+):(\S+)$/.exec(header);
|
|
77
|
+
if (!m) return refuse(401, 'unauthorized', 'Unauthorized: send a service token as Authorization: <SERVICE_TOKEN_ID>:<SERVICE_TOKEN>');
|
|
78
|
+
const token = ctx.rowsRaw('ServiceToken').find((t) => t.id === m[1]);
|
|
79
|
+
if (token === undefined) return undefined;
|
|
80
|
+
// a token deleted on the settings page or by delete_service_token is refused as any token PlanetScale does not hold
|
|
81
|
+
if (token._deleted_at) return refuse(401, 'unauthorized', 'Unauthorized: the service token is not valid');
|
|
82
|
+
// a token whose ttl has passed "will be invalid" (the spec's create_service_token)
|
|
83
|
+
if (typeof token.expires_at === 'string' && Date.parse(token.expires_at) <= Date.parse(ctx.occurredAt)) return refuse(401, 'unauthorized', 'Unauthorized: the service token is not valid');
|
|
84
|
+
if (token._secret_sha256 !== createHash('sha256').update(m[2]!).digest('hex')) return refuse(401, 'unauthorized', 'Unauthorized: the service token is not valid');
|
|
85
|
+
const needed = operation === undefined ? undefined : ACCESSES[operation];
|
|
86
|
+
if (needed === undefined) return undefined;
|
|
87
|
+
const database = ctx.call.params.database;
|
|
88
|
+
const held = ((token.service_token_accesses ?? []) as Array<{ access: string; resource_type: string; resource_name: string }>)
|
|
89
|
+
.filter((a) => a.resource_type === 'Organization' || (a.resource_type === 'Database' && a.resource_name === database));
|
|
90
|
+
return held.some((a) => needed.includes(a.access)) ? undefined : forbidden(needed);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** PlanetScale's 403 for a token that holds none of an operation's accesses. */
|
|
94
|
+
function forbidden(needed: string[]): Response {
|
|
95
|
+
return refuse(403, 'forbidden', `Forbidden: this service token needs one of these accesses: ${needed.join(', ')}`);
|
|
96
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// The management API lane of the planetscale pack (`api.planetscale.com/v1`), the lane's own dispatch: the pack's router
|
|
2
|
+
// (../../src/planetscale-server.ts) sends every `/v1/organizations` request here, and the organization's service tokens
|
|
3
|
+
// page (`app.planetscale.com/{organization}/settings/service-tokens`, screens/). The generated surface is PlanetScale's
|
|
4
|
+
// own Swagger document; the service-token gate sits in front of the dispatch (token-gate.ts), then the kernel's
|
|
5
|
+
// cross-cutting checks (read-only, a malformed body), then the handlers (semantics/). An operation this lane does not
|
|
6
|
+
// serve answers PlanetScale's 404: the gap.
|
|
7
|
+
import { bindSemantics, compileSurface, coreFor, createDerivedFetch, crossCutting, matchOperation, semanticsContext } from '@volter/world-core';
|
|
8
|
+
import surface from './generated/surface.gen.json' with { type: 'json' };
|
|
9
|
+
import { manifest } from "./manifest.js";
|
|
10
|
+
import { apiSemantics } from "./semantics/index.js";
|
|
11
|
+
import { catchUp } from "./semantics/time.js";
|
|
12
|
+
import { tokenGate } from "./token-gate.js";
|
|
13
|
+
import { serviceTokensPage } from "./screens/service-tokens.js";
|
|
14
|
+
import { sessionFlow } from "./screens/session.js";
|
|
15
|
+
import { deployRequestPage } from "./screens/deploy-request.js";
|
|
16
|
+
const SCREEN = { id: 'ServiceTokensPage', method: 'POST', path: '/{organization}/settings/service-tokens', class: 'action' };
|
|
17
|
+
const GATE = { id: 'TwinTokenGate', method: 'GET', path: '/v1/organizations', class: 'retrieve' };
|
|
18
|
+
export function createPlanetscaleApiLaneFetch(options = {}) {
|
|
19
|
+
const scope = { ...(options.root !== undefined ? { root: options.root } : {}), ...(options.clock ? { clock: options.clock } : {}) };
|
|
20
|
+
const derived = createDerivedFetch({
|
|
21
|
+
surface: surface,
|
|
22
|
+
handlers: bindSemantics(manifest, apiSemantics, scope),
|
|
23
|
+
core: coreFor(manifest, scope),
|
|
24
|
+
around: crossCutting(manifest, { readOnly: options.readOnly ?? false, ...scope }),
|
|
25
|
+
gap: () => Response.json({ code: 'not_found', message: 'Not Found' }, { status: 404 }),
|
|
26
|
+
});
|
|
27
|
+
const routes = compileSurface(surface);
|
|
28
|
+
const contextFor = (request) => semanticsContext(manifest, request, SCREEN, scope);
|
|
29
|
+
return Object.assign(async (request) => {
|
|
30
|
+
// what the World clock has made due (a database turning ready, a backup running and completing) happens first, so
|
|
31
|
+
// every door reads the lane as it stands now (semantics/time.ts)
|
|
32
|
+
if (!options.readOnly)
|
|
33
|
+
await catchUp(await contextFor(new Request(request.url)));
|
|
34
|
+
// the organization's settings page, where a person makes a token and grants its accesses (screens/service-tokens.tsx)
|
|
35
|
+
const screen = (await sessionFlow(request.clone(), await contextFor(new Request(request.url))))
|
|
36
|
+
?? (await serviceTokensPage(request.clone(), contextFor)) ?? (await deployRequestPage(request, contextFor));
|
|
37
|
+
if (screen)
|
|
38
|
+
return screen;
|
|
39
|
+
const url = new URL(request.url);
|
|
40
|
+
const matched = matchOperation(routes, request.method, url.pathname, url.searchParams, request.headers);
|
|
41
|
+
const ctx = await semanticsContext(manifest, new Request(request.url, { headers: request.headers }), matched?.operation ?? GATE, scope);
|
|
42
|
+
const call = { ...ctx.call, params: matched?.params ?? {} };
|
|
43
|
+
return tokenGate({ ...ctx, call }, request, matched?.operation.id) ?? derived(request);
|
|
44
|
+
}, {
|
|
45
|
+
owners: () => derived.owners(),
|
|
46
|
+
/** What the World clock has made due, caught up for a request another wire of the pack answers (psdb's Execute reads
|
|
47
|
+
* the branch a deploy has just changed): a move time makes happens when any request next arrives. */
|
|
48
|
+
catchUp: async (request) => { if (!options.readOnly)
|
|
49
|
+
await catchUp(await contextFor(new Request(request.url))); },
|
|
50
|
+
});
|
|
51
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// The management API lane of the planetscale pack (`api.planetscale.com/v1`), the lane's own dispatch: the pack's router
|
|
2
|
+
// (../../src/planetscale-server.ts) sends every `/v1/organizations` request here, and the organization's service tokens
|
|
3
|
+
// page (`app.planetscale.com/{organization}/settings/service-tokens`, screens/). The generated surface is PlanetScale's
|
|
4
|
+
// own Swagger document; the service-token gate sits in front of the dispatch (token-gate.ts), then the kernel's
|
|
5
|
+
// cross-cutting checks (read-only, a malformed body), then the handlers (semantics/). An operation this lane does not
|
|
6
|
+
// serve answers PlanetScale's 404: the gap.
|
|
7
|
+
import { bindSemantics, compileSurface, coreFor, createDerivedFetch, crossCutting, matchOperation, semanticsContext, type DerivedFetch, type DerivedOperation, type DerivedSurface } from '@volter/world-core';
|
|
8
|
+
import surface from './generated/surface.gen.json' with { type: 'json' };
|
|
9
|
+
import { manifest } from './manifest.ts';
|
|
10
|
+
import { apiSemantics } from './semantics/index.ts';
|
|
11
|
+
import { catchUp } from './semantics/time.ts';
|
|
12
|
+
import { tokenGate } from './token-gate.ts';
|
|
13
|
+
import { serviceTokensPage } from './screens/service-tokens.tsx';
|
|
14
|
+
import { sessionFlow } from './screens/session.tsx';
|
|
15
|
+
import { deployRequestPage } from './screens/deploy-request.tsx';
|
|
16
|
+
|
|
17
|
+
const SCREEN: DerivedOperation = { id: 'ServiceTokensPage', method: 'POST', path: '/{organization}/settings/service-tokens', class: 'action' };
|
|
18
|
+
const GATE: DerivedOperation = { id: 'TwinTokenGate', method: 'GET', path: '/v1/organizations', class: 'retrieve' };
|
|
19
|
+
|
|
20
|
+
export function createPlanetscaleApiLaneFetch(options: { root?: string; readOnly?: boolean; clock?: () => string } = {}): DerivedFetch & { catchUp: (request: Request) => Promise<void> } {
|
|
21
|
+
const scope = { ...(options.root !== undefined ? { root: options.root } : {}), ...(options.clock ? { clock: options.clock } : {}) };
|
|
22
|
+
const derived = createDerivedFetch({
|
|
23
|
+
surface: surface as DerivedSurface,
|
|
24
|
+
handlers: bindSemantics(manifest, apiSemantics, scope),
|
|
25
|
+
core: coreFor(manifest, scope),
|
|
26
|
+
around: crossCutting(manifest, { readOnly: options.readOnly ?? false, ...scope }),
|
|
27
|
+
gap: () => Response.json({ code: 'not_found', message: 'Not Found' }, { status: 404 }),
|
|
28
|
+
});
|
|
29
|
+
const routes = compileSurface(surface as DerivedSurface);
|
|
30
|
+
const contextFor = (request: Request) => semanticsContext(manifest, request, SCREEN, scope);
|
|
31
|
+
return Object.assign(async (request: Request): Promise<Response> => {
|
|
32
|
+
// what the World clock has made due (a database turning ready, a backup running and completing) happens first, so
|
|
33
|
+
// every door reads the lane as it stands now (semantics/time.ts)
|
|
34
|
+
if (!options.readOnly) await catchUp(await contextFor(new Request(request.url)));
|
|
35
|
+
// the organization's settings page, where a person makes a token and grants its accesses (screens/service-tokens.tsx)
|
|
36
|
+
const screen = (await sessionFlow(request.clone(), await contextFor(new Request(request.url))))
|
|
37
|
+
?? (await serviceTokensPage(request.clone(), contextFor)) ?? (await deployRequestPage(request, contextFor));
|
|
38
|
+
if (screen) return screen;
|
|
39
|
+
const url = new URL(request.url);
|
|
40
|
+
const matched = matchOperation(routes, request.method, url.pathname, url.searchParams, request.headers);
|
|
41
|
+
const ctx = await semanticsContext(manifest, new Request(request.url, { headers: request.headers }), matched?.operation ?? GATE, scope);
|
|
42
|
+
const call = { ...ctx.call, params: matched?.params ?? {} };
|
|
43
|
+
return tokenGate({ ...ctx, call }, request, matched?.operation.id) ?? derived(request);
|
|
44
|
+
}, {
|
|
45
|
+
owners: () => derived.owners(),
|
|
46
|
+
/** What the World clock has made due, caught up for a request another wire of the pack answers (psdb's Execute reads
|
|
47
|
+
* the branch a deploy has just changed): a move time makes happens when any request next arrives. */
|
|
48
|
+
catchUp: async (request: Request): Promise<void> => { if (!options.readOnly) await catchUp(await contextFor(new Request(request.url))); },
|
|
49
|
+
});
|
|
50
|
+
}
|