@volter/world-core 2.0.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 +29 -0
- package/app-route.cjs +154 -0
- package/app-route.d.cts +7 -0
- package/attach.cjs +80 -0
- package/dist/app-route.cjs +154 -0
- package/dist/app-route.d.cts +7 -0
- package/dist/attach.cjs +80 -0
- package/dist/generated/pack-facts.json +4306 -0
- package/dist/inject.cjs +1097 -0
- package/dist/network-policy.cjs +92 -0
- package/dist/network-policy.d.cts +10 -0
- package/dist/src/actions.d.ts +276 -0
- package/dist/src/actions.js +436 -0
- package/dist/src/ancestry.d.ts +22 -0
- package/dist/src/ancestry.js +238 -0
- package/dist/src/args.d.ts +3 -0
- package/dist/src/args.js +12 -0
- package/dist/src/blob-store.d.ts +55 -0
- package/dist/src/blob-store.js +186 -0
- package/dist/src/brand-tokens.d.ts +2 -0
- package/dist/src/brand-tokens.js +17 -0
- package/dist/src/changeset.d.ts +431 -0
- package/dist/src/changeset.js +0 -0
- package/dist/src/client-bundle.d.ts +1 -0
- package/dist/src/client-bundle.js +28 -0
- package/dist/src/credential.d.ts +38 -0
- package/dist/src/credential.js +114 -0
- package/dist/src/derived-core.d.ts +452 -0
- package/dist/src/derived-core.js +782 -0
- package/dist/src/derived.d.ts +84 -0
- package/dist/src/derived.js +122 -0
- package/dist/src/emit.d.ts +106 -0
- package/dist/src/emit.js +157 -0
- package/dist/src/executor.d.ts +120 -0
- package/dist/src/executor.js +387 -0
- package/dist/src/file-response.d.ts +3 -0
- package/dist/src/file-response.js +22 -0
- package/dist/src/fork.d.ts +26 -0
- package/dist/src/fork.js +68 -0
- package/dist/src/git/history.d.ts +36 -0
- package/dist/src/git/history.js +298 -0
- package/dist/src/git/index.d.ts +6 -0
- package/dist/src/git/index.js +6 -0
- package/dist/src/git/inflate.d.ts +11 -0
- package/dist/src/git/inflate.js +194 -0
- package/dist/src/git/objects.d.ts +64 -0
- package/dist/src/git/objects.js +161 -0
- package/dist/src/git/pack.d.ts +14 -0
- package/dist/src/git/pack.js +199 -0
- package/dist/src/git/refs.d.ts +19 -0
- package/dist/src/git/refs.js +35 -0
- package/dist/src/git/smart-http.d.ts +45 -0
- package/dist/src/git/smart-http.js +223 -0
- package/dist/src/hash.d.ts +38 -0
- package/dist/src/hash.js +48 -0
- package/dist/src/head.d.ts +140 -0
- package/dist/src/head.js +313 -0
- package/dist/src/history.d.ts +76 -0
- package/dist/src/history.js +322 -0
- package/dist/src/index.d.ts +73 -0
- package/dist/src/index.js +98 -0
- package/dist/src/lifecycle.d.ts +1 -0
- package/dist/src/lifecycle.js +8 -0
- package/dist/src/log.d.ts +254 -0
- package/dist/src/log.js +801 -0
- package/dist/src/mirror-shell.d.ts +2 -0
- package/dist/src/mirror-shell.js +13 -0
- package/dist/src/observe.d.ts +49 -0
- package/dist/src/observe.js +148 -0
- package/dist/src/pack-assets.d.ts +30 -0
- package/dist/src/pack-assets.js +88 -0
- package/dist/src/packRegistry.d.ts +374 -0
- package/dist/src/packRegistry.js +142 -0
- package/dist/src/placeholder-remote.d.ts +22 -0
- package/dist/src/placeholder-remote.js +86 -0
- package/dist/src/proxy.d.ts +25 -0
- package/dist/src/proxy.js +155 -0
- package/dist/src/rateBudget.d.ts +367 -0
- package/dist/src/rateBudget.js +925 -0
- package/dist/src/references.d.ts +18 -0
- package/dist/src/references.js +27 -0
- package/dist/src/remote-execute.d.ts +22 -0
- package/dist/src/remote-execute.js +1 -0
- package/dist/src/resource-blob.d.ts +10 -0
- package/dist/src/resource-blob.js +56 -0
- package/dist/src/scenario.d.ts +197 -0
- package/dist/src/scenario.js +425 -0
- package/dist/src/schemas.d.ts +78 -0
- package/dist/src/schemas.js +50 -0
- package/dist/src/serve-http.d.ts +48 -0
- package/dist/src/serve-http.js +340 -0
- package/dist/src/serve.d.ts +147 -0
- package/dist/src/serve.js +507 -0
- package/dist/src/shared-blob-index.d.ts +4 -0
- package/dist/src/shared-blob-index.js +126 -0
- package/dist/src/state-system.d.ts +70 -0
- package/dist/src/state-system.js +90 -0
- package/dist/src/storage.d.ts +101 -0
- package/dist/src/storage.js +337 -0
- package/dist/src/twin-fetch.d.ts +64 -0
- package/dist/src/twin-fetch.js +91 -0
- package/dist/src/types.d.ts +40 -0
- package/dist/src/types.js +1 -0
- package/dist/src/v1-removed.d.ts +159 -0
- package/dist/src/v1-removed.js +124 -0
- package/dist/src/volter-home.d.ts +5 -0
- package/dist/src/volter-home.js +10 -0
- package/dist/src/world-clock.d.ts +4 -0
- package/dist/src/world-clock.js +32 -0
- package/dist/src/world-env.d.ts +3 -0
- package/dist/src/world-env.js +22 -0
- package/dist/src/world-store-sql.d.ts +27 -0
- package/dist/src/world-store-sql.js +86 -0
- package/dist/src/world-store.d.ts +168 -0
- package/dist/src/world-store.js +475 -0
- package/dist/src/worldConfig.d.ts +9 -0
- package/dist/src/worldConfig.js +17 -0
- package/dist/stream-bridge.cjs +80 -0
- package/dist/vendor-hosts.cjs +200 -0
- package/generated/pack-facts.json +4306 -0
- package/inject.cjs +1097 -0
- package/network-policy.cjs +92 -0
- package/network-policy.d.cts +10 -0
- package/package.json +103 -0
- package/src/actions.ts +564 -0
- package/src/ancestry.ts +213 -0
- package/src/args.ts +14 -0
- package/src/blob-store.ts +185 -0
- package/src/brand-tokens.ts +17 -0
- package/src/changeset.ts +1032 -0
- package/src/client-bundle.ts +29 -0
- package/src/credential.ts +140 -0
- package/src/derived-core.ts +1004 -0
- package/src/derived.ts +176 -0
- package/src/emit.ts +242 -0
- package/src/executor.ts +431 -0
- package/src/file-response.ts +22 -0
- package/src/fork.ts +89 -0
- package/src/git/history.ts +177 -0
- package/src/git/index.ts +6 -0
- package/src/git/inflate.ts +125 -0
- package/src/git/objects.ts +110 -0
- package/src/git/pack.ts +105 -0
- package/src/git/refs.ts +25 -0
- package/src/git/smart-http.ts +149 -0
- package/src/hash.ts +66 -0
- package/src/head.ts +318 -0
- package/src/history.ts +246 -0
- package/src/index.ts +323 -0
- package/src/lifecycle.ts +8 -0
- package/src/log.ts +793 -0
- package/src/mirror-shell.ts +15 -0
- package/src/observe.ts +130 -0
- package/src/pack-assets.ts +81 -0
- package/src/packRegistry.ts +408 -0
- package/src/placeholder-remote.ts +81 -0
- package/src/proxy.ts +183 -0
- package/src/rateBudget.ts +1115 -0
- package/src/references.ts +46 -0
- package/src/remote-execute.ts +26 -0
- package/src/resource-blob.ts +57 -0
- package/src/scenario.ts +479 -0
- package/src/schemas.ts +56 -0
- package/src/serve-http.ts +299 -0
- package/src/serve.ts +618 -0
- package/src/shared-blob-index.ts +108 -0
- package/src/state-system.ts +115 -0
- package/src/storage.ts +407 -0
- package/src/twin-fetch.ts +147 -0
- package/src/types.ts +50 -0
- package/src/v1-removed.ts +172 -0
- package/src/volter-home.ts +11 -0
- package/src/world-clock.ts +33 -0
- package/src/world-env.ts +18 -0
- package/src/world-store-sql.ts +118 -0
- package/src/world-store.ts +572 -0
- package/src/worldConfig.ts +27 -0
- package/stream-bridge.cjs +80 -0
- package/vendor-hosts.cjs +200 -0
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
// THE ONE HTTP ADAPTATION (runtime contract R12b): most packs' serve path is the same
|
|
2
|
+
// ~20 lines of glue — the keyless GET /twin manifest door, the body read, a header map,
|
|
3
|
+
// the dispatch into `handle<Vendor>TwinRequest` with the WORLD instant, and a JSON
|
|
4
|
+
// response. That glue is a MECHANISM, so it lives here once; a pack's differences are
|
|
5
|
+
// VALUES (its manifest, its header threading, its static handler options, its extra
|
|
6
|
+
// response headers). A pack whose wire genuinely exceeds this shape (SSE streaming,
|
|
7
|
+
// multipart adaptation, raw-bytes responses) writes its own fetch instead — openai is
|
|
8
|
+
// that reference; this adapter is the reference for everyone else.
|
|
9
|
+
//
|
|
10
|
+
// Workerd-clean by construction: no Bun APIs, no fs, no clock but worldNow() — the same
|
|
11
|
+
// closure serves under Bun.serve locally and mounted in-process on Cloudflare.
|
|
12
|
+
import { runWithCorrelationId } from "./actions.js";
|
|
13
|
+
import { worldNow } from "./world-clock.js";
|
|
14
|
+
/** The header a host sets when it mounts a twin under a path (a served World's wire:
|
|
15
|
+
* `/<org>/<world>/<vendor>`), the reverse-proxy convention; with it the host sets
|
|
16
|
+
* `x-forwarded-host` and `x-forwarded-proto` to where the World is reached. */
|
|
17
|
+
export const TWIN_PREFIX_HEADER = 'x-forwarded-prefix';
|
|
18
|
+
/** Where this twin is reached for this request: the request's origin plus the path a host mounted
|
|
19
|
+
* the twin under. A pack mints every live URL it hands back (an upload target, a file link, a
|
|
20
|
+
* callback) from this, never from the bare origin, so the URL works behind a served World. */
|
|
21
|
+
export function twinPublicBase(request) {
|
|
22
|
+
const prefix = request.headers.get(TWIN_PREFIX_HEADER);
|
|
23
|
+
if (!prefix || !/^\/[A-Za-z0-9._~\/-]*$/.test(prefix))
|
|
24
|
+
return new URL(request.url).origin;
|
|
25
|
+
// the host that mounted the twin says where it is reached: a local World forwards to the twin's
|
|
26
|
+
// own port, so the request's own origin is not the public one
|
|
27
|
+
const host = request.headers.get('x-forwarded-host');
|
|
28
|
+
const proto = request.headers.get('x-forwarded-proto');
|
|
29
|
+
const origin = host && /^[A-Za-z0-9.-]+(:\d+)?$/.test(host) && (proto === 'http' || proto === 'https') ? `${proto}://${host}` : new URL(request.url).origin;
|
|
30
|
+
return `${origin}${prefix.replace(/\/+$/, '')}`;
|
|
31
|
+
}
|
|
32
|
+
export function createTwinFetchFromHandler(handler, config) {
|
|
33
|
+
const readOnly = config.readOnly ?? false;
|
|
34
|
+
return async function twinFetch(request) {
|
|
35
|
+
const url = new URL(request.url);
|
|
36
|
+
const cleanPath = url.pathname.replace(/\/+$/, '') || '/';
|
|
37
|
+
if (request.method === 'GET' && cleanPath === '/twin') {
|
|
38
|
+
const manifest = typeof config.manifest === 'function' ? config.manifest() : config.manifest;
|
|
39
|
+
const storeNames = Object.keys(config.stores ?? {}).sort();
|
|
40
|
+
if (storeNames.length > 0 && manifest !== null && typeof manifest === 'object') {
|
|
41
|
+
const m = manifest;
|
|
42
|
+
const doors = m.doors !== null && typeof m.doors === 'object' ? m.doors : {};
|
|
43
|
+
// The manifest EDUCATES: it lists the store names and names the door's path.
|
|
44
|
+
return Response.json({ ...m, stores: storeNames, doors: { ...doors, store: 'GET /twin/store/<name>' } });
|
|
45
|
+
}
|
|
46
|
+
return Response.json(manifest);
|
|
47
|
+
}
|
|
48
|
+
if (request.method === 'GET' && cleanPath === '/twin/scenario' && config.scenarioStatus !== undefined) {
|
|
49
|
+
return Response.json(config.scenarioStatus());
|
|
50
|
+
}
|
|
51
|
+
if (request.method === 'GET' && cleanPath.startsWith('/twin/store/')) {
|
|
52
|
+
const name = cleanPath.slice('/twin/store/'.length);
|
|
53
|
+
const store = config.stores?.[name];
|
|
54
|
+
if (store === undefined)
|
|
55
|
+
return Response.json({ error: 'unknown store', store: name, stores: Object.keys(config.stores ?? {}).sort() }, { status: 404 });
|
|
56
|
+
return Response.json(await store()); // a projection may read through the handler (async) — the door awaits it
|
|
57
|
+
}
|
|
58
|
+
const body = request.method === 'GET' || request.method === 'HEAD' ? '' : await request.text();
|
|
59
|
+
const headers = {};
|
|
60
|
+
request.headers.forEach((value, key) => { headers[key] = value; });
|
|
61
|
+
// FIXED FIELDS WIN (review B1): handlerOptions/extras spread FIRST, so no config —
|
|
62
|
+
// and via extras, no WIRE INPUT — can override method/path/body/readOnly or the
|
|
63
|
+
// world instant. A client-settable occurredAt would be a direct R9 hole.
|
|
64
|
+
// D3 — the wire's request id becomes the correlation of every action this handler appends.
|
|
65
|
+
const requestId = request.headers.get('x-twins-request-id') ?? undefined;
|
|
66
|
+
const invoke = () => handler({
|
|
67
|
+
...config.handlerOptions,
|
|
68
|
+
...config.extras?.(request, url),
|
|
69
|
+
method: request.method,
|
|
70
|
+
path: url.pathname + (url.search || ''),
|
|
71
|
+
body,
|
|
72
|
+
headers,
|
|
73
|
+
readOnly,
|
|
74
|
+
occurredAt: worldNow(),
|
|
75
|
+
...(config.root !== undefined ? { root: config.root } : {}),
|
|
76
|
+
});
|
|
77
|
+
const result = await (requestId ? runWithCorrelationId(requestId, invoke) : invoke());
|
|
78
|
+
// A null/undefined body is an EMPTY reply (`JSON.stringify(null)` would serve the
|
|
79
|
+
// four bytes "null") — and so is the estate's own 204 idiom, `body: ''` with a
|
|
80
|
+
// null-body status: workerd THROWS on any body with 204/205/304 (review B2), where
|
|
81
|
+
// Bun silently serves '""' — either way a wire break on exactly one lane.
|
|
82
|
+
const nullBodyStatus = result.status === 204 || result.status === 205 || result.status === 304;
|
|
83
|
+
if (result.body === null || result.body === undefined || (nullBodyStatus && result.body === '')) {
|
|
84
|
+
return new Response(null, { status: result.status, headers: { ...config.responseHeaders, ...result.headers } });
|
|
85
|
+
}
|
|
86
|
+
return new Response(JSON.stringify(result.body), {
|
|
87
|
+
status: result.status,
|
|
88
|
+
headers: { 'content-type': 'application/json', ...config.responseHeaders, ...result.headers },
|
|
89
|
+
});
|
|
90
|
+
};
|
|
91
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { z } from 'zod';
|
|
2
|
+
import type { GenericWorldStateSchema, WorldServiceEventSchema } from './schemas.js';
|
|
3
|
+
export type WorldServiceEvent = z.infer<typeof WorldServiceEventSchema>;
|
|
4
|
+
export type GenericWorldState = z.infer<typeof GenericWorldStateSchema>;
|
|
5
|
+
export type AppendEventResult = {
|
|
6
|
+
event: WorldServiceEvent;
|
|
7
|
+
appended: boolean;
|
|
8
|
+
duplicateOf?: string;
|
|
9
|
+
};
|
|
10
|
+
export type QueuedWorldServiceEvent = {
|
|
11
|
+
id: string;
|
|
12
|
+
service: string;
|
|
13
|
+
receivedAt: string;
|
|
14
|
+
source: 'listener' | 'webhook' | 'broker' | 'capture' | 'manual' | 'replay';
|
|
15
|
+
event: WorldServiceEvent;
|
|
16
|
+
};
|
|
17
|
+
export type EnqueueEventResult = {
|
|
18
|
+
queued: QueuedWorldServiceEvent;
|
|
19
|
+
appended: boolean;
|
|
20
|
+
duplicateOf?: string;
|
|
21
|
+
};
|
|
22
|
+
export type CommitQueuedEventsResult = {
|
|
23
|
+
service: string;
|
|
24
|
+
queued: number;
|
|
25
|
+
committed: number;
|
|
26
|
+
skipped: number;
|
|
27
|
+
eventIds: string[];
|
|
28
|
+
};
|
|
29
|
+
export type WorldPaths = {
|
|
30
|
+
root: string;
|
|
31
|
+
service: string;
|
|
32
|
+
dir: string;
|
|
33
|
+
events: string;
|
|
34
|
+
state: string;
|
|
35
|
+
resources: string;
|
|
36
|
+
cursors: string;
|
|
37
|
+
ingests: string;
|
|
38
|
+
eventQueue: string;
|
|
39
|
+
};
|
|
40
|
+
export type WorldReducer<State> = (state: State, event: WorldServiceEvent) => State;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
export declare function v1Reached(): string[];
|
|
2
|
+
/** Run `fn` and learn which v1 names it reached (directly or through anything it awaited). */
|
|
3
|
+
export declare function v1ReachedIn<T>(fn: () => Promise<T>): Promise<{
|
|
4
|
+
value: T;
|
|
5
|
+
reached: string[];
|
|
6
|
+
}>;
|
|
7
|
+
export declare function appendEventLocked(..._args: any[]): any;
|
|
8
|
+
export declare function eventsLockPath(..._args: any[]): any;
|
|
9
|
+
export declare function appendPushRecord(..._args: any[]): any;
|
|
10
|
+
export declare function listPushLedger(..._args: any[]): any;
|
|
11
|
+
export declare function latestPushByActionId(..._args: any[]): any;
|
|
12
|
+
export declare function unconfirmedPushes(..._args: any[]): any;
|
|
13
|
+
export declare function assertFastForward(..._args: any[]): any;
|
|
14
|
+
export declare function pushTransaction(..._args: any[]): any;
|
|
15
|
+
export declare function abandonPush(..._args: any[]): any;
|
|
16
|
+
export declare function syncPull(..._args: any[]): any;
|
|
17
|
+
export declare function syncPush(..._args: any[]): any;
|
|
18
|
+
export declare function currentResources(..._args: any[]): any;
|
|
19
|
+
export declare function isEgressEventType(..._args: any[]): any;
|
|
20
|
+
export declare function recordWriteIntent(..._args: any[]): any;
|
|
21
|
+
export declare function performExternalWrite(..._args: any[]): any;
|
|
22
|
+
export declare function listUnreconciledWriteIntents(..._args: any[]): any;
|
|
23
|
+
export declare function listEgressLedger(..._args: any[]): any;
|
|
24
|
+
export declare function findWriteResultByExternal(..._args: any[]): any;
|
|
25
|
+
export declare function transactionSetId(..._args: any[]): any;
|
|
26
|
+
export declare function listPlanReviews(..._args: any[]): any;
|
|
27
|
+
export declare function latestPlanReview(..._args: any[]): any;
|
|
28
|
+
export declare function buildLocalActionPlan(..._args: any[]): any;
|
|
29
|
+
export declare function recordPlanReview(..._args: any[]): any;
|
|
30
|
+
export declare function approvedPendingActionSet(..._args: any[]): any;
|
|
31
|
+
export declare function assertPendingActionsApproved(..._args: any[]): any;
|
|
32
|
+
export declare function pendingConflicts(..._args: any[]): any;
|
|
33
|
+
export declare function buildApplyPlan(..._args: any[]): any;
|
|
34
|
+
export declare function writePlan(..._args: any[]): any;
|
|
35
|
+
export declare function readPlan(..._args: any[]): any;
|
|
36
|
+
export declare function listPlans(..._args: any[]): any;
|
|
37
|
+
export declare function planRequiresApproval(..._args: any[]): any;
|
|
38
|
+
export declare function applyPlan(..._args: any[]): any;
|
|
39
|
+
export declare function listLeases(..._args: any[]): any;
|
|
40
|
+
export declare function isLeaseActive(..._args: any[]): any;
|
|
41
|
+
export declare function activeLease(..._args: any[]): any;
|
|
42
|
+
export declare function acquireLease(..._args: any[]): any;
|
|
43
|
+
export declare function releaseLease(..._args: any[]): any;
|
|
44
|
+
export declare function reconcile(..._args: any[]): any;
|
|
45
|
+
export declare function isCleanlyReconcilable(..._args: any[]): any;
|
|
46
|
+
export declare function reconcileRequiresApproval(..._args: any[]): any;
|
|
47
|
+
export declare function writeRemoteRef(..._args: any[]): any;
|
|
48
|
+
export declare function readRemoteRef(..._args: any[]): any;
|
|
49
|
+
export declare function listRemoteRefs(..._args: any[]): any;
|
|
50
|
+
export declare function writeLocalRef(..._args: any[]): any;
|
|
51
|
+
export declare function readLocalRef(..._args: any[]): any;
|
|
52
|
+
export declare function isBaseStale(..._args: any[]): any;
|
|
53
|
+
export declare function setQueueRowStatus(..._args: any[]): any;
|
|
54
|
+
export declare function queueRowStatus(..._args: any[]): any;
|
|
55
|
+
export declare function listQueueWithStatus(..._args: any[]): any;
|
|
56
|
+
export declare function pendingQueueRows(..._args: any[]): any;
|
|
57
|
+
export declare function queueCounts(..._args: any[]): any;
|
|
58
|
+
export declare function commitQueueRow(..._args: any[]): any;
|
|
59
|
+
export declare function ignoreQueueRow(..._args: any[]): any;
|
|
60
|
+
export declare function supersedeQueueRow(..._args: any[]): any;
|
|
61
|
+
export declare function poisonQueueRow(..._args: any[]): any;
|
|
62
|
+
export declare function commitPendingQueue(..._args: any[]): any;
|
|
63
|
+
export declare function worldStatus(..._args: any[]): any;
|
|
64
|
+
export declare function formatStatus(..._args: any[]): any;
|
|
65
|
+
export declare function discoverWorldServices(..._args: any[]): any;
|
|
66
|
+
export declare function validateWorldService(..._args: any[]): any;
|
|
67
|
+
export declare function summarizeWorldFindings(..._args: any[]): any;
|
|
68
|
+
export declare function validateWorld(..._args: any[]): any;
|
|
69
|
+
export declare function renderTwinHtml(..._args: any[]): any;
|
|
70
|
+
export declare function createVisualizerServer(..._args: any[]): any;
|
|
71
|
+
export declare function pushPending(..._args: any[]): any;
|
|
72
|
+
export declare function pushOperationOf(..._args: any[]): any;
|
|
73
|
+
export declare function pushIdempotencyKeyOf(..._args: any[]): any;
|
|
74
|
+
export declare function remoteRefs(..._args: any[]): any;
|
|
75
|
+
export declare function buildShadowState(..._args: any[]): any;
|
|
76
|
+
export declare function recordObservedDelta(..._args: any[]): any;
|
|
77
|
+
export declare function recordObservedDeltaLocked(..._args: any[]): any;
|
|
78
|
+
export declare function pollCursorPath(..._args: any[]): any;
|
|
79
|
+
export declare function loadPollCursor(..._args: any[]): any;
|
|
80
|
+
export declare function savePollCursor(..._args: any[]): any;
|
|
81
|
+
export declare function runConnectorPoll(..._args: any[]): any;
|
|
82
|
+
export declare function runConnectorSweep(..._args: any[]): any;
|
|
83
|
+
export declare function applyChangeset(..._args: any[]): any;
|
|
84
|
+
export declare function forkDivergence(..._args: any[]): any;
|
|
85
|
+
export declare function auditForkNoRealWrites(..._args: any[]): any;
|
|
86
|
+
export declare function discardFork(..._args: any[]): any;
|
|
87
|
+
export declare function cherryPickActions(..._args: any[]): any;
|
|
88
|
+
export declare function mergeForks(..._args: any[]): any;
|
|
89
|
+
export declare function resetFork(..._args: any[]): any;
|
|
90
|
+
export declare function rebaseFork(..._args: any[]): any;
|
|
91
|
+
export declare const WRITE_INTENT_SUFFIX = "";
|
|
92
|
+
export declare const WRITE_RESULT_SUFFIX = "";
|
|
93
|
+
export declare class UnreconciledPushError extends Error {
|
|
94
|
+
constructor(message?: string);
|
|
95
|
+
}
|
|
96
|
+
export declare class NonFastForwardPushError extends Error {
|
|
97
|
+
constructor(message?: string);
|
|
98
|
+
}
|
|
99
|
+
export declare class UnreconciledWriteIntentError extends Error {
|
|
100
|
+
constructor(message?: string);
|
|
101
|
+
}
|
|
102
|
+
export declare class LeaseHeldError extends Error {
|
|
103
|
+
constructor(message?: string);
|
|
104
|
+
}
|
|
105
|
+
export type PushStatus = any;
|
|
106
|
+
export type WorldPushRecord = any;
|
|
107
|
+
export type SyncResource = any;
|
|
108
|
+
export type PullResult = any;
|
|
109
|
+
export type PushItemResult = any;
|
|
110
|
+
export type PushResult = any;
|
|
111
|
+
export type EgressActor = any;
|
|
112
|
+
export type EgressWriteRequest = any;
|
|
113
|
+
export type EgressWriteOutcome = any;
|
|
114
|
+
export type EgressWriteResult = any;
|
|
115
|
+
export type EgressLedgerEntry = any;
|
|
116
|
+
export type ProviderCall = any;
|
|
117
|
+
export type WorldApplyPlan = any;
|
|
118
|
+
export type PlanReviewDecision = any;
|
|
119
|
+
export type PlanReviewRecord = any;
|
|
120
|
+
export type LocalActionPlan = any;
|
|
121
|
+
export type ApprovedPendingActionSet = any;
|
|
122
|
+
export type ActionMapper = any;
|
|
123
|
+
export type ApplyResult = any;
|
|
124
|
+
export type WorldApplyLease = any;
|
|
125
|
+
export type ReconcilePolicy = any;
|
|
126
|
+
export type FieldDecision = any;
|
|
127
|
+
export type SubjectReconcile = any;
|
|
128
|
+
export type ReconcilePlan = any;
|
|
129
|
+
export type WorldRemoteRef = any;
|
|
130
|
+
export type WorldLocalRef = any;
|
|
131
|
+
export type QueueRowStatus = any;
|
|
132
|
+
export type QueueStatusTransition = any;
|
|
133
|
+
export type QueueRowWithStatus = any;
|
|
134
|
+
export type QueueCounts = any;
|
|
135
|
+
export type WorldStatus = any;
|
|
136
|
+
export type WorldValidationFinding = any;
|
|
137
|
+
export type WorldValidationSummary = any;
|
|
138
|
+
export type WorldValidationReport = any;
|
|
139
|
+
export type PushReport = any;
|
|
140
|
+
export type RemoteRefsMode = any;
|
|
141
|
+
export type ShadowState = any;
|
|
142
|
+
export type SubjectFieldExtractor = any;
|
|
143
|
+
export type DeltaObservation = any;
|
|
144
|
+
export type DeltaResult = any;
|
|
145
|
+
export type ConnectorObservation = any;
|
|
146
|
+
export type WorldConnector = any;
|
|
147
|
+
export type ConnectorPollResult = any;
|
|
148
|
+
export type SweepConnector = any;
|
|
149
|
+
export type ConnectorSweepResult = any;
|
|
150
|
+
export type CopyResult = any;
|
|
151
|
+
export type ForkAudit = any;
|
|
152
|
+
export type ForkDivergence = any;
|
|
153
|
+
export type SubjectDivergence = any;
|
|
154
|
+
export type FieldDivergence = any;
|
|
155
|
+
export type ApplyTarget = any;
|
|
156
|
+
export type ApplyChangesetOutcome = any;
|
|
157
|
+
export type TwinProxy = any;
|
|
158
|
+
export type TwinProxyOptions = any;
|
|
159
|
+
export type VendorRoute = any;
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
// v1 LEFT THE KERNEL (company contract "Just like Neon", 5 — ruled 2026-09-06). These names are what
|
|
2
|
+
// a protocol 1 pack still imports: the push ledger, the shadow refs and bases, observed deltas as a
|
|
3
|
+
// row kind, egress intents, plans, leases, reconcile, remote refs, the queue lifecycle, the v1 status,
|
|
4
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
5
|
+
// validation and inspector, the poll runner, the v1 apply. Each throws on its first call. A pack
|
|
6
|
+
// that reaches one is out of date; the sweep moves it (protocol 2: the head, the fold, the tree).
|
|
7
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
8
|
+
// Reaching a tombstone is RECORDED, process-wide (one list whichever copy of the kernel the caller
|
|
9
|
+
// resolved), so a verify() that swallows the throw and answers false is still known to have reached
|
|
10
|
+
// v1 machinery: the capability runner reads this list around each verify and says "out of date".
|
|
11
|
+
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
12
|
+
const REACHED = Symbol.for('volter.v1.reached');
|
|
13
|
+
export function v1Reached() { return (globalThis[REACHED] ??= []); }
|
|
14
|
+
// …and per SCOPE, so concurrent verifies attribute a reach to the one that made it (the runner opens a
|
|
15
|
+
// scope around each verify); one storage per process, whichever copy of the kernel the caller resolved
|
|
16
|
+
const SCOPE = Symbol.for('volter.v1.reached.scope');
|
|
17
|
+
// created on first use, never at import (a browser bundle has no AsyncLocalStorage; see observe.ts)
|
|
18
|
+
const scope = () => (globalThis[SCOPE] ??= new AsyncLocalStorage());
|
|
19
|
+
/** Run `fn` and learn which v1 names it reached (directly or through anything it awaited). */
|
|
20
|
+
export async function v1ReachedIn(fn) {
|
|
21
|
+
const reached = [];
|
|
22
|
+
const value = await scope().run(reached, fn);
|
|
23
|
+
return { value, reached };
|
|
24
|
+
}
|
|
25
|
+
const gone = (name) => { v1Reached().push(name); if (typeof AsyncLocalStorage === 'function')
|
|
26
|
+
scope().getStore()?.push(name); throw new Error(`v1 removed: ${name} — protocol 2 replaces it (the head performs, the kernel folds observations, the tree is read); this pack is out of date until the sweep moves it`); };
|
|
27
|
+
export function appendEventLocked(..._args) { return gone('appendEventLocked'); }
|
|
28
|
+
export function eventsLockPath(..._args) { return gone('eventsLockPath'); }
|
|
29
|
+
export function appendPushRecord(..._args) { return gone('appendPushRecord'); }
|
|
30
|
+
export function listPushLedger(..._args) { return gone('listPushLedger'); }
|
|
31
|
+
export function latestPushByActionId(..._args) { return gone('latestPushByActionId'); }
|
|
32
|
+
export function unconfirmedPushes(..._args) { return gone('unconfirmedPushes'); }
|
|
33
|
+
export function assertFastForward(..._args) { return gone('assertFastForward'); }
|
|
34
|
+
export function pushTransaction(..._args) { return gone('pushTransaction'); }
|
|
35
|
+
export function abandonPush(..._args) { return gone('abandonPush'); }
|
|
36
|
+
export function syncPull(..._args) { return gone('syncPull'); }
|
|
37
|
+
export function syncPush(..._args) { return gone('syncPush'); }
|
|
38
|
+
export function currentResources(..._args) { return gone('currentResources'); }
|
|
39
|
+
export function isEgressEventType(..._args) { return gone('isEgressEventType'); }
|
|
40
|
+
export function recordWriteIntent(..._args) { return gone('recordWriteIntent'); }
|
|
41
|
+
export function performExternalWrite(..._args) { return gone('performExternalWrite'); }
|
|
42
|
+
export function listUnreconciledWriteIntents(..._args) { return gone('listUnreconciledWriteIntents'); }
|
|
43
|
+
export function listEgressLedger(..._args) { return gone('listEgressLedger'); }
|
|
44
|
+
export function findWriteResultByExternal(..._args) { return gone('findWriteResultByExternal'); }
|
|
45
|
+
export function transactionSetId(..._args) { return gone('transactionSetId'); }
|
|
46
|
+
export function listPlanReviews(..._args) { return gone('listPlanReviews'); }
|
|
47
|
+
export function latestPlanReview(..._args) { return gone('latestPlanReview'); }
|
|
48
|
+
export function buildLocalActionPlan(..._args) { return gone('buildLocalActionPlan'); }
|
|
49
|
+
export function recordPlanReview(..._args) { return gone('recordPlanReview'); }
|
|
50
|
+
export function approvedPendingActionSet(..._args) { return gone('approvedPendingActionSet'); }
|
|
51
|
+
export function assertPendingActionsApproved(..._args) { return gone('assertPendingActionsApproved'); }
|
|
52
|
+
export function pendingConflicts(..._args) { return gone('pendingConflicts'); }
|
|
53
|
+
export function buildApplyPlan(..._args) { return gone('buildApplyPlan'); }
|
|
54
|
+
export function writePlan(..._args) { return gone('writePlan'); }
|
|
55
|
+
export function readPlan(..._args) { return gone('readPlan'); }
|
|
56
|
+
export function listPlans(..._args) { return gone('listPlans'); }
|
|
57
|
+
export function planRequiresApproval(..._args) { return gone('planRequiresApproval'); }
|
|
58
|
+
export function applyPlan(..._args) { return gone('applyPlan'); }
|
|
59
|
+
export function listLeases(..._args) { return gone('listLeases'); }
|
|
60
|
+
export function isLeaseActive(..._args) { return gone('isLeaseActive'); }
|
|
61
|
+
export function activeLease(..._args) { return gone('activeLease'); }
|
|
62
|
+
export function acquireLease(..._args) { return gone('acquireLease'); }
|
|
63
|
+
export function releaseLease(..._args) { return gone('releaseLease'); }
|
|
64
|
+
export function reconcile(..._args) { return gone('reconcile'); }
|
|
65
|
+
export function isCleanlyReconcilable(..._args) { return gone('isCleanlyReconcilable'); }
|
|
66
|
+
export function reconcileRequiresApproval(..._args) { return gone('reconcileRequiresApproval'); }
|
|
67
|
+
export function writeRemoteRef(..._args) { return gone('writeRemoteRef'); }
|
|
68
|
+
export function readRemoteRef(..._args) { return gone('readRemoteRef'); }
|
|
69
|
+
export function listRemoteRefs(..._args) { return gone('listRemoteRefs'); }
|
|
70
|
+
export function writeLocalRef(..._args) { return gone('writeLocalRef'); }
|
|
71
|
+
export function readLocalRef(..._args) { return gone('readLocalRef'); }
|
|
72
|
+
export function isBaseStale(..._args) { return gone('isBaseStale'); }
|
|
73
|
+
export function setQueueRowStatus(..._args) { return gone('setQueueRowStatus'); }
|
|
74
|
+
export function queueRowStatus(..._args) { return gone('queueRowStatus'); }
|
|
75
|
+
export function listQueueWithStatus(..._args) { return gone('listQueueWithStatus'); }
|
|
76
|
+
export function pendingQueueRows(..._args) { return gone('pendingQueueRows'); }
|
|
77
|
+
export function queueCounts(..._args) { return gone('queueCounts'); }
|
|
78
|
+
export function commitQueueRow(..._args) { return gone('commitQueueRow'); }
|
|
79
|
+
export function ignoreQueueRow(..._args) { return gone('ignoreQueueRow'); }
|
|
80
|
+
export function supersedeQueueRow(..._args) { return gone('supersedeQueueRow'); }
|
|
81
|
+
export function poisonQueueRow(..._args) { return gone('poisonQueueRow'); }
|
|
82
|
+
export function commitPendingQueue(..._args) { return gone('commitPendingQueue'); }
|
|
83
|
+
export function worldStatus(..._args) { return gone('worldStatus'); }
|
|
84
|
+
export function formatStatus(..._args) { return gone('formatStatus'); }
|
|
85
|
+
export function discoverWorldServices(..._args) { return gone('discoverWorldServices'); }
|
|
86
|
+
export function validateWorldService(..._args) { return gone('validateWorldService'); }
|
|
87
|
+
export function summarizeWorldFindings(..._args) { return gone('summarizeWorldFindings'); }
|
|
88
|
+
export function validateWorld(..._args) { return gone('validateWorld'); }
|
|
89
|
+
export function renderTwinHtml(..._args) { return gone('renderTwinHtml'); }
|
|
90
|
+
export function createVisualizerServer(..._args) { return gone('createVisualizerServer'); }
|
|
91
|
+
export function pushPending(..._args) { return gone('pushPending'); }
|
|
92
|
+
export function pushOperationOf(..._args) { return gone('pushOperationOf'); }
|
|
93
|
+
export function pushIdempotencyKeyOf(..._args) { return gone('pushIdempotencyKeyOf'); }
|
|
94
|
+
export function remoteRefs(..._args) { return gone('remoteRefs'); }
|
|
95
|
+
export function buildShadowState(..._args) { return gone('buildShadowState'); }
|
|
96
|
+
export function recordObservedDelta(..._args) { return gone('recordObservedDelta'); }
|
|
97
|
+
export function recordObservedDeltaLocked(..._args) { return gone('recordObservedDeltaLocked'); }
|
|
98
|
+
export function pollCursorPath(..._args) { return gone('pollCursorPath'); }
|
|
99
|
+
export function loadPollCursor(..._args) { return gone('loadPollCursor'); }
|
|
100
|
+
export function savePollCursor(..._args) { return gone('savePollCursor'); }
|
|
101
|
+
export function runConnectorPoll(..._args) { return gone('runConnectorPoll'); }
|
|
102
|
+
export function runConnectorSweep(..._args) { return gone('runConnectorSweep'); }
|
|
103
|
+
export function applyChangeset(..._args) { return gone('applyChangeset'); }
|
|
104
|
+
export function forkDivergence(..._args) { return gone('forkDivergence'); }
|
|
105
|
+
export function auditForkNoRealWrites(..._args) { return gone('auditForkNoRealWrites'); }
|
|
106
|
+
export function discardFork(..._args) { return gone('discardFork'); }
|
|
107
|
+
export function cherryPickActions(..._args) { return gone('cherryPickActions'); }
|
|
108
|
+
export function mergeForks(..._args) { return gone('mergeForks'); }
|
|
109
|
+
export function resetFork(..._args) { return gone('resetFork'); }
|
|
110
|
+
export function rebaseFork(..._args) { return gone('rebaseFork'); }
|
|
111
|
+
export const WRITE_INTENT_SUFFIX = '';
|
|
112
|
+
export const WRITE_RESULT_SUFFIX = '';
|
|
113
|
+
export class UnreconciledPushError extends Error {
|
|
114
|
+
constructor(message = 'v1 removed: UnreconciledPushError') { super(message); this.name = 'UnreconciledPushError'; }
|
|
115
|
+
}
|
|
116
|
+
export class NonFastForwardPushError extends Error {
|
|
117
|
+
constructor(message = 'v1 removed: NonFastForwardPushError') { super(message); this.name = 'NonFastForwardPushError'; }
|
|
118
|
+
}
|
|
119
|
+
export class UnreconciledWriteIntentError extends Error {
|
|
120
|
+
constructor(message = 'v1 removed: UnreconciledWriteIntentError') { super(message); this.name = 'UnreconciledWriteIntentError'; }
|
|
121
|
+
}
|
|
122
|
+
export class LeaseHeldError extends Error {
|
|
123
|
+
constructor(message = 'v1 removed: LeaseHeldError') { super(message); this.name = 'LeaseHeldError'; }
|
|
124
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/** The directory host-owned state lives under — rate-budget ledgers, the branch-ancestry lock and
|
|
2
|
+
* removals, the shared blob index, World lifecycle records: `VOLTER_HOME`, else `~/.volter`. A
|
|
3
|
+
* verification run points it at a directory of its own (scripts/isolated-home.ts), so a run
|
|
4
|
+
* spends no other actor's allowance and deletes none of their records. Read on every call. */
|
|
5
|
+
export declare function volterHome(): string;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { homedir } from 'node:os';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
/** The directory host-owned state lives under — rate-budget ledgers, the branch-ancestry lock and
|
|
4
|
+
* removals, the shared blob index, World lifecycle records: `VOLTER_HOME`, else `~/.volter`. A
|
|
5
|
+
* verification run points it at a directory of its own (scripts/isolated-home.ts), so a run
|
|
6
|
+
* spends no other actor's allowance and deletes none of their records. Read on every call. */
|
|
7
|
+
export function volterHome() {
|
|
8
|
+
const set = process.env.VOLTER_HOME;
|
|
9
|
+
return set !== undefined && set !== '' ? set : join(homedir(), '.volter');
|
|
10
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// THE WORLD CLOCK — the one piece of physics every twin consults (TWIN-PROGRAMMING-MODEL P3).
|
|
2
|
+
// Time is a GLOBAL fact requiring simultaneous agreement: TTL/expiry logic reads it at serve
|
|
3
|
+
// time, so no seed-side workaround exists. The clock is a FILE, not a service — every twin
|
|
4
|
+
// reads it per request (a tiny local read; the serve-path determinism tooth bans network
|
|
5
|
+
// egress, not filesystem reads), the world runtime owns writing it, and `volter-world clock`
|
|
6
|
+
// is the operator door (set / advance / show). No file, or no env → real wall-clock time,
|
|
7
|
+
// exactly the pre-clock behavior; a world only has scripted time when its operator says so.
|
|
8
|
+
//
|
|
9
|
+
// File format: a single ISO-8601 line (a FROZEN instant). Every read returns exactly that
|
|
10
|
+
// instant — deterministic by construction. Advancing time is an explicit operator act, never
|
|
11
|
+
// a background drift.
|
|
12
|
+
// Read through the ACTIVE WorldStore, not node:fs: the clock file is world state, and a
|
|
13
|
+
// hydrated serverless namespace must carry its frozen instant with it (runtime contract R11–R14
|
|
14
|
+
// — a MemoryWorldStore world that read the clock off the host filesystem would silently
|
|
15
|
+
// serve the HOST's time, the exact two-services-disagreeing-about-now defect below).
|
|
16
|
+
import { getActiveWorldStore } from "./world-store.js";
|
|
17
|
+
export const WORLD_CLOCK_ENV = 'TWIN_WORLD_CLOCK_FILE';
|
|
18
|
+
/** The world's "now": the frozen instant in the clock file when one is configured and
|
|
19
|
+
* parseable; real wall-clock time otherwise. */
|
|
20
|
+
export function worldNow() {
|
|
21
|
+
const file = process.env[WORLD_CLOCK_ENV];
|
|
22
|
+
if (file && getActiveWorldStore().exists(file)) {
|
|
23
|
+
const raw = (getActiveWorldStore().read(file) ?? '').trim();
|
|
24
|
+
const parsed = Date.parse(raw);
|
|
25
|
+
if (!Number.isNaN(parsed))
|
|
26
|
+
return new Date(parsed).toISOString();
|
|
27
|
+
// A configured-but-corrupt clock is a WORLD defect — fail loudly, never silently drift
|
|
28
|
+
// back to wall-clock (two services disagreeing about now breaks the world).
|
|
29
|
+
throw new Error(`world clock: ${file} does not contain a parseable ISO-8601 instant (got ${JSON.stringify(raw.slice(0, 40))})`);
|
|
30
|
+
}
|
|
31
|
+
return new Date().toISOString();
|
|
32
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// A WORLD'S OWN ENV VALUES, as a twin reads them. A World hands its env to every twin it runs (a spawned process, a
|
|
2
|
+
// colocated host, a worker) on top of the caller's own, which only `runtime.environment.strip` keeps out. The runtime
|
|
3
|
+
// names the variables the World itself sets in WORLD_ENV_NAMES_ENV; a twin that must agree with the app (a signing
|
|
4
|
+
// secret the app verifies with) reads only those, so a developer's own exported secret never becomes World state.
|
|
5
|
+
// Read per call: an import reads nothing, and a runtime with no `process` (a browser tab) has no World env.
|
|
6
|
+
export const WORLD_ENV_NAMES_ENV = 'VOLTER_WORLD_ENV_NAMES';
|
|
7
|
+
/** The value the World set for `name`; undefined when the World sets none (whatever the caller's shell holds). */
|
|
8
|
+
export function worldEnvValue(name) {
|
|
9
|
+
const env = typeof process === 'undefined' ? undefined : process.env;
|
|
10
|
+
const raw = env?.[WORLD_ENV_NAMES_ENV];
|
|
11
|
+
if (!raw)
|
|
12
|
+
return undefined;
|
|
13
|
+
let names;
|
|
14
|
+
try {
|
|
15
|
+
names = JSON.parse(raw);
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
return undefined;
|
|
19
|
+
}
|
|
20
|
+
const value = Array.isArray(names) && names.includes(name) ? env[name] : undefined;
|
|
21
|
+
return value ? value : undefined;
|
|
22
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { WorldStat, WorldStore } from './world-store.js';
|
|
2
|
+
type SqlValue = string | number | null | ArrayBuffer;
|
|
3
|
+
/** The slice of a SQLite handle the store uses — Durable Objects' `SqlStorage` satisfies it. */
|
|
4
|
+
export type SqlExec = {
|
|
5
|
+
exec(query: string, ...bindings: unknown[]): {
|
|
6
|
+
toArray(): Record<string, SqlValue>[];
|
|
7
|
+
};
|
|
8
|
+
};
|
|
9
|
+
export declare class SqlWorldStore implements WorldStore {
|
|
10
|
+
private readonly sql;
|
|
11
|
+
private version;
|
|
12
|
+
constructor(sql: SqlExec);
|
|
13
|
+
private file;
|
|
14
|
+
private put;
|
|
15
|
+
read(path: string): string | null;
|
|
16
|
+
readLines(path: string): string[];
|
|
17
|
+
append(path: string, data: string): void;
|
|
18
|
+
write(path: string, data: string): void;
|
|
19
|
+
writeAtomic(path: string, data: string): void;
|
|
20
|
+
exists(path: string): boolean;
|
|
21
|
+
remove(path: string): void;
|
|
22
|
+
mkdir(_dirPath: string): void;
|
|
23
|
+
list(dirPath: string): string[];
|
|
24
|
+
stat(path: string): WorldStat | null;
|
|
25
|
+
withLock<T>(_lockPath: string, fn: () => T): T;
|
|
26
|
+
}
|
|
27
|
+
export {};
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/** Characters per stored chunk: at most 4 UTF-8 bytes each, well under a 2 MB row. */
|
|
2
|
+
const CHUNK = 256 * 1024;
|
|
3
|
+
const encoder = new TextEncoder();
|
|
4
|
+
/** `[low, high)` bounds selecting every path strictly under `dir`. */
|
|
5
|
+
function under(dir) {
|
|
6
|
+
const base = dir.endsWith('/') ? dir.slice(0, -1) : dir;
|
|
7
|
+
return [`${base}/`, `${base}0`]; // '0' is the code point after '/'
|
|
8
|
+
}
|
|
9
|
+
export class SqlWorldStore {
|
|
10
|
+
sql;
|
|
11
|
+
version;
|
|
12
|
+
constructor(sql) {
|
|
13
|
+
this.sql = sql;
|
|
14
|
+
sql.exec('CREATE TABLE IF NOT EXISTS ws_files (path TEXT PRIMARY KEY, size INTEGER NOT NULL, version INTEGER NOT NULL, chunks INTEGER NOT NULL)');
|
|
15
|
+
sql.exec('CREATE TABLE IF NOT EXISTS ws_chunks (path TEXT NOT NULL, seq INTEGER NOT NULL, data TEXT NOT NULL, PRIMARY KEY (path, seq))');
|
|
16
|
+
this.version = Number(sql.exec('SELECT COALESCE(MAX(version), 0) AS v FROM ws_files').toArray()[0]?.v ?? 0);
|
|
17
|
+
}
|
|
18
|
+
file(path) {
|
|
19
|
+
const row = this.sql.exec('SELECT size, version, chunks FROM ws_files WHERE path = ?', path).toArray()[0];
|
|
20
|
+
return row ? { size: Number(row.size), version: Number(row.version), chunks: Number(row.chunks) } : null;
|
|
21
|
+
}
|
|
22
|
+
put(path, data, from, size) {
|
|
23
|
+
let seq = from;
|
|
24
|
+
for (let i = 0; i < data.length; i += CHUNK)
|
|
25
|
+
this.sql.exec('INSERT INTO ws_chunks (path, seq, data) VALUES (?, ?, ?)', path, seq++, data.slice(i, i + CHUNK));
|
|
26
|
+
this.version += 1;
|
|
27
|
+
this.sql.exec('INSERT INTO ws_files (path, size, version, chunks) VALUES (?, ?, ?, ?) ON CONFLICT(path) DO UPDATE SET size = excluded.size, version = excluded.version, chunks = excluded.chunks', path, size, this.version, seq);
|
|
28
|
+
}
|
|
29
|
+
read(path) {
|
|
30
|
+
if (!this.file(path))
|
|
31
|
+
return null;
|
|
32
|
+
return this.sql.exec('SELECT data FROM ws_chunks WHERE path = ? ORDER BY seq', path).toArray().map((r) => String(r.data)).join('');
|
|
33
|
+
}
|
|
34
|
+
readLines(path) {
|
|
35
|
+
const content = this.read(path);
|
|
36
|
+
return content === null ? [] : content.split('\n');
|
|
37
|
+
}
|
|
38
|
+
append(path, data) {
|
|
39
|
+
const existing = this.file(path);
|
|
40
|
+
this.put(path, data, existing?.chunks ?? 0, (existing?.size ?? 0) + encoder.encode(data).length);
|
|
41
|
+
}
|
|
42
|
+
write(path, data) {
|
|
43
|
+
this.sql.exec('DELETE FROM ws_chunks WHERE path = ?', path);
|
|
44
|
+
this.put(path, data, 0, encoder.encode(data).length);
|
|
45
|
+
}
|
|
46
|
+
writeAtomic(path, data) {
|
|
47
|
+
// One synchronous run of SQL with no await inside: no reader interleaves, and the object's
|
|
48
|
+
// output gate holds the response until the write is durable.
|
|
49
|
+
this.write(path, data);
|
|
50
|
+
}
|
|
51
|
+
exists(path) {
|
|
52
|
+
if (this.file(path))
|
|
53
|
+
return true;
|
|
54
|
+
const [low, high] = under(path);
|
|
55
|
+
return this.sql.exec('SELECT 1 FROM ws_files WHERE path >= ? AND path < ? LIMIT 1', low, high).toArray().length > 0;
|
|
56
|
+
}
|
|
57
|
+
remove(path) {
|
|
58
|
+
const [low, high] = under(path);
|
|
59
|
+
this.sql.exec('DELETE FROM ws_chunks WHERE path = ? OR (path >= ? AND path < ?)', path, low, high);
|
|
60
|
+
this.sql.exec('DELETE FROM ws_files WHERE path = ? OR (path >= ? AND path < ?)', path, low, high);
|
|
61
|
+
}
|
|
62
|
+
mkdir(_dirPath) {
|
|
63
|
+
// Directories are implicit in the path set.
|
|
64
|
+
}
|
|
65
|
+
list(dirPath) {
|
|
66
|
+
const [low, high] = under(dirPath);
|
|
67
|
+
const children = new Set();
|
|
68
|
+
for (const row of this.sql.exec('SELECT path FROM ws_files WHERE path >= ? AND path < ?', low, high).toArray()) {
|
|
69
|
+
const rest = String(row.path).slice(low.length);
|
|
70
|
+
const slash = rest.indexOf('/');
|
|
71
|
+
children.add(slash === -1 ? rest : rest.slice(0, slash));
|
|
72
|
+
}
|
|
73
|
+
return [...children];
|
|
74
|
+
}
|
|
75
|
+
stat(path) {
|
|
76
|
+
const file = this.file(path);
|
|
77
|
+
if (file)
|
|
78
|
+
return { size: file.size, mtimeMs: file.version, isDirectory: false };
|
|
79
|
+
return this.exists(path) ? { size: 0, mtimeMs: 0, isDirectory: true } : null;
|
|
80
|
+
}
|
|
81
|
+
withLock(_lockPath, fn) {
|
|
82
|
+
// A Durable Object runs one synchronous stretch of JavaScript at a time and `fn` is synchronous,
|
|
83
|
+
// so the kernel's read-then-append critical sections are already exclusive.
|
|
84
|
+
return fn();
|
|
85
|
+
}
|
|
86
|
+
}
|