@volter/world-runtime 2.0.0 → 2.0.2
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/known-external-services.json +0 -8
- package/dist/src/app-url.js +1 -1
- package/dist/src/branch.js +79 -2
- package/dist/src/catalog.js +1 -1
- package/dist/src/cli.js +50 -22
- package/dist/src/console-apart.d.ts +1 -0
- package/dist/src/console-apart.js +7 -0
- package/dist/src/covers.js +4 -2
- package/dist/src/fixture-env.d.ts +3 -0
- package/dist/src/fixture-env.js +24 -0
- package/dist/src/host-worker.js +2 -9
- package/dist/src/host.js +2 -9
- package/dist/src/import-module.d.ts +1 -0
- package/dist/src/import-module.js +16 -0
- package/dist/src/index.d.ts +7 -2
- package/dist/src/index.js +4 -1
- package/dist/src/infra-cli.js +61 -12
- package/dist/src/init.d.ts +1 -1
- package/dist/src/init.js +34 -5
- package/dist/src/local-branches.d.ts +37 -0
- package/dist/src/local-branches.js +193 -0
- package/dist/src/pglite-backing.d.ts +8 -0
- package/dist/src/pglite-backing.js +121 -35
- package/dist/src/pglite-host.mjs +520 -14
- package/dist/src/prerequisites.js +1 -1
- package/dist/src/process-groups.js +1 -1
- package/dist/src/redirect-proxy.d.ts +1 -1
- package/dist/src/redirect-proxy.js +4 -4
- package/dist/src/redis-backing.d.ts +8 -0
- package/dist/src/redis-backing.js +120 -0
- package/dist/src/root.d.ts +23 -0
- package/dist/src/root.js +22 -10
- package/dist/src/run-task.js +1 -1
- package/dist/src/runtime.d.ts +1 -0
- package/dist/src/runtime.js +37 -20
- package/dist/src/schema.d.ts +5 -0
- package/dist/src/schema.js +10 -1
- package/dist/src/served-world.d.ts +196 -9
- package/dist/src/served-world.js +847 -103
- package/dist/src/service-recorder.js +1 -1
- package/dist/src/storage-capacity.js +2 -2
- package/dist/src/up-task.js +1 -1
- package/dist/src/world-origins.d.ts +13 -0
- package/dist/src/world-origins.js +37 -0
- package/dist/src/world-view.d.ts +25 -0
- package/dist/src/world-view.js +110 -0
- package/known-external-services.json +0 -8
- package/package.json +10 -4
- package/src/app-url.ts +1 -1
- package/src/branch.ts +66 -2
- package/src/catalog.ts +1 -1
- package/src/cli.ts +43 -21
- package/src/console-apart.ts +7 -1
- package/src/covers.ts +4 -2
- package/src/fixture-env.ts +25 -0
- package/src/host-worker.ts +2 -1
- package/src/host.ts +2 -1
- package/src/import-module.ts +9 -0
- package/src/index.ts +7 -2
- package/src/infra-cli.ts +56 -12
- package/src/init.ts +34 -5
- package/src/local-branches.ts +173 -0
- package/src/pglite-backing.ts +112 -36
- package/src/pglite-host.mjs +520 -14
- package/src/prerequisites.ts +1 -1
- package/src/process-groups.ts +1 -1
- package/src/redirect-proxy.ts +4 -4
- package/src/redis-backing.ts +107 -0
- package/src/root.ts +27 -2
- package/src/run-task.ts +1 -1
- package/src/runtime.ts +38 -20
- package/src/schema.ts +11 -1
- package/src/served-world.ts +762 -85
- package/src/service-recorder.ts +1 -1
- package/src/storage-capacity.ts +2 -2
- package/src/up-task.ts +1 -1
- package/src/world-origins.ts +38 -0
- package/src/world-view.ts +100 -0
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type HistoryReference } from '@volter/world-core';
|
|
2
2
|
import { type TwinStreamConnection, type TwinStreamSink } from '@volter/world-core';
|
|
3
|
+
import { type TrustedIssuer } from '@volter/world-access';
|
|
3
4
|
import { type Changeset, type Receipt } from '@volter/world-core';
|
|
4
5
|
import { rootForControlRoot } from './root.js';
|
|
5
6
|
import type { WorldInstance } from './schema.js';
|
|
@@ -29,6 +30,11 @@ export type ServedWorld = {
|
|
|
29
30
|
console: string | null;
|
|
30
31
|
stop: () => Promise<void>;
|
|
31
32
|
};
|
|
33
|
+
/** Where a World keeps its browser sessions (WorldDoors): a host that rotates the tokens without the doors at hand
|
|
34
|
+
* clears it itself. */
|
|
35
|
+
export declare function sessionsFile(worldRoot: string): string;
|
|
36
|
+
/** Where a World keeps its named keys (their hashes, never a key): `.volter/keys.json`. */
|
|
37
|
+
export declare function keysFile(worldRoot: string): string;
|
|
32
38
|
/** The served name: `bare.name` (`acme/team`), else `<id>/<id>`. */
|
|
33
39
|
export declare function servedName(worldRoot: string, configRef: string): string;
|
|
34
40
|
/** A world MOUNTED for serving: its doors, its tokens, and a boot that writes the serve record once the
|
|
@@ -53,7 +59,11 @@ export type MountedWorld = {
|
|
|
53
59
|
stop: () => Promise<void>;
|
|
54
60
|
};
|
|
55
61
|
export declare function mountWorld(name: string, opts?: {
|
|
56
|
-
|
|
62
|
+
passIssuers?: TrustedIssuer[];
|
|
63
|
+
root?: string; /** the host's branches of this World, by its served name */
|
|
64
|
+
branches?: (served: string) => BranchDoors | undefined; /** the origin a browser reaches this World at, when the host gives each World one */
|
|
65
|
+
browserOrigin?: (served: string) => string | null; /** served on this machine's loopback: its own pages need no token (DoorHost.localTrust) */
|
|
66
|
+
localTrust?: boolean;
|
|
57
67
|
}): Promise<MountedWorld>;
|
|
58
68
|
/** Boot the world (state kept) and serve it on its own port. Announces and returns after boot; `stop` downs the world. */
|
|
59
69
|
export declare function serveWorld(name: string, opts?: {
|
|
@@ -99,7 +109,78 @@ export type DoorHost = {
|
|
|
99
109
|
/** Where this host reads a twin's scenario (the handlers document a generative twin serves from,
|
|
100
110
|
* re-read on every request), or null when the twin takes none here. */
|
|
101
111
|
scenarioPath?: (vendor: string) => string | null;
|
|
112
|
+
/** A twin's mirror as its pack builds it: `index.html` (the shell) or `assets/<file>`, or null when
|
|
113
|
+
* the twin has none here. Every host answers the same mount (`/<served>/<vendor>/mirror/`); only
|
|
114
|
+
* where the files come from differs (built at serve under Bun, prebuilt under Node, static assets
|
|
115
|
+
* on Cloudflare). */
|
|
116
|
+
mirror?: (vendor: string, file: string) => Promise<Response | null>;
|
|
117
|
+
/** Whether the twin has a mirror on this host, without building it. */
|
|
118
|
+
hasMirror?: (vendor: string) => boolean | Promise<boolean>;
|
|
119
|
+
/** The account and token a twin's screens open signed in as, resolved by its pack from the twin's state at `root`
|
|
120
|
+
* for the account the World's config names (`signIn.as`); null when the pack cannot, or has no such account. */
|
|
121
|
+
mirrorSignIn?: (vendor: string, as: string, ctx: {
|
|
122
|
+
root: string;
|
|
123
|
+
twin: (path: string, init?: RequestInit) => Promise<Response>;
|
|
124
|
+
}) => Promise<{
|
|
125
|
+
account: string;
|
|
126
|
+
token: string;
|
|
127
|
+
} | null>;
|
|
128
|
+
/** Where this World's clock lives in its store (the file every twin reads `worldNow()` from). */
|
|
129
|
+
clockPath?: () => string;
|
|
130
|
+
/** The origin a browser reaches this World at (`http://<world>--<org>.localhost:<port>`), when the
|
|
131
|
+
* host gives each World one: its session lives only there (docs/contributing/architecture.md,
|
|
132
|
+
* "Viewing a World"). Null, or absent, on a host whose Worlds share one origin. */
|
|
133
|
+
browserOrigin?: () => string | null;
|
|
134
|
+
/** The origin another World this host serves (a branch of this one) is reached at, where it has one of its own. */
|
|
135
|
+
originOf?: (served: string) => string | null;
|
|
136
|
+
/** This World is served on this machine's loopback, for the person at it: a page of this World, reached by a
|
|
137
|
+
* loopback name, is given the World's session without a token (`localSession`). A remote host leaves it absent,
|
|
138
|
+
* and its pages sign in with a token. */
|
|
139
|
+
localTrust?: () => boolean;
|
|
140
|
+
/** The platforms whose passes open this World (docs/contributing/architecture.md, "The hosted product"): a person a
|
|
141
|
+
* trusted platform signed a pass for is given a session of the pass's scope. Absent, or empty, no pass opens it. */
|
|
142
|
+
passIssuers?: () => TrustedIssuer[];
|
|
143
|
+
/** This World's branches, where the host can make them (a branch is another World, so making one
|
|
144
|
+
* is the host's act); absent, the branches doors answer 404. */
|
|
145
|
+
branches?: BranchDoors;
|
|
146
|
+
};
|
|
147
|
+
/** A branch as its parent lists it. */
|
|
148
|
+
export type BranchRow = {
|
|
149
|
+
name: string;
|
|
150
|
+
from: string;
|
|
151
|
+
at: {
|
|
152
|
+
instant?: string;
|
|
153
|
+
live?: boolean;
|
|
154
|
+
label?: string;
|
|
155
|
+
} | null;
|
|
156
|
+
createdAt: string;
|
|
157
|
+
expiresAt: string | null;
|
|
102
158
|
};
|
|
159
|
+
/** What a host does for a World's branches doors. `create` makes a World cloned from this one at
|
|
160
|
+
* `at` (its history cut at that instant), removed when `ttlSeconds` runs out; `origin` is where the
|
|
161
|
+
* request reached this World (a host whose branches clone over its public URL uses it). */
|
|
162
|
+
export type BranchDoors = {
|
|
163
|
+
list: () => Promise<BranchRow[]>;
|
|
164
|
+
/** `parentKey`: the key the branch reaches this World with (never this World's own token), made for whoever asked */
|
|
165
|
+
create: (at: {
|
|
166
|
+
instant?: string;
|
|
167
|
+
live?: boolean;
|
|
168
|
+
label?: string;
|
|
169
|
+
}, ttlSeconds: number | null, origin: string, parentKey?: string) => Promise<{
|
|
170
|
+
name: string;
|
|
171
|
+
token: string;
|
|
172
|
+
readToken: string;
|
|
173
|
+
expiresAt: string | null;
|
|
174
|
+
}>;
|
|
175
|
+
remove: (name: string) => Promise<boolean>;
|
|
176
|
+
};
|
|
177
|
+
/** A World's browser-origin label, `<world>--<org>`, or null when its names cannot make one (a DNS
|
|
178
|
+
* label: lowercase letters, digits and single hyphens, 63 at most). A host maps a label back by the
|
|
179
|
+
* Worlds it serves, never by parsing it. */
|
|
180
|
+
export declare function worldOriginLabel(served: string): string | null;
|
|
181
|
+
/** The header the doors set on a read-scope request to a twin: the twin refuses the caller's writes
|
|
182
|
+
* and still makes the vendor's own moves. It only restricts, so a twin trusts it without a token. */
|
|
183
|
+
export declare const READ_ONLY_HEADER = "x-volter-read-only";
|
|
103
184
|
export declare class WorldDoors {
|
|
104
185
|
readonly name: string;
|
|
105
186
|
readonly worldRoot: string;
|
|
@@ -112,6 +193,46 @@ export declare class WorldDoors {
|
|
|
112
193
|
constructor(name: string, worldRoot: string, configRef: string, served: string, host: DoorHost, token: string, readToken: string);
|
|
113
194
|
/** New tokens; a request presenting the old ones is refused from the next call on. */
|
|
114
195
|
retoken(token: string, readToken: string): void;
|
|
196
|
+
/** BROWSER SESSIONS: the session cookie carries an opaque id, never a token, so a leaked cookie is one session and
|
|
197
|
+
* not the World's write token; rotating the tokens ends every session. Kept in the World's store, so a restart
|
|
198
|
+
* keeps them; each lasts 30 days from its opening. */
|
|
199
|
+
private sessionsPath;
|
|
200
|
+
private held;
|
|
201
|
+
private sessions;
|
|
202
|
+
private keepSessions;
|
|
203
|
+
/** A new session of `scope`: its id, the cookie's value. A token's lasts 30 days; a person's from a pass, `who`,
|
|
204
|
+
* lasts as long as the pass's platform says (twelve hours), so a person removed there loses it within the day. */
|
|
205
|
+
private openSession;
|
|
206
|
+
/** NAMED KEYS: what an app or a script holds (docs/contributing/architecture.md, "The hosted product"), each named
|
|
207
|
+
* when made, shown once, kept only as its hash, and revoked alone; the World's two tokens stay its first keys. */
|
|
208
|
+
private heldKeys;
|
|
209
|
+
private keys;
|
|
210
|
+
private keepKeys;
|
|
211
|
+
/** When a key was last used, to the minute (a key in constant use is not a write per request). */
|
|
212
|
+
private touchKey;
|
|
213
|
+
/** `GET|POST /-/<world>/keys`, `DELETE /-/<world>/keys/<id>`, `DELETE /-/<world>/keys?person=<sub>`: named keys are
|
|
214
|
+
* managed with write access by the World's token or a person's session, never by a key (a key cannot make a key
|
|
215
|
+
* that outlives it). Each records who made it and whom it is for; the token may give one an end and a person, and
|
|
216
|
+
* revoke every key made by or for a person (their leaving the org). */
|
|
217
|
+
/** The person a key is for: whom it was made for, else the person whose session made it. */
|
|
218
|
+
private keyPerson;
|
|
219
|
+
/** Revoke the keys `pick` names, and what they reach: the branches whose parent key goes, and the branches (with their
|
|
220
|
+
* keys) a revoked key made. Answers the revoked keys' ids. */
|
|
221
|
+
private revokeKeys;
|
|
222
|
+
private keysDoor;
|
|
223
|
+
/** Passes this World has already spent: each opens one session. */
|
|
224
|
+
private readonly spent;
|
|
225
|
+
/** A PASS opens a session: a person a trusted platform signed a pass for, into this World, at its own origin, asked
|
|
226
|
+
* from its own page (the console reads the pass from its address's fragment and posts it here). */
|
|
227
|
+
/** The org its host records as this World's owner (`<state>/owner`, written at its making or its claim), or null. */
|
|
228
|
+
private recordedOwner;
|
|
229
|
+
private passSession;
|
|
230
|
+
/** A named key that still opens this World: not revoked, not expired. */
|
|
231
|
+
private liveKey;
|
|
232
|
+
/** The named key a request presents, with the end of its life. */
|
|
233
|
+
private presentedKey;
|
|
234
|
+
private endSession;
|
|
235
|
+
private endSessions;
|
|
115
236
|
/** The world's twins, by vendor, once booted. */
|
|
116
237
|
twins(): string[];
|
|
117
238
|
private up;
|
|
@@ -119,8 +240,29 @@ export declare class WorldDoors {
|
|
|
119
240
|
* the injector's `x-twins-key` (a zero-edit app keeps its SDK's own Authorization), `Bearer <token>`,
|
|
120
241
|
* GitHub's `token <token>`, or the password half of `Basic` (Jira's email:token). */
|
|
121
242
|
private scope;
|
|
122
|
-
/** The
|
|
123
|
-
|
|
243
|
+
/** The request's scope, whether it came by a token the caller presented or by a browser's session
|
|
244
|
+
* (the cookie `POST …/session` set), and the token that opened it. Every form presented is a
|
|
245
|
+
* candidate: a mirror page sends the VENDOR's bearer (the token its sign-in holds) beside the World's
|
|
246
|
+
* session cookie, and the cookie is what opens the World. With a browser origin of its own
|
|
247
|
+
* (`browserOrigin`), a World honours its session only on that origin: a session cookie on any other
|
|
248
|
+
* (a host's shared origin, path-addressed) opens nothing. */
|
|
249
|
+
/** What this World issued, for world-access to decide a grant against: its two tokens, its named keys, its sessions. */
|
|
250
|
+
private issued;
|
|
251
|
+
private credential;
|
|
252
|
+
/** The session cookie's name where `url` reached this World, or null where a session opens nothing:
|
|
253
|
+
* on the World's own origin a host-only cookie (`__Host-` over https); without an origin of its own,
|
|
254
|
+
* the path-scoped cookie a shared origin has always carried. */
|
|
255
|
+
private sessionCookieName;
|
|
256
|
+
/** The session cookie for `url`, set to `value`: host-only over the whole origin on the World's own
|
|
257
|
+
* origin (a `__Host-` cookie must be), scoped to the World's paths on a shared one. Null where a
|
|
258
|
+
* session opens nothing (another World's origin). */
|
|
259
|
+
private sessionCookie;
|
|
260
|
+
/** A browser's session opens the World only to the World's own pages: a request the session cookie carries must
|
|
261
|
+
* say `Sec-Fetch-Site: same-origin` (a browser sets it; a page cannot), or be a read the person navigated to
|
|
262
|
+
* (`none`: a typed or bookmarked address). Another page of the same site (a sibling World's origin shares its
|
|
263
|
+
* suffix) is refused its reads too, so a twin that echoes the caller's Origin with credentials cannot hand one
|
|
264
|
+
* World's data to another World's page; a request with no header fails closed. Null when it may pass. */
|
|
265
|
+
private crossOriginSession;
|
|
124
266
|
/** Open one connection on a byte-stream door for the holder of the World's (write) token; null when
|
|
125
267
|
* the token is not the World's or the World has no such stream. */
|
|
126
268
|
openStream(token: string, id: string, sink: TwinStreamSink, peer: string): TwinStreamConnection | null;
|
|
@@ -129,14 +271,24 @@ export declare class WorldDoors {
|
|
|
129
271
|
private controlRoot;
|
|
130
272
|
private stateOf;
|
|
131
273
|
handle(request: Request): Promise<Response>;
|
|
274
|
+
/** THE MIRROR MOUNT: `/<served>/<vendor>/mirror/` and `…/mirror/assets/<file>`, keyless and GET only —
|
|
275
|
+
* the pack's own shell and client. The shell's <base> is the twin's place in this World, so the
|
|
276
|
+
* mirror reads and writes the World's wire with the browser's session (`POST …/session`); any
|
|
277
|
+
* other path under mirror/ is one of the mirror's own routes (a reloaded deep link) and gets the
|
|
278
|
+
* shell. A link is not a twin and has no mirror. */
|
|
279
|
+
private mirror;
|
|
280
|
+
/** A vendor's screens' sign-in (`signIn.as`), once per account every twelve hours while this World is served (rotating the
|
|
281
|
+
* tokens forgets them): the pack finds the account and mints its credential through the twin's own doors; a failure
|
|
282
|
+
* is not kept, so the next page asks again. An account or credential that is not plain printable text is refused. */
|
|
283
|
+
private readonly signIns;
|
|
284
|
+
private readonly signInAt;
|
|
285
|
+
private signedIn;
|
|
286
|
+
/** Whether a twin enforces a read-scope request itself (its manifest names `requestScopes: ['read']`):
|
|
287
|
+
* only then may the read token reach it with anything but GET or HEAD. Asked once per twin. */
|
|
288
|
+
private readonly readEnforced;
|
|
289
|
+
private enforcesRead;
|
|
132
290
|
/** The vendor wire: `/<org>/<world>/<vendor>/<rest>` → the twin's own URL. */
|
|
133
291
|
private wire;
|
|
134
|
-
/** Whether the twin's pack ships a mirror (its vendor's UI over this World's state). */
|
|
135
|
-
private hasMirror;
|
|
136
|
-
/** THE MIRROR MOUNT, as the hosted World serves it (apps/cloud supervisor.ts): `/<org>/<world>/<vendor>/mirror/`
|
|
137
|
-
* and `…/mirror/assets/*`, keyless, GET only — the pack's own shell and bundle. The shell's <base> is the
|
|
138
|
-
* twin's place under this World, so its reads go to the keyed wire with the browser's World session. */
|
|
139
|
-
private mirror;
|
|
140
292
|
private linksPath;
|
|
141
293
|
/** The World's links: name → the vendor origin it forwards to. */
|
|
142
294
|
links(): Record<string, {
|
|
@@ -151,6 +303,41 @@ export declare class WorldDoors {
|
|
|
151
303
|
private observedAt;
|
|
152
304
|
/** The world's own doors under `/-/<org>/<world>/`. */
|
|
153
305
|
private door;
|
|
306
|
+
/** A browser session for this World: the presented token as an HttpOnly cookie scoped to the World's
|
|
307
|
+
* paths, so a page served under it (a twin's mirror) reads the wire as the token's holder, in the
|
|
308
|
+
* token's scope. */
|
|
309
|
+
/** THE LOCAL SESSION: a World served on this machine's loopback never asks the person at it for a token. Any
|
|
310
|
+
* website can send requests to 127.0.0.1, so what stands in for the token is proof the request is this World's own
|
|
311
|
+
* page, as Vite and Jupyter check their dev servers: the Host is a loopback name (a rebound DNS name is not, so DNS
|
|
312
|
+
* rebinding fails), the browser's Origin is exactly this origin (another site's page cannot claim it, so CSRF
|
|
313
|
+
* fails), and the fetch says it is same-origin. A process on this machine can claim all three: a local World
|
|
314
|
+
* trusts this machine's user, as a local dev server does. The session is the write token's. A World with no origin
|
|
315
|
+
* of its own (a shared origin, where every World's pages are one origin) is not trusted so: its page asks for a
|
|
316
|
+
* token. */
|
|
317
|
+
private localPage;
|
|
318
|
+
private localSession;
|
|
319
|
+
private session;
|
|
320
|
+
/** THE CLOCK: the frozen instant every twin stamps from, kept in the World's store. It moves only
|
|
321
|
+
* forward once the World has entries (a twin's catch-up stamps each move at its due time, so a
|
|
322
|
+
* clock set back would put new entries before old ones); the past is reached by branching. */
|
|
323
|
+
private clockDoor;
|
|
324
|
+
/** THE BRANCHES DOORS: list (read), make one as of an instant, remove one (write). The host makes
|
|
325
|
+
* and removes them; a host that makes none answers 404, and a view offers no "as of". */
|
|
326
|
+
private branchesDoor;
|
|
327
|
+
/** The newest entry's instant across the World's twins (ms), or null when it has none. */
|
|
328
|
+
private newestEntry;
|
|
329
|
+
/** THE MAP: every twin and what it holds, by resource type, and whether it has a mirror. */
|
|
330
|
+
private map;
|
|
331
|
+
/** THE TIMELINE: the entries of every twin's log merged, newest first, by each entry's instant, then
|
|
332
|
+
* twin, then position. The World keeps no order across twins finer than its clock, and this claims
|
|
333
|
+
* none. `before` is the cursor the previous page answered; `twin` and `trace` narrow it. */
|
|
334
|
+
private timeline;
|
|
335
|
+
/** Each twin's timeline rows, kept until its logs change: a poll of an unchanged World reads no log
|
|
336
|
+
* (a twin is read again only when the size or time of its parent log, branch log or branch record
|
|
337
|
+
* moved), and the merged order is sorted again only when a twin was. */
|
|
338
|
+
private readonly timelineByTwin;
|
|
339
|
+
private timelineMerged;
|
|
340
|
+
private timelineRows;
|
|
154
341
|
/** PUSH: see `landChangeset` — the door hands the body to it. */
|
|
155
342
|
private push;
|
|
156
343
|
}
|