@statewalker/cellb 0.5.1 → 0.6.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.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"files-rpc.js","sourceRoot":"","sources":["../../src/ltx/files-rpc.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * A FilesApi across threads: `serveFilesApi(port, files)` answers on one end of a MessagePort\n * for a FilesApi that lives in this thread (the user's own: File System Access, memory, OPFS,\n * a key/value store, S3…), and `rpcFilesClient(port)` is that FilesApi in another thread (the\n * LTX store worker, say). An instance cannot be posted to a worker; a port can.\n *\n * On @statewalker/webrun-rpc: the unary methods are one `callPort` each; a ranged read (the\n * LTX store's page reads) is one `callPort` answered with the bytes; `read` without a length,\n * `list` and `write` stream with `callBidi`. A write is sent in batches of up to `batchBytes`\n * (the LTX encoder yields one small block per page).\n *\n * The client must not live in a thread that blocks on `Atomics.wait` (the SQLite VFS worker):\n * a blocked thread receives no answers.\n */\nimport type {\n FileInfo,\n FileStats,\n FilesApi,\n ListOptions,\n ReadOptions,\n} from \"@statewalker/webrun-files\";\nimport {\n callBidi,\n callPort,\n listenBidi,\n listenPort,\n type MessageTarget,\n} from \"@statewalker/webrun-rpc\";\n\nconst UNARY = \"webrun-files:unary\";\nconst UNARY_METHODS = [\"stats\", \"exists\", \"mkdir\", \"remove\", \"move\", \"copy\", \"readRange\"] as const;\ntype UnaryMethod = (typeof UNARY_METHODS)[number];\n\ninterface UnaryRequest {\n m: UnaryMethod;\n a: unknown[];\n}\ninterface StreamRequest {\n fs: \"read\" | \"list\" | \"write\";\n path: string;\n opts?: { start?: number; length?: number; recursive?: boolean; after?: string };\n}\n\nexport interface FilesRpcOptions {\n /** How long one call may take, in ms (a disk, a network store); 60 s by default. */\n timeout?: number;\n}\n\nconst start = (port: MessageTarget) => (port as { start?: () => void }).start?.();\n\n/**\n * The bytes of `c` in a buffer of their own: posting a view clones its WHOLE buffer (a page\n * read as a subarray of a 10 MB file would cross the thread as 10 MB).\n */\nconst own = (c: Uint8Array) =>\n c.byteOffset === 0 && c.byteLength === c.buffer.byteLength ? c : c.slice();\n\nasync function* owned(chunks: AsyncIterable<Uint8Array>) {\n for await (const c of chunks) yield own(c);\n}\n\nasync function concat(chunks: AsyncIterable<Uint8Array>): Promise<Uint8Array> {\n const parts: Uint8Array[] = [];\n let n = 0;\n for await (const c of chunks) parts.push(c), (n += c.length);\n if (parts.length === 1) return own(parts[0]);\n const out = new Uint8Array(n);\n let o = 0;\n for (const p of parts) out.set(p, o), (o += p.length);\n return out;\n}\n\n/**\n * Answers `rpcFilesClient` calls on `port` with `files`. Several ports may serve one FilesApi\n * (one per worker). Returns the teardown.\n */\nexport function serveFilesApi(port: MessageTarget, files: FilesApi): () => void {\n const offUnary = listenPort<UnaryRequest, unknown>(\n port,\n async ({ m, a }) => {\n if (!UNARY_METHODS.includes(m)) throw new Error(`files-rpc: unknown method ${String(m)}`);\n if (m === \"readRange\") {\n const [path, start, length] = a as [string, number, number];\n return concat(files.read(path, { start, length }));\n }\n return (files[m] as (...args: unknown[]) => Promise<unknown>).apply(files, a);\n },\n { channelName: UNARY },\n );\n const offStream = listenBidi<Uint8Array, unknown>(\n port,\n async (input, params) => {\n const { fs, path, opts } = params as unknown as StreamRequest;\n if (fs === \"read\") return owned(files.read(path, opts as ReadOptions));\n if (fs === \"list\") return files.list(path, opts as ListOptions);\n await files.write(path, input);\n return (async function* () {})();\n },\n (params) => typeof (params as { fs?: unknown }).fs === \"string\",\n );\n start(port);\n return () => {\n offUnary();\n offStream();\n };\n}\n\n/** A FilesApi whose calls go over `port` to a `serveFilesApi`. */\nexport function rpcFilesClient(\n port: MessageTarget,\n opts: FilesRpcOptions & {\n /** Bytes of a write sent per message; 1 MiB by default. */\n batchBytes?: number;\n } = {},\n): FilesApi {\n start(port);\n const timeout = opts.timeout ?? 60_000;\n const batchBytes = opts.batchBytes ?? 1 << 20;\n const call = <T>(m: UnaryMethod, a: unknown[], signal?: AbortSignal) =>\n callPort<T, UnaryRequest>(port, { m, a }, { channelName: UNARY, timeout, signal });\n const unary =\n <T>(m: UnaryMethod) =>\n (...a: unknown[]) =>\n call<T>(m, a);\n const stream = <T>(\n req: StreamRequest,\n input: AsyncIterable<Uint8Array> | Uint8Array[] = [],\n signal?: AbortSignal,\n ) =>\n callBidi<Uint8Array, T>(port, input, {\n ...req,\n // Read by the serving side too: its `send` waits this long for each chunk's\n // acknowledgement, which comes when this side's consumer asks for the next one (without\n // it, a consumer pausing over 1 s between chunks failed the stream).\n timeout,\n options: { timeout, bidiTimeout: 2147483647, signal },\n });\n /** Chunks gathered into messages of about `batchBytes`. */\n async function* batched(content: Iterable<Uint8Array> | AsyncIterable<Uint8Array>) {\n let parts: Uint8Array[] = [];\n let n = 0;\n const flush = () => {\n const out = parts.length === 1 ? own(parts[0]) : new Uint8Array(n);\n if (parts.length > 1) {\n let o = 0;\n for (const p of parts) out.set(p, o), (o += p.length);\n }\n parts = [];\n n = 0;\n return out;\n };\n for await (const c of content as AsyncIterable<Uint8Array>) {\n if (!c.length) continue;\n parts.push(c);\n n += c.length;\n if (n >= batchBytes) yield flush();\n }\n if (n) yield flush();\n }\n const api: FilesApi = {\n read(path: string, o?: ReadOptions): AsyncIterable<Uint8Array> {\n const { start, length, signal } = o ?? {};\n // A ranged read (the LTX store's page reads): one call, one answer.\n if (length !== undefined)\n return (async function* () {\n const bytes = await call<Uint8Array>(\"readRange\", [path, start ?? 0, length], signal);\n if (bytes.length) yield bytes;\n })();\n return stream<Uint8Array>({ fs: \"read\", path, opts: { start } }, [], signal);\n },\n list(path: string, o?: ListOptions): AsyncIterable<FileInfo> {\n return stream<FileInfo>({\n fs: \"list\",\n path,\n opts: { recursive: o?.recursive, after: o?.after },\n });\n },\n async write(path, content) {\n for await (const _ of stream<never>({ fs: \"write\", path }, batched(content))) void _;\n },\n stats: unary<FileStats | undefined>(\"stats\") as FilesApi[\"stats\"],\n exists: unary<boolean>(\"exists\") as FilesApi[\"exists\"],\n mkdir: unary<void>(\"mkdir\") as FilesApi[\"mkdir\"],\n remove: unary<boolean>(\"remove\") as FilesApi[\"remove\"],\n move: unary<boolean>(\"move\") as FilesApi[\"move\"],\n copy: unary<boolean>(\"copy\") as FilesApi[\"copy\"],\n };\n return api;\n}\n"]}
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Cells' storage chosen by the app: it opens a FilesApi (File System Access, memory, OPFS, a
3
+ * key/value store, S3…) BEFORE starting cellb's workers, then hands it to them in one of two
4
+ * ways (a FilesApi instance cannot be posted to a worker; a port can):
5
+ *
6
+ * - `filesApiPort(files)`: the FilesApi stays in this thread and is served over a port; the
7
+ * shard worker passes the port to its LTX store worker (`bucket: { ltx, files: { port } }`),
8
+ * which runs the store (LZ4, compaction) and calls the FilesApi by message.
9
+ * - `serveLtxStore(files)`: the LTX store itself runs in this thread on the instance (no
10
+ * FilesApi messages at all); the shard worker's VFS sends it its page reads and commits
11
+ * (`bucket: { ltx, ltxStore: { sab, port } }`). The store's work then shares this thread.
12
+ *
13
+ * Either way, one call per shard worker: a port answers one worker, a store one VFS.
14
+ */
15
+ import type { FilesApi } from "@statewalker/webrun-files";
16
+ /** A port to `files` for a worker (transfer it); `close()` stops serving it. */
17
+ export declare function filesApiPort(files: FilesApi): {
18
+ port: MessagePort;
19
+ close(): void;
20
+ };
21
+ export interface ServeLtxStoreOptions {
22
+ /** Names the storage: two stores with one scope never write the same cell at once. */
23
+ lockScope: string;
24
+ /** Files per cell before it is folded into one snapshot (default 64). */
25
+ compactAfter?: number;
26
+ /** Free bytes where the files are (see ltxCellStore); unknown by default. */
27
+ room?: () => Promise<number | undefined>;
28
+ /** Bytes of the shared buffer's payload: a read-ahead run (default 1 MiB, at least 64 KiB). */
29
+ payloadBytes?: number;
30
+ }
31
+ /**
32
+ * The LTX store on `files` in this thread, for ONE shard worker's VFS: give the worker
33
+ * `{ sab, port }` (transfer the port). This thread must not block (a page, or any worker that
34
+ * does not run SQLite); it answers into `sab`.
35
+ */
36
+ export declare function serveLtxStore(files: FilesApi, opts: ServeLtxStoreOptions): {
37
+ sab: SharedArrayBuffer;
38
+ port: MessagePort;
39
+ store: {
40
+ handle(msg: import("./ltx-page-server.js").LtxStoreMessage & {
41
+ seq?: number;
42
+ until?: number;
43
+ }): void;
44
+ compact: (cell: string) => Promise<number>;
45
+ stats: {
46
+ commits: number;
47
+ writeMs: number;
48
+ appendMs: number;
49
+ compactions: number;
50
+ compactMs: number;
51
+ reads: number;
52
+ readMs: number;
53
+ };
54
+ close(): Promise<void>;
55
+ files: (cell: string) => Promise<string[]>;
56
+ };
57
+ /**
58
+ * Stops serving: call it when the shard worker ends, so a store for its successor can
59
+ * take the cells' Web Locks (it waits 20 s for them, then fails). Resolves once the
60
+ * store's work under way has ended and its locks are released.
61
+ */
62
+ close(): Promise<void>;
63
+ };
@@ -0,0 +1,46 @@
1
+ import { serveFilesApi } from "./files-rpc.js";
2
+ import { ltxCellStore } from "./ltx-page-server.js";
3
+ /** A port to `files` for a worker (transfer it); `close()` stops serving it. */
4
+ export function filesApiPort(files) {
5
+ const ch = new MessageChannel();
6
+ const stop = serveFilesApi(ch.port1, files);
7
+ return {
8
+ port: ch.port2,
9
+ close() {
10
+ stop();
11
+ ch.port1.close();
12
+ },
13
+ };
14
+ }
15
+ /**
16
+ * The LTX store on `files` in this thread, for ONE shard worker's VFS: give the worker
17
+ * `{ sab, port }` (transfer the port). This thread must not block (a page, or any worker that
18
+ * does not run SQLite); it answers into `sab`.
19
+ */
20
+ export function serveLtxStore(files, opts) {
21
+ // At least one page of SQLite's largest size: a smaller buffer would answer reads with no
22
+ // pages at all.
23
+ const sab = new SharedArrayBuffer(8 + Math.max(65536, opts.payloadBytes ?? 16 * 65536));
24
+ const store = ltxCellStore(files, sab, {
25
+ lockScope: opts.lockScope,
26
+ compactAfter: opts.compactAfter,
27
+ room: opts.room ?? (async () => undefined),
28
+ });
29
+ const ch = new MessageChannel();
30
+ ch.port1.onmessage = (e) => store.handle(e.data);
31
+ return {
32
+ sab,
33
+ port: ch.port2,
34
+ store,
35
+ /**
36
+ * Stops serving: call it when the shard worker ends, so a store for its successor can
37
+ * take the cells' Web Locks (it waits 20 s for them, then fails). Resolves once the
38
+ * store's work under way has ended and its locks are released.
39
+ */
40
+ close() {
41
+ ch.port1.close();
42
+ return store.close();
43
+ },
44
+ };
45
+ }
46
+ //# sourceMappingURL=ltx-host.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ltx-host.js","sourceRoot":"","sources":["../../src/ltx/ltx-host.ts"],"names":[],"mappings":"AAeA,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAC/C,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAEpD,gFAAgF;AAChF,MAAM,UAAU,YAAY,CAAC,KAAe;IAC1C,MAAM,EAAE,GAAG,IAAI,cAAc,EAAE,CAAC;IAChC,MAAM,IAAI,GAAG,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;IAC5C,OAAO;QACL,IAAI,EAAE,EAAE,CAAC,KAAK;QACd,KAAK;YACH,IAAI,EAAE,CAAC;YACP,EAAE,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC;QACnB,CAAC;KACF,CAAC;AACJ,CAAC;AAaD;;;;GAIG;AACH,MAAM,UAAU,aAAa,CAAC,KAAe,EAAE,IAA0B;IACvE,0FAA0F;IAC1F,gBAAgB;IAChB,MAAM,GAAG,GAAG,IAAI,iBAAiB,CAAC,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,IAAI,CAAC,YAAY,IAAI,EAAE,GAAG,KAAK,CAAC,CAAC,CAAC;IACxF,MAAM,KAAK,GAAG,YAAY,CAAC,KAAK,EAAE,GAAG,EAAE;QACrC,SAAS,EAAE,IAAI,CAAC,SAAS;QACzB,YAAY,EAAE,IAAI,CAAC,YAAY;QAC/B,IAAI,EAAE,IAAI,CAAC,IAAI,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC,SAAS,CAAC;KAC3C,CAAC,CAAC;IACH,MAAM,EAAE,GAAG,IAAI,cAAc,EAAE,CAAC;IAChC,EAAE,CAAC,KAAK,CAAC,SAAS,GAAG,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;IACjD,OAAO;QACL,GAAG;QACH,IAAI,EAAE,EAAE,CAAC,KAAK;QACd,KAAK;QACL;;;;WAIG;QACH,KAAK;YACH,EAAE,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC;YACjB,OAAO,KAAK,CAAC,KAAK,EAAE,CAAC;QACvB,CAAC;KACF,CAAC;AACJ,CAAC","sourcesContent":["/**\n * Cells' storage chosen by the app: it opens a FilesApi (File System Access, memory, OPFS, a\n * key/value store, S3…) BEFORE starting cellb's workers, then hands it to them in one of two\n * ways (a FilesApi instance cannot be posted to a worker; a port can):\n *\n * - `filesApiPort(files)`: the FilesApi stays in this thread and is served over a port; the\n * shard worker passes the port to its LTX store worker (`bucket: { ltx, files: { port } }`),\n * which runs the store (LZ4, compaction) and calls the FilesApi by message.\n * - `serveLtxStore(files)`: the LTX store itself runs in this thread on the instance (no\n * FilesApi messages at all); the shard worker's VFS sends it its page reads and commits\n * (`bucket: { ltx, ltxStore: { sab, port } }`). The store's work then shares this thread.\n *\n * Either way, one call per shard worker: a port answers one worker, a store one VFS.\n */\nimport type { FilesApi } from \"@statewalker/webrun-files\";\nimport { serveFilesApi } from \"./files-rpc.js\";\nimport { ltxCellStore } from \"./ltx-page-server.js\";\n\n/** A port to `files` for a worker (transfer it); `close()` stops serving it. */\nexport function filesApiPort(files: FilesApi): { port: MessagePort; close(): void } {\n const ch = new MessageChannel();\n const stop = serveFilesApi(ch.port1, files);\n return {\n port: ch.port2,\n close() {\n stop();\n ch.port1.close();\n },\n };\n}\n\nexport interface ServeLtxStoreOptions {\n /** Names the storage: two stores with one scope never write the same cell at once. */\n lockScope: string;\n /** Files per cell before it is folded into one snapshot (default 64). */\n compactAfter?: number;\n /** Free bytes where the files are (see ltxCellStore); unknown by default. */\n room?: () => Promise<number | undefined>;\n /** Bytes of the shared buffer's payload: a read-ahead run (default 1 MiB, at least 64 KiB). */\n payloadBytes?: number;\n}\n\n/**\n * The LTX store on `files` in this thread, for ONE shard worker's VFS: give the worker\n * `{ sab, port }` (transfer the port). This thread must not block (a page, or any worker that\n * does not run SQLite); it answers into `sab`.\n */\nexport function serveLtxStore(files: FilesApi, opts: ServeLtxStoreOptions) {\n // At least one page of SQLite's largest size: a smaller buffer would answer reads with no\n // pages at all.\n const sab = new SharedArrayBuffer(8 + Math.max(65536, opts.payloadBytes ?? 16 * 65536));\n const store = ltxCellStore(files, sab, {\n lockScope: opts.lockScope,\n compactAfter: opts.compactAfter,\n room: opts.room ?? (async () => undefined),\n });\n const ch = new MessageChannel();\n ch.port1.onmessage = (e) => store.handle(e.data);\n return {\n sab,\n port: ch.port2,\n store,\n /**\n * Stops serving: call it when the shard worker ends, so a store for its successor can\n * take the cells' Web Locks (it waits 20 s for them, then fails). Resolves once the\n * store's work under way has ended and its locks are released.\n */\n close() {\n ch.port1.close();\n return store.close();\n },\n };\n}\n"]}
@@ -6,6 +6,32 @@
6
6
  * shared buffer. A cell whose files pass `compactAfter` is folded into one snapshot (streamed).
7
7
  */
8
8
  import type { FilesApi } from "@statewalker/webrun-files";
9
+ /**
10
+ * Where the store's FilesApi comes from. An instance cannot cross to this worker, so it is:
11
+ * - a preset by name, on OPFS directory `dir`: "opfs-sync" (default: synchronous handles; each
12
+ * file written under a temporary name and moved into place, so a file being written is never
13
+ * read), "opfs" (OPFS's asynchronous API), "memory" (nothing kept);
14
+ * - `{ port }`: a FilesApi served elsewhere (`serveFilesApi`, on the page or in a worker of
15
+ * the app's own): File System Access, IndexedDB, S3, anything;
16
+ * - `{ module, args }`: a module this worker imports, whose default export (or
17
+ * `createFilesApi`) builds the FilesApi here from `args` (structured-cloneable: a
18
+ * FileSystemDirectoryHandle, S3 settings…), and whose optional `room(args)` says the free
19
+ * bytes there.
20
+ */
21
+ export type LtxStoreFiles = "opfs" | "opfs-sync" | "memory" | {
22
+ port: MessagePort;
23
+ } | {
24
+ module: string;
25
+ args?: unknown;
26
+ };
27
+ export interface LtxStoreInit {
28
+ op: "init";
29
+ sab: SharedArrayBuffer;
30
+ /** The OPFS directory of a preset; else the name of the storage (the Web Locks' scope). */
31
+ dir: string;
32
+ compactAfter?: number;
33
+ files?: LtxStoreFiles;
34
+ }
9
35
  export interface LtxStoreMessage {
10
36
  op: "meta" | "read" | "readRun" | "commit" | "stats";
11
37
  cell: string;
@@ -24,9 +50,19 @@ export declare function ltxCellStore(files: FilesApi, sab: SharedArrayBuffer, op
24
50
  * stores of two directories (two projects in one tab) do not take each other's locks.
25
51
  */
26
52
  lockScope?: string;
53
+ /**
54
+ * Free bytes where the files are, checked before a compaction writes its snapshot beside
55
+ * the files it replaces; undefined: unknown, compact anyway. Defaults to this origin's
56
+ * storage estimate, which is right for OPFS only: give another FilesApi its own (or
57
+ * `async () => undefined`).
58
+ */
59
+ room?: () => Promise<number | undefined>;
27
60
  }): {
28
61
  /** Answers one request of a VFS (requests are served in order). */
29
- handle(msg: LtxStoreMessage): void;
62
+ handle(msg: LtxStoreMessage & {
63
+ seq?: number;
64
+ until?: number;
65
+ }): void;
30
66
  /** Folds a cell's files into one snapshot; answers its file count after. */
31
67
  /** Folds a cell's files into one snapshot (not through the request queue: it takes its
32
68
  * own turn there to swap the files); answers the cell's file count after. */
@@ -40,6 +76,11 @@ export declare function ltxCellStore(files: FilesApi, sab: SharedArrayBuffer, op
40
76
  reads: number;
41
77
  readMs: number;
42
78
  };
79
+ /**
80
+ * Stops the store: its Web Locks released (another store may open its cells), its open
81
+ * cells forgotten. A store in a worker of its own ends with the worker instead.
82
+ */
83
+ close(): Promise<void>;
43
84
  /** The paths of a cell's current files. */
44
85
  files: (cell: string) => Promise<string[]>;
45
86
  };
@@ -51,7 +51,14 @@ export function ltxCellStore(files, sab, opts = {}) {
51
51
  * fails instead. (Across machines, celld's epochs and ownership records do this.)
52
52
  */
53
53
  const owned = new Set();
54
+ /** Ends the held Web Locks (`close()`). */
55
+ const releases = [];
56
+ /** Set by `close()`: no request served, no lock taken, no swap made after it. */
57
+ let closed = false;
58
+ const closing = new AbortController();
54
59
  const own = async (dir) => {
60
+ if (closed)
61
+ throw new Error("ltx: the store is closed");
55
62
  const locks = globalThis.navigator?.locks;
56
63
  if (!locks || owned.has(dir))
57
64
  return;
@@ -59,14 +66,19 @@ export function ltxCellStore(files, sab, opts = {}) {
59
66
  // goes, a moment later (failing at once broke the cell's Durable Object on every reload).
60
67
  const got = await new Promise((resolve) => {
61
68
  void locks
62
- .request(`cellb-ltx:${opts.lockScope ?? ""}:${dir}`, { signal: AbortSignal.timeout(20_000) }, () => {
69
+ .request(`cellb-ltx:${opts.lockScope ?? ""}:${dir}`, { signal: AbortSignal.any([AbortSignal.timeout(20_000), closing.signal]) }, () => {
63
70
  resolve(true);
64
- return new Promise(() => { }); // held while this store lives
71
+ // Held until close(); one granted after close() (a request was waiting) goes at once.
72
+ return closed
73
+ ? Promise.resolve()
74
+ : new Promise((release) => releases.push(release));
65
75
  })
66
76
  .catch(() => resolve(false));
67
77
  });
68
78
  if (!got)
69
79
  throw new Error(`ltx: cell ${dir} is open in another tab or worker (waited 20 s)`);
80
+ if (closed)
81
+ throw new Error("ltx: the store is closed");
70
82
  owned.add(dir);
71
83
  };
72
84
  const cellOf = async (cell) => {
@@ -75,18 +87,43 @@ export function ltxCellStore(files, sab, opts = {}) {
75
87
  const first = !owned.has(dir);
76
88
  await own(dir);
77
89
  const paths = await list(dir, first);
78
- cells.set(cell, paths.length ? await openPagedCell(files, paths) : undefined);
90
+ const opened = paths.length ? await openPagedCell(files, paths) : undefined;
91
+ // An interrupted compaction's leftovers, which its snapshot covers: their space back
92
+ // (this store owns the cell, so no one else reads them).
93
+ if (opened?.superseded.length)
94
+ for (const p of opened.superseded)
95
+ await files.remove(p).catch(() => false);
96
+ if (opened)
97
+ opened.superseded = [];
98
+ cells.set(cell, opened);
79
99
  }
80
100
  return cells.get(cell);
81
101
  };
102
+ /**
103
+ * The request being answered's sequence number (the VFS's `seq`), tagged onto the state
104
+ * word: a VFS that gave up on a request (its 30 s wait) and sent the next one ignores the
105
+ * late answer instead of reading it as the next one's.
106
+ */
107
+ let answering = 0;
108
+ /**
109
+ * The number of the newest request received (set as it arrives, before its turn in the
110
+ * queue). The VFS has one request in flight unless it gave up on one, so a request whose
111
+ * number is not the newest was abandoned: not served, and a commit not kept (SQLite rolled
112
+ * that transaction back when the VFS reported the failure).
113
+ */
114
+ let newestSeq;
82
115
  const answer = (bytes, state = 1) => {
83
116
  payload.set(bytes);
84
117
  ctl[1] = bytes.length;
85
- Atomics.store(ctl, 0, state);
118
+ Atomics.store(ctl, 0, answering ? (answering << 2) | state : state);
86
119
  Atomics.notify(ctl, 0);
87
120
  };
88
121
  const enc = new TextEncoder();
89
122
  let queue = Promise.resolve();
123
+ /** `task` in the request queue (between two requests): the cell views change only there. */
124
+ const inQueue = (task) => new Promise((resolve, reject) => {
125
+ queue = queue.then(() => task().then(resolve, reject));
126
+ });
90
127
  /** The request being served and its stage: a watchdog names the one that does not end. */
91
128
  const current = { op: "", cell: "", stage: "", since: 0 };
92
129
  const stage = (s) => {
@@ -109,7 +146,7 @@ export function ltxCellStore(files, sab, opts = {}) {
109
146
  if (running)
110
147
  return running;
111
148
  const job = (async () => {
112
- const c = await cellOf(cell);
149
+ const c = await inQueue(() => cellOf(cell));
113
150
  if (!c || c.files.length < 2)
114
151
  return c?.files.length ?? 0;
115
152
  const dir = dirOf(cell);
@@ -117,11 +154,13 @@ export function ltxCellStore(files, sab, opts = {}) {
117
154
  // The new snapshot exists beside the files it replaces until the swap: without the room
118
155
  // for it, the compaction would only fill the disk (and fail the commits after it).
119
156
  const needed = pageBytes(c);
120
- const free = await room();
157
+ const free = await (opts.room ?? room)();
121
158
  if (free !== undefined && free < needed * 1.25 + (64 << 20)) {
122
159
  console.warn(`cellb ltx-store: ${cell}: no compaction, ${Math.round(free / 1e6)} MB free for a snapshot of up to ${Math.round(needed / 1e6)} MB`);
123
160
  return c.files.length;
124
161
  }
162
+ if (closed)
163
+ throw new Error("ltx: the store closed during a compaction");
125
164
  const frozen = await openPagedCell(files, replaced);
126
165
  const final = `${dir}/${ltxFileName(1n, frozen.maxTXID)}`;
127
166
  const tmp = `${final}.tmp`;
@@ -132,21 +171,28 @@ export function ltxCellStore(files, sab, opts = {}) {
132
171
  await files.remove(tmp).catch(() => false);
133
172
  throw err;
134
173
  }
135
- await new Promise((resolve, reject) => {
136
- queue = queue.then(async () => {
137
- try {
138
- for (const p of replaced)
139
- await files.remove(p);
140
- await files.move(tmp, final);
141
- cells.delete(cell);
142
- resolve();
143
- }
144
- catch (err) {
145
- reject(err);
146
- }
147
- });
174
+ // The swap and the reopen in one turn of the queue: a reopen outside it could finish
175
+ // after a commit that came between, and put back a view without that commit's file (the
176
+ // next commit would then take its TXID and overwrite it).
177
+ return inQueue(async () => {
178
+ if (closed) {
179
+ await files.remove(tmp).catch(() => false);
180
+ throw new Error("ltx: the store closed during a compaction");
181
+ }
182
+ // The snapshot in place FIRST, then the files it replaces removed: stopped between the
183
+ // two (a closed tab, a lost connection to a remote FilesApi), the cell still opens from
184
+ // the snapshot (openPagedCell skips the files it covers). The other order could leave
185
+ // only the `.tmp`, which the next open deletes: the cell lost.
186
+ await files.move(tmp, final);
187
+ try {
188
+ for (const p of replaced)
189
+ await files.remove(p);
190
+ }
191
+ finally {
192
+ cells.delete(cell); // the moved snapshot is the cell now, whatever the removes did
193
+ }
194
+ return (await cellOf(cell))?.files.length ?? 0;
148
195
  });
149
- return (await cellOf(cell))?.files.length ?? 0;
150
196
  })().finally(() => compacting.delete(cell));
151
197
  compacting.set(cell, job);
152
198
  return job;
@@ -168,7 +214,15 @@ export function ltxCellStore(files, sab, opts = {}) {
168
214
  return {
169
215
  /** Answers one request of a VFS (requests are served in order). */
170
216
  handle(msg) {
217
+ if (msg.seq !== undefined)
218
+ newestSeq = msg.seq;
219
+ const abandoned = () => msg.seq !== undefined && msg.seq !== newestSeq;
171
220
  queue = queue.then(async () => {
221
+ if (abandoned())
222
+ return;
223
+ answering = msg.seq ?? 0;
224
+ if (closed)
225
+ return answer(enc.encode("ltx: the store is closed"), 2);
172
226
  Object.assign(current, {
173
227
  op: msg.op,
174
228
  cell: msg.cell,
@@ -233,6 +287,16 @@ export function ltxCellStore(files, sab, opts = {}) {
233
287
  yield* pages;
234
288
  })(), 0n, written));
235
289
  const t1 = performance.now();
290
+ // Given up on while it was written (a newer request came), or written too late
291
+ // for the VFS to still be waiting (its `until`, less a second for the append and
292
+ // the answer): not a transaction of the cell, since SQLite rolled it back.
293
+ const late = msg.until !== undefined && Date.now() > msg.until - 1000;
294
+ if (abandoned() || late) {
295
+ await files.remove(path).catch(() => false);
296
+ if (abandoned())
297
+ return;
298
+ throw new Error(`ltx: commit of ${msg.cell} took too long; not kept`);
299
+ }
236
300
  stage("append");
237
301
  // The first commit is the snapshot; later ones extend the overlay by one file.
238
302
  if (c)
@@ -272,8 +336,24 @@ export function ltxCellStore(files, sab, opts = {}) {
272
336
  * own turn there to swap the files); answers the cell's file count after. */
273
337
  compact: (cell) => compact(cell),
274
338
  stats,
339
+ /**
340
+ * Stops the store: its Web Locks released (another store may open its cells), its open
341
+ * cells forgotten. A store in a worker of its own ends with the worker instead.
342
+ */
343
+ async close() {
344
+ closed = true;
345
+ closing.abort(); // a request waiting for a cell's Web Lock stops waiting
346
+ // What is under way ends first (a compaction's swap is refused), then the locks go.
347
+ await Promise.allSettled([queue, ...compacting.values()]);
348
+ await queue.catch(() => { });
349
+ clearInterval(watchdog);
350
+ for (const release of releases.splice(0))
351
+ release();
352
+ owned.clear();
353
+ cells.clear();
354
+ },
275
355
  /** The paths of a cell's current files. */
276
- files: async (cell) => (await cellOf(cell))?.files.map((f) => f.path) ?? [],
356
+ files: (cell) => inQueue(async () => (await cellOf(cell))?.files.map((f) => f.path) ?? []),
277
357
  };
278
358
  }
279
359
  /** One cell at `dir` (the earlier single-cell form): messages may omit `cell`. */
@@ -1 +1 @@
1
- {"version":3,"file":"ltx-page-server.js","sourceRoot":"","sources":["../../src/ltx/ltx-page-server.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,OAAO,EAAE,MAAM,yBAAyB,CAAC;AAClD,OAAO,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AACvC,OAAO,EACL,kBAAkB,EAClB,eAAe,EACf,iBAAiB,EACjB,aAAa,GAEd,MAAM,gBAAgB,CAAC;AAExB,qFAAqF;AACrF,MAAM,SAAS,GAAG,CAAC,CAAY,EAAE,EAAE,CACjC,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,IAAI,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;AAE9F,6FAA6F;AAC7F,KAAK,UAAU,IAAI;IACjB,MAAM,OAAO,GAAI,UAAU,CAAC,SAAsD,EAAE,OAAO,CAAC;IAC5F,IAAI,CAAC,OAAO,EAAE,QAAQ;QAAE,OAAO,SAAS,CAAC;IACzC,MAAM,EAAE,KAAK,GAAG,CAAC,EAAE,KAAK,EAAE,GAAG,MAAM,OAAO,CAAC,QAAQ,EAAE,CAAC;IACtD,OAAO,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,KAAK,GAAG,KAAK,CAAC;AACzD,CAAC;AAYD,MAAM,UAAU,YAAY,CAC1B,KAAe,EACf,GAAsB,EACtB,IAAI,GASA,EAAE;IAEN,MAAM,GAAG,GAAG,IAAI,UAAU,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;IACtC,MAAM,OAAO,GAAG,IAAI,UAAU,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;IACvC,MAAM,IAAI,GAAG,CAAC,IAAI,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;IAClD,MAAM,YAAY,GAAG,IAAI,CAAC,YAAY,IAAI,EAAE,CAAC;IAC7C,yFAAyF;IACzF,MAAM,KAAK,GAAG,CAAC,IAAY,EAAE,EAAE,CAC7B,GAAG,IAAI,UAAU,OAAO,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,QAAQ,EAAE,GAAG,CAAC,cAAc,CAAC;IACtE,MAAM,KAAK,GAAG,IAAI,GAAG,EAAiC,CAAC;IACvD,oDAAoD;IACpD,MAAM,KAAK,GAAG;QACZ,OAAO,EAAE,CAAC;QACV,OAAO,EAAE,CAAC;QACV,QAAQ,EAAE,CAAC;QACX,WAAW,EAAE,CAAC;QACd,SAAS,EAAE,CAAC;QACZ,KAAK,EAAE,CAAC;QACR,MAAM,EAAE,CAAC;KACV,CAAC;IACF,MAAM,IAAI,GAAG,KAAK,EAAE,GAAW,EAAE,OAAO,GAAG,KAAK,EAAE,EAAE;QAClD,MAAM,GAAG,GAAa,EAAE,CAAC;QACzB,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YAAE,OAAO,GAAG,CAAC;QAC3C,IAAI,KAAK,EAAE,MAAM,CAAC,IAAI,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;YACtC,IAAI,CAAC,CAAC,IAAI,KAAK,MAAM;gBAAE,SAAS;YAChC,IAAI,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC;gBAAE,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;YAC9C,sFAAsF;YACtF,+CAA+C;iBAC1C,IAAI,OAAO,IAAI,wBAAwB,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC;gBACvD,MAAM,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,CAAC;QAClD,CAAC;QACD,OAAO,GAAG,CAAC;IACb,CAAC,CAAC;IACF;;;;OAIG;IACH,MAAM,KAAK,GAAG,IAAI,GAAG,EAAU,CAAC;IAChC,MAAM,GAAG,GAAG,KAAK,EAAE,GAAW,EAAE,EAAE;QAChC,MAAM,KAAK,GAAI,UAAU,CAAC,SAAiD,EAAE,KAAK,CAAC;QACnF,IAAI,CAAC,KAAK,IAAI,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC;YAAE,OAAO;QACrC,yFAAyF;QACzF,0FAA0F;QAC1F,MAAM,GAAG,GAAG,MAAM,IAAI,OAAO,CAAU,CAAC,OAAO,EAAE,EAAE;YACjD,KAAK,KAAK;iBACP,OAAO,CACN,aAAa,IAAI,CAAC,SAAS,IAAI,EAAE,IAAI,GAAG,EAAE,EAC1C,EAAE,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,EACvC,GAAG,EAAE;gBACH,OAAO,CAAC,IAAI,CAAC,CAAC;gBACd,OAAO,IAAI,OAAO,CAAO,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC,CAAC,8BAA8B;YACpE,CAAC,CACF;iBACA,KAAK,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC;QACjC,CAAC,CAAC,CAAC;QACH,IAAI,CAAC,GAAG;YAAE,MAAM,IAAI,KAAK,CAAC,aAAa,GAAG,iDAAiD,CAAC,CAAC;QAC7F,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IACjB,CAAC,CAAC;IACF,MAAM,MAAM,GAAG,KAAK,EAAE,IAAY,EAAE,EAAE;QACpC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;YACrB,MAAM,GAAG,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC;YACxB,MAAM,KAAK,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YAC9B,MAAM,GAAG,CAAC,GAAG,CAAC,CAAC;YACf,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;YACrC,KAAK,CAAC,GAAG,CAAC,IAAI,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,aAAa,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;QAChF,CAAC;QACD,OAAO,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IACzB,CAAC,CAAC;IACF,MAAM,MAAM,GAAG,CAAC,KAAiB,EAAE,KAAK,GAAG,CAAC,EAAE,EAAE;QAC9C,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QACnB,GAAG,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,MAAM,CAAC;QACtB,OAAO,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAE,KAAK,CAAC,CAAC;QAC7B,OAAO,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;IACzB,CAAC,CAAC;IACF,MAAM,GAAG,GAAG,IAAI,WAAW,EAAE,CAAC;IAC9B,IAAI,KAAK,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC;IAC9B,0FAA0F;IAC1F,MAAM,OAAO,GAAG,EAAE,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC;IAC1D,MAAM,KAAK,GAAG,CAAC,CAAS,EAAE,EAAE;QAC1B,OAAO,CAAC,KAAK,GAAG,CAAC,CAAC;IACpB,CAAC,CAAC;IACF,MAAM,QAAQ,GAAG,WAAW,CAAC,GAAG,EAAE;QAChC,IAAI,OAAO,CAAC,EAAE,IAAI,WAAW,CAAC,GAAG,EAAE,GAAG,OAAO,CAAC,KAAK,GAAG,MAAM;YAC1D,OAAO,CAAC,IAAI,CACV,oBAAoB,OAAO,CAAC,EAAE,OAAO,OAAO,CAAC,IAAI,QAAQ,OAAO,CAAC,KAAK,SAAS,IAAI,CAAC,KAAK,CAAC,CAAC,WAAW,CAAC,GAAG,EAAE,GAAG,OAAO,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,IAAI,CAC1I,CAAC;IACN,CAAC,EAAE,MAAM,CAAC,CAAC;IACV,QAAmC,CAAC,KAAK,EAAE,EAAE,CAAC;IAE/C;;;;;OAKG;IACH,MAAM,UAAU,GAAG,IAAI,GAAG,EAA2B,CAAC;IACtD,MAAM,OAAO,GAAG,CAAC,IAAY,EAAmB,EAAE;QAChD,MAAM,OAAO,GAAG,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACrC,IAAI,OAAO;YAAE,OAAO,OAAO,CAAC;QAC5B,MAAM,GAAG,GAAG,CAAC,KAAK,IAAI,EAAE;YACtB,MAAM,CAAC,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,CAAC;YAC7B,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC;gBAAE,OAAO,CAAC,EAAE,KAAK,CAAC,MAAM,IAAI,CAAC,CAAC;YAC1D,MAAM,GAAG,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC;YACxB,MAAM,QAAQ,GAAG,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;YAC5C,wFAAwF;YACxF,mFAAmF;YACnF,MAAM,MAAM,GAAG,SAAS,CAAC,CAAC,CAAC,CAAC;YAC5B,MAAM,IAAI,GAAG,MAAM,IAAI,EAAE,CAAC;YAC1B,IAAI,IAAI,KAAK,SAAS,IAAI,IAAI,GAAG,MAAM,GAAG,IAAI,GAAG,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC;gBAC5D,OAAO,CAAC,IAAI,CACV,oBAAoB,IAAI,oBAAoB,IAAI,CAAC,KAAK,CAAC,IAAI,GAAG,GAAG,CAAC,oCAAoC,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,GAAG,CAAC,KAAK,CACpI,CAAC;gBACF,OAAO,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC;YACxB,CAAC;YACD,MAAM,MAAM,GAAG,MAAM,aAAa,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC;YACpD,MAAM,KAAK,GAAG,GAAG,GAAG,IAAI,WAAW,CAAC,EAAE,EAAE,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC;YAC1D,MAAM,GAAG,GAAG,GAAG,KAAK,MAAM,CAAC;YAC3B,IAAI,CAAC;gBACH,MAAM,kBAAkB,CAAC,MAAM,EAAE,KAAK,EAAE,GAAG,CAAC,CAAC,CAAC,2CAA2C;YAC3F,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,MAAM,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,CAAC;gBAC3C,MAAM,GAAG,CAAC;YACZ,CAAC;YACD,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;gBAC1C,KAAK,GAAG,KAAK,CAAC,IAAI,CAAC,KAAK,IAAI,EAAE;oBAC5B,IAAI,CAAC;wBACH,KAAK,MAAM,CAAC,IAAI,QAAQ;4BAAE,MAAM,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;wBAChD,MAAM,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;wBAC7B,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;wBACnB,OAAO,EAAE,CAAC;oBACZ,CAAC;oBAAC,OAAO,GAAG,EAAE,CAAC;wBACb,MAAM,CAAC,GAAG,CAAC,CAAC;oBACd,CAAC;gBACH,CAAC,CAAC,CAAC;YACL,CAAC,CAAC,CAAC;YACH,OAAO,CAAC,MAAM,MAAM,CAAC,IAAI,CAAC,CAAC,EAAE,KAAK,CAAC,MAAM,IAAI,CAAC,CAAC;QACjD,CAAC,CAAC,EAAE,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC;QAC5C,UAAU,CAAC,GAAG,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;QAC1B,OAAO,GAAG,CAAC;IACb,CAAC,CAAC;IAEF;;;OAGG;IACH,MAAM,UAAU,GAAG,IAAI,GAAG,EAAkB,CAAC;IAC7C,MAAM,eAAe,GAAG,CAAC,IAAY,EAAE,CAAY,EAAE,EAAE;QACrD,IAAI,IAAI,CAAC,WAAW,KAAK,KAAK;YAAE,OAAO,KAAK,CAAC;QAC7C,IAAI,CAAC,CAAC,KAAK,CAAC,MAAM,IAAI,YAAY;YAAE,OAAO,IAAI,CAAC;QAChD,MAAM,QAAQ,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;QAC5B,MAAM,aAAa,GAAG,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,IAAI,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,IAAI,EAAE,CAAC,CAAC,CAAC;QACxF,OAAO,CAAC,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,aAAa,GAAG,CAAC,CAAC,CAAC;IAClG,CAAC,CAAC;IAEF,OAAO;QACL,mEAAmE;QACnE,MAAM,CAAC,GAAoB;YACzB,KAAK,GAAG,KAAK,CAAC,IAAI,CAAC,KAAK,IAAI,EAAE;gBAC5B,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE;oBACrB,EAAE,EAAE,GAAG,CAAC,EAAE;oBACV,IAAI,EAAE,GAAG,CAAC,IAAI;oBACd,KAAK,EAAE,MAAM;oBACb,KAAK,EAAE,WAAW,CAAC,GAAG,EAAE;iBACzB,CAAC,CAAC;gBACH,IAAI,CAAC;oBACH,MAAM,CAAC,GAAG,MAAM,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;oBACjC,KAAK,CAAC,OAAO,CAAC,CAAC;oBACf,IAAI,GAAG,CAAC,EAAE,KAAK,MAAM;wBACnB,OAAO,MAAM,CACX,GAAG,CAAC,MAAM,CACR,IAAI,CAAC,SAAS,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,QAAQ,IAAI,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,SAAS,IAAI,CAAC,EAAE,CAAC,CAC7E,CACF,CAAC;oBACJ,oFAAoF;oBACpF,MAAM,OAAO,GAAG,KAAK,EAAK,IAAkC,EAAc,EAAE;wBAC1E,IAAI,CAAC;4BACH,OAAO,MAAM,IAAI,CAAC,CAAc,CAAC,CAAC;wBACpC,CAAC;wBAAC,OAAO,GAAG,EAAE,CAAC;4BACb,IAAI,CAAC,CAAC,GAAG,YAAY,iBAAiB,CAAC;gCAAE,MAAM,GAAG,CAAC;4BACnD,OAAO,CAAC,IAAI,CAAC,oBAAoB,GAAG,CAAC,IAAI,KAAK,GAAG,CAAC,OAAO,sBAAsB,CAAC,CAAC;4BACjF,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;4BACvB,KAAK,CAAC,QAAQ,CAAC,CAAC;4BAChB,OAAO,IAAI,CAAC,CAAC,MAAM,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAc,CAAC,CAAC;wBACrD,CAAC;oBACH,CAAC,CAAC;oBACF,IAAI,GAAG,CAAC,EAAE,KAAK,MAAM;wBACnB,OAAO,MAAM,CAAC,MAAM,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAc,CAAC,CAAC,CAAC,CAAC;oBACtE,IAAI,GAAG,CAAC,EAAE,KAAK,SAAS,EAAE,CAAC;wBACzB,KAAK,CAAC,KAAK,EAAE,CAAC;wBACd,MAAM,EAAE,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC;wBAC7B,IAAI,CAAC;4BACH,MAAM,GAAG,GAAG,MAAM,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,IAAc,EAAE,GAAG,CAAC,KAAe,CAAC,CAAC,CAAC;4BACrF,MAAM,IAAI,GAAI,CAAe,CAAC,QAAQ,CAAC;4BACvC,MAAM,GAAG,GAAG,IAAI,UAAU,CAAC,GAAG,CAAC,IAAI,GAAG,IAAI,CAAC,CAAC;4BAC5C,MAAM,KAAK,GAAG,CAAC,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;4BACpD,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,KAAK,CAAC,OAAO,EAAE;gCAAE,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAe,EAAE,CAAC,GAAG,IAAI,CAAC,CAAC;4BAClF,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC;wBACrB,CAAC;gCAAS,CAAC;4BACT,KAAK,CAAC,MAAM,IAAI,WAAW,CAAC,GAAG,EAAE,GAAG,EAAE,CAAC;wBACzC,CAAC;oBACH,CAAC;oBACD,IAAI,GAAG,CAAC,EAAE,KAAK,OAAO;wBAAE,OAAO,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;oBACzE,IAAI,GAAG,CAAC,EAAE,KAAK,QAAQ,EAAE,CAAC;wBACxB,MAAM,EAAE,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC;wBAC7B,MAAM,IAAI,GAAG,CAAC,CAAC,EAAE,OAAO,IAAI,EAAE,CAAC,GAAG,EAAE,CAAC;wBACrC,MAAM,IAAI,GAAG,GAAG,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,WAAW,CAAC,IAAI,EAAE,IAAI,CAAC,EAAE,CAAC;wBAC7D,MAAM,KAAK,GAAG,GAAG,CAAC,KAA+B,CAAC;wBAClD,MAAM,OAAO,GAAG,EAAE,KAAK,EAAE,IAAI,GAAG,EAA4B,EAAE,CAAC;wBAC/D,MAAM,MAAM,GAAG;4BACb,QAAQ,EAAE,GAAG,CAAC,QAAkB;4BAChC,MAAM,EAAE,GAAG,CAAC,SAAmB;4BAC/B,OAAO,EAAE,IAAI;4BACb,OAAO,EAAE,IAAI;4BACb,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE;4BACrB,gBAAgB,EAAE,EAAE;yBACrB,CAAC;wBACF,KAAK,CAAC,SAAS,IAAI,EAAE,CAAC,CAAC;wBACvB,MAAM,KAAK,CAAC,KAAK,CACf,IAAI,EACJ,eAAe,CACb,MAAM,EACN,CAAC,KAAK,SAAS,CAAC;4BACd,KAAK,CAAC,CAAC,KAAK,CAAC;wBACf,CAAC,CAAC,EAAE,EACJ,EAAE,EACF,OAAO,CACR,CACF,CAAC;wBACF,MAAM,EAAE,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC;wBAC7B,KAAK,CAAC,QAAQ,CAAC,CAAC;wBAChB,+EAA+E;wBAC/E,IAAI,CAAC;4BAAE,MAAM,CAAC,CAAC,MAAM,CAAC,IAAI,EAAE,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,GAAG,MAAM,EAAE,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC;;4BAClF,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;wBAC5B,MAAM,CAAC,IAAI,UAAU,EAAE,CAAC,CAAC;wBACzB,KAAK,CAAC,OAAO,EAAE,CAAC;wBAChB,KAAK,CAAC,OAAO,IAAI,EAAE,GAAG,EAAE,CAAC;wBACzB,KAAK,CAAC,QAAQ,IAAI,WAAW,CAAC,GAAG,EAAE,GAAG,EAAE,CAAC;wBACzC,oFAAoF;wBACpF,UAAU,CAAC,GAAG,CACZ,GAAG,CAAC,IAAI,EACR,CAAC,UAAU,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;4BAC7B,CAAC,GAAG,OAAO,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,IAAI,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,IAAI,EAAE,CAAC,CAAC,CACnE,CAAC;wBACF,IAAI,CAAC,IAAI,eAAe,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;4BACnE,MAAM,EAAE,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC;4BAC7B,UAAU,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;4BAC5B,KAAK,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,IAAI,CACzB,GAAG,EAAE;gCACH,KAAK,CAAC,SAAS,IAAI,WAAW,CAAC,GAAG,EAAE,GAAG,EAAE,CAAC;4BAC5C,CAAC,EACD,CAAC,GAAG,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,kCAAkC,GAAG,CAAC,IAAI,UAAU,EAAE,GAAG,CAAC,CACjF,CAAC;4BACF,KAAK,CAAC,WAAW,EAAE,CAAC;wBACtB,CAAC;wBACD,OAAO;oBACT,CAAC;oBACD,MAAM,IAAI,KAAK,CAAC,cAAc,GAAG,CAAC,EAAE,EAAE,CAAC,CAAC;gBAC1C,CAAC;gBAAC,OAAO,GAAG,EAAE,CAAC;oBACb,OAAO,CAAC,IAAI,CACV,oBAAoB,GAAG,CAAC,EAAE,OAAO,GAAG,CAAC,IAAI,eAAe,OAAO,CAAC,KAAK,IAAI,EACzE,GAAG,CACJ,CAAC;oBACF,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,CAAE,GAAa,EAAE,OAAO,IAAI,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;gBAChE,CAAC;wBAAS,CAAC;oBACT,OAAO,CAAC,EAAE,GAAG,EAAE,CAAC;gBAClB,CAAC;YACH,CAAC,CAAC,CAAC;QACL,CAAC;QACD,4EAA4E;QAC5E;qFAC6E;QAC7E,OAAO,EAAE,CAAC,IAAY,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC;QACxC,KAAK;QACL,2CAA2C;QAC3C,KAAK,EAAE,KAAK,EAAE,IAAY,EAAE,EAAE,CAAC,CAAC,MAAM,MAAM,CAAC,IAAI,CAAC,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,EAAE;KACpF,CAAC;AACJ,CAAC;AAED,kFAAkF;AAClF,MAAM,UAAU,aAAa,CAAC,KAAe,EAAE,GAAW,EAAE,GAAsB;IAChF,MAAM,CAAC,GAAG,uCAAuC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC5D,MAAM,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC;IAC5B,MAAM,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;IAC/B,MAAM,KAAK,GAAG,YAAY,CAAC,KAAK,EAAE,GAAG,EAAE,EAAE,IAAI,EAAE,WAAW,EAAE,KAAK,EAAE,CAAC,CAAC;IACrE,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,MAAM,OAAO,GAAG,KAAK,IAAI,EAAE;QACzB,KAAK,GAAG,CAAC,MAAM,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC;IAC3C,CAAC,CAAC;IACF,OAAO;QACL,wFAAwF;QACxF,KAAK,EAAE,OAAO,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC;QAChC,MAAM,EAAE,CAAC,GAAsD,EAAE,EAAE;YACjE,KAAK,CAAC,MAAM,CAAC,EAAE,GAAG,GAAG,EAAE,IAAI,EAAE,IAAI,EAAqB,CAAC,CAAC;YACxD,KAAK,OAAO,EAAE,CAAC;QACjB,CAAC;QACD,OAAO,EAAE,KAAK,IAAI,EAAE;YAClB,MAAM,CAAC,GAAG,MAAM,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;YACpC,KAAK,GAAG,CAAC,CAAC;YACV,OAAO,CAAC,CAAC;QACX,CAAC;QACD,IAAI,KAAK;YACP,OAAO,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC;QACvC,CAAC;QACD,KAAK,EAAE,KAAK,CAAC,KAAK;QAClB,6DAA6D;QAC7D,SAAS,EAAE,KAAK,IAAI,EAAE,CAAC,CAAC,MAAM,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM;KACxD,CAAC;AACJ,CAAC","sourcesContent":["/**\n * The asynchronous side of the LTX VFS (src/adapters/ltx-vfs.ts): serves cells' pages from their\n * LTX files on a FilesApi and turns each commit into one LTX file there, in celld's layout\n * (`cells/<cell>/ltx/e1/0000/<min>-<max>.ltx`, a snapshot 1..N first). Runs in any thread that\n * is not one blocked on reads (a worker of its own, or a page): `handle(msg)` answers into the\n * shared buffer. A cell whose files pass `compactAfter` is folded into one snapshot (streamed).\n */\nimport type { FilesApi } from \"@statewalker/webrun-files\";\nimport { scopeOf } from \"../core/celld-schema.js\";\nimport { ltxFileName } from \"./ltx.js\";\nimport {\n compactPagedCellTo,\n encodeLtxStream,\n LtxShortReadError,\n openPagedCell,\n type PagedCell,\n} from \"./ltx-pages.js\";\n\n/** Bytes of page data a cell's files hold (what a compaction may write, at most). */\nconst pageBytes = (c: PagedCell) =>\n c.files.reduce((n, f) => n + [...f.index.values()].reduce((m, [, size]) => m + size, 0), 0);\n\n/** Free bytes in this origin's storage, or undefined where the estimate is not available. */\nasync function room(): Promise<number | undefined> {\n const storage = (globalThis.navigator as { storage?: StorageManager } | undefined)?.storage;\n if (!storage?.estimate) return undefined;\n const { usage = 0, quota } = await storage.estimate();\n return quota === undefined ? undefined : quota - usage;\n}\n\nexport interface LtxStoreMessage {\n op: \"meta\" | \"read\" | \"readRun\" | \"commit\" | \"stats\";\n cell: string;\n pgno?: number;\n count?: number;\n pageSize?: number;\n pageCount?: number;\n pages?: [number, Uint8Array][];\n}\n\nexport function ltxCellStore(\n files: FilesApi,\n sab: SharedArrayBuffer,\n opts: {\n root?: string;\n compactAfter?: number;\n autoCompact?: boolean;\n /**\n * What the files are (the OPFS directory's name, say): part of each cell's Web Lock, so the\n * stores of two directories (two projects in one tab) do not take each other's locks.\n */\n lockScope?: string;\n } = {},\n) {\n const ctl = new Int32Array(sab, 0, 2);\n const payload = new Uint8Array(sab, 8);\n const root = (opts.root ?? \"\").replace(/\\/$/, \"\");\n const compactAfter = opts.compactAfter ?? 64;\n // celld's cell directory: `<Class>:<idHex>` (scopeOf), as the LTX log and celld name it.\n const dirOf = (cell: string) =>\n `${root}/cells/${scopeOf(cell).replace(/[\\\\/]/g, \"_\")}/ltx/e1/0000`;\n const cells = new Map<string, PagedCell | undefined>();\n /** Where the store's time goes (ms), for traces. */\n const stats = {\n commits: 0,\n writeMs: 0,\n appendMs: 0,\n compactions: 0,\n compactMs: 0,\n reads: 0,\n readMs: 0,\n };\n const list = async (dir: string, cleanUp = false) => {\n const out: string[] = [];\n if (!(await files.exists(dir))) return out;\n for await (const e of files.list(dir)) {\n if (e.kind !== \"file\") continue;\n if (e.name.endsWith(\".ltx\")) out.push(e.path);\n // On this store's first open of the cell: a compaction or a write that did not finish\n // (a closed tab, a full disk), its space back.\n else if (cleanUp && /\\.ltx\\.(tmp|part-\\w+)$/.test(e.name))\n await files.remove(e.path).catch(() => false);\n }\n return out;\n };\n /**\n * One writer per cell in the origin: the store holds a Web Lock per cell directory while the\n * cell is open, so another tab's store (or worker) cannot write the same cell meanwhile; it\n * fails instead. (Across machines, celld's epochs and ownership records do this.)\n */\n const owned = new Set<string>();\n const own = async (dir: string) => {\n const locks = (globalThis.navigator as { locks?: LockManager } | undefined)?.locks;\n if (!locks || owned.has(dir)) return;\n // Waits a while for it: on a reload, the previous page's store holds it until its worker\n // goes, a moment later (failing at once broke the cell's Durable Object on every reload).\n const got = await new Promise<boolean>((resolve) => {\n void locks\n .request(\n `cellb-ltx:${opts.lockScope ?? \"\"}:${dir}`,\n { signal: AbortSignal.timeout(20_000) },\n () => {\n resolve(true);\n return new Promise<void>(() => {}); // held while this store lives\n },\n )\n .catch(() => resolve(false));\n });\n if (!got) throw new Error(`ltx: cell ${dir} is open in another tab or worker (waited 20 s)`);\n owned.add(dir);\n };\n const cellOf = async (cell: string) => {\n if (!cells.has(cell)) {\n const dir = dirOf(cell);\n const first = !owned.has(dir);\n await own(dir);\n const paths = await list(dir, first);\n cells.set(cell, paths.length ? await openPagedCell(files, paths) : undefined);\n }\n return cells.get(cell);\n };\n const answer = (bytes: Uint8Array, state = 1) => {\n payload.set(bytes);\n ctl[1] = bytes.length;\n Atomics.store(ctl, 0, state);\n Atomics.notify(ctl, 0);\n };\n const enc = new TextEncoder();\n let queue = Promise.resolve();\n /** The request being served and its stage: a watchdog names the one that does not end. */\n const current = { op: \"\", cell: \"\", stage: \"\", since: 0 };\n const stage = (s: string) => {\n current.stage = s;\n };\n const watchdog = setInterval(() => {\n if (current.op && performance.now() - current.since > 10_000)\n console.warn(\n `cellb ltx-store: ${current.op} of ${current.cell} at \"${current.stage}\" for ${Math.round((performance.now() - current.since) / 1000)} s`,\n );\n }, 10_000);\n (watchdog as { unref?: () => void }).unref?.();\n\n /**\n * Folds a cell's files into one snapshot WITHOUT holding up its requests: the snapshot is\n * written from a frozen view of the files as they are now (later commits add files after\n * them), then, between two requests, the files it replaces are removed and the cell reopened\n * (the snapshot plus the files written meanwhile). One compaction per cell at a time.\n */\n const compacting = new Map<string, Promise<number>>();\n const compact = (cell: string): Promise<number> => {\n const running = compacting.get(cell);\n if (running) return running;\n const job = (async () => {\n const c = await cellOf(cell);\n if (!c || c.files.length < 2) return c?.files.length ?? 0;\n const dir = dirOf(cell);\n const replaced = c.files.map((f) => f.path);\n // The new snapshot exists beside the files it replaces until the swap: without the room\n // for it, the compaction would only fill the disk (and fail the commits after it).\n const needed = pageBytes(c);\n const free = await room();\n if (free !== undefined && free < needed * 1.25 + (64 << 20)) {\n console.warn(\n `cellb ltx-store: ${cell}: no compaction, ${Math.round(free / 1e6)} MB free for a snapshot of up to ${Math.round(needed / 1e6)} MB`,\n );\n return c.files.length;\n }\n const frozen = await openPagedCell(files, replaced);\n const final = `${dir}/${ltxFileName(1n, frozen.maxTXID)}`;\n const tmp = `${final}.tmp`;\n try {\n await compactPagedCellTo(frozen, files, tmp); // page runs: memory stays at a run + index\n } catch (err) {\n await files.remove(tmp).catch(() => false);\n throw err;\n }\n await new Promise<void>((resolve, reject) => {\n queue = queue.then(async () => {\n try {\n for (const p of replaced) await files.remove(p);\n await files.move(tmp, final);\n cells.delete(cell);\n resolve();\n } catch (err) {\n reject(err);\n }\n });\n });\n return (await cellOf(cell))?.files.length ?? 0;\n })().finally(() => compacting.delete(cell));\n compacting.set(cell, job);\n return job;\n };\n\n /**\n * When to compact: past `compactAfter` files, or once the files after the snapshot hold more\n * bytes than half of it (a large cell is not rewritten for a few small commits).\n */\n const deltaBytes = new Map<string, number>();\n const wantsCompaction = (cell: string, c: PagedCell) => {\n if (opts.autoCompact === false) return false;\n if (c.files.length >= compactAfter) return true;\n const snapshot = c.files[0];\n const snapshotBytes = [...snapshot.index.values()].reduce((n, [, size]) => n + size, 0);\n return c.files.length > 1 && (deltaBytes.get(cell) ?? 0) > Math.max(1 << 20, snapshotBytes / 2);\n };\n\n return {\n /** Answers one request of a VFS (requests are served in order). */\n handle(msg: LtxStoreMessage) {\n queue = queue.then(async () => {\n Object.assign(current, {\n op: msg.op,\n cell: msg.cell,\n stage: \"open\",\n since: performance.now(),\n });\n try {\n const c = await cellOf(msg.cell);\n stage(\"serve\");\n if (msg.op === \"meta\")\n return answer(\n enc.encode(\n JSON.stringify({ pageSize: c?.pageSize ?? 0, pageCount: c?.pageCount ?? 0 }),\n ),\n );\n // A read of a file that changed under the cell's view: reopen the cell, read again.\n const retried = async <T>(read: (c: PagedCell) => Promise<T>): Promise<T> => {\n try {\n return await read(c as PagedCell);\n } catch (err) {\n if (!(err instanceof LtxShortReadError)) throw err;\n console.warn(`cellb ltx-store: ${msg.cell}: ${err.message}; reopening the cell`);\n cells.delete(msg.cell);\n stage(\"reopen\");\n return read((await cellOf(msg.cell)) as PagedCell);\n }\n };\n if (msg.op === \"read\")\n return answer(await retried((c) => c.readPage(msg.pgno as number)));\n if (msg.op === \"readRun\") {\n stats.reads++;\n const tr = performance.now();\n try {\n const run = await retried((c) => c.readRun(msg.pgno as number, msg.count as number));\n const size = (c as PagedCell).pageSize;\n const out = new Uint8Array(run.size * size);\n const pgnos = [...run.keys()].sort((a, b) => a - b);\n for (const [i, p] of pgnos.entries()) out.set(run.get(p) as Uint8Array, i * size);\n return answer(out);\n } finally {\n stats.readMs += performance.now() - tr;\n }\n }\n if (msg.op === \"stats\") return answer(enc.encode(JSON.stringify(stats)));\n if (msg.op === \"commit\") {\n const t0 = performance.now();\n const txid = (c?.maxTXID ?? 0n) + 1n;\n const path = `${dirOf(msg.cell)}/${ltxFileName(txid, txid)}`;\n const pages = msg.pages as [number, Uint8Array][];\n const written = { index: new Map<number, [number, number]>() };\n const header = {\n pageSize: msg.pageSize as number,\n commit: msg.pageCount as number,\n minTXID: txid,\n maxTXID: txid,\n timestamp: Date.now(),\n preApplyChecksum: 0n,\n };\n stage(`write ${path}`);\n await files.write(\n path,\n encodeLtxStream(\n header,\n (async function* () {\n yield* pages;\n })(),\n 0n,\n written,\n ),\n );\n const t1 = performance.now();\n stage(\"append\");\n // The first commit is the snapshot; later ones extend the overlay by one file.\n if (c) await c.append(path, { header: { flags: 2, ...header }, index: written.index });\n else cells.delete(msg.cell);\n answer(new Uint8Array());\n stats.commits++;\n stats.writeMs += t1 - t0;\n stats.appendMs += performance.now() - t1;\n // Past the threshold, fold the cell into one snapshot (after answering the commit).\n deltaBytes.set(\n msg.cell,\n (deltaBytes.get(msg.cell) ?? 0) +\n [...written.index.values()].reduce((n, [, size]) => n + size, 0),\n );\n if (c && wantsCompaction(msg.cell, c) && !compacting.has(msg.cell)) {\n const t2 = performance.now();\n deltaBytes.set(msg.cell, 0);\n void compact(msg.cell).then(\n () => {\n stats.compactMs += performance.now() - t2;\n },\n (err) => console.warn(`cellb ltx-store: compaction of ${msg.cell} failed:`, err),\n );\n stats.compactions++;\n }\n return;\n }\n throw new Error(`unknown op ${msg.op}`);\n } catch (err) {\n console.warn(\n `cellb ltx-store: ${msg.op} of ${msg.cell} failed at \"${current.stage}\":`,\n err,\n );\n answer(enc.encode(String((err as Error)?.message ?? err)), 2);\n } finally {\n current.op = \"\";\n }\n });\n },\n /** Folds a cell's files into one snapshot; answers its file count after. */\n /** Folds a cell's files into one snapshot (not through the request queue: it takes its\n * own turn there to swap the files); answers the cell's file count after. */\n compact: (cell: string) => compact(cell),\n stats,\n /** The paths of a cell's current files. */\n files: async (cell: string) => (await cellOf(cell))?.files.map((f) => f.path) ?? [],\n };\n}\n\n/** One cell at `dir` (the earlier single-cell form): messages may omit `cell`. */\nexport function ltxPageServer(files: FilesApi, dir: string, sab: SharedArrayBuffer) {\n const m = /^(.*)\\/cells\\/([^/]+)\\/ltx\\/e1\\/0000$/.exec(dir);\n const root = m ? m[1] : dir;\n const name = m ? m[2] : \"cell\";\n const store = ltxCellStore(files, sab, { root, autoCompact: false });\n let count = 0;\n const refresh = async () => {\n count = (await store.files(name)).length;\n };\n return {\n // A cell another store owns fails its requests (the VFS gets the error), not the start.\n ready: refresh().catch(() => {}),\n handle: (msg: Omit<LtxStoreMessage, \"cell\"> & { cell?: string }) => {\n store.handle({ ...msg, cell: name } as LtxStoreMessage);\n void refresh();\n },\n compact: async () => {\n const n = await store.compact(name);\n count = n;\n return n;\n },\n get files() {\n return Array.from({ length: count });\n },\n stats: store.stats,\n /** The cell's file count, after the requests sent so far. */\n fileCount: async () => (await store.files(name)).length,\n };\n}\n"]}
1
+ {"version":3,"file":"ltx-page-server.js","sourceRoot":"","sources":["../../src/ltx/ltx-page-server.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The asynchronous side of the LTX VFS (src/adapters/ltx-vfs.ts): serves cells' pages from their\n * LTX files on a FilesApi and turns each commit into one LTX file there, in celld's layout\n * (`cells/<cell>/ltx/e1/0000/<min>-<max>.ltx`, a snapshot 1..N first). Runs in any thread that\n * is not one blocked on reads (a worker of its own, or a page): `handle(msg)` answers into the\n * shared buffer. A cell whose files pass `compactAfter` is folded into one snapshot (streamed).\n */\nimport type { FilesApi } from \"@statewalker/webrun-files\";\nimport { scopeOf } from \"../core/celld-schema.js\";\nimport { ltxFileName } from \"./ltx.js\";\nimport {\n compactPagedCellTo,\n encodeLtxStream,\n LtxShortReadError,\n openPagedCell,\n type PagedCell,\n} from \"./ltx-pages.js\";\n\n/** Bytes of page data a cell's files hold (what a compaction may write, at most). */\nconst pageBytes = (c: PagedCell) =>\n c.files.reduce((n, f) => n + [...f.index.values()].reduce((m, [, size]) => m + size, 0), 0);\n\n/** Free bytes in this origin's storage, or undefined where the estimate is not available. */\nasync function room(): Promise<number | undefined> {\n const storage = (globalThis.navigator as { storage?: StorageManager } | undefined)?.storage;\n if (!storage?.estimate) return undefined;\n const { usage = 0, quota } = await storage.estimate();\n return quota === undefined ? undefined : quota - usage;\n}\n\n/**\n * Where the store's FilesApi comes from. An instance cannot cross to this worker, so it is:\n * - a preset by name, on OPFS directory `dir`: \"opfs-sync\" (default: synchronous handles; each\n * file written under a temporary name and moved into place, so a file being written is never\n * read), \"opfs\" (OPFS's asynchronous API), \"memory\" (nothing kept);\n * - `{ port }`: a FilesApi served elsewhere (`serveFilesApi`, on the page or in a worker of\n * the app's own): File System Access, IndexedDB, S3, anything;\n * - `{ module, args }`: a module this worker imports, whose default export (or\n * `createFilesApi`) builds the FilesApi here from `args` (structured-cloneable: a\n * FileSystemDirectoryHandle, S3 settings…), and whose optional `room(args)` says the free\n * bytes there.\n */\nexport type LtxStoreFiles =\n | \"opfs\"\n | \"opfs-sync\"\n | \"memory\"\n | { port: MessagePort }\n | { module: string; args?: unknown };\n\nexport interface LtxStoreInit {\n op: \"init\";\n sab: SharedArrayBuffer;\n /** The OPFS directory of a preset; else the name of the storage (the Web Locks' scope). */\n dir: string;\n compactAfter?: number;\n files?: LtxStoreFiles;\n}\n\nexport interface LtxStoreMessage {\n op: \"meta\" | \"read\" | \"readRun\" | \"commit\" | \"stats\";\n cell: string;\n pgno?: number;\n count?: number;\n pageSize?: number;\n pageCount?: number;\n pages?: [number, Uint8Array][];\n}\n\nexport function ltxCellStore(\n files: FilesApi,\n sab: SharedArrayBuffer,\n opts: {\n root?: string;\n compactAfter?: number;\n autoCompact?: boolean;\n /**\n * What the files are (the OPFS directory's name, say): part of each cell's Web Lock, so the\n * stores of two directories (two projects in one tab) do not take each other's locks.\n */\n lockScope?: string;\n /**\n * Free bytes where the files are, checked before a compaction writes its snapshot beside\n * the files it replaces; undefined: unknown, compact anyway. Defaults to this origin's\n * storage estimate, which is right for OPFS only: give another FilesApi its own (or\n * `async () => undefined`).\n */\n room?: () => Promise<number | undefined>;\n } = {},\n) {\n const ctl = new Int32Array(sab, 0, 2);\n const payload = new Uint8Array(sab, 8);\n const root = (opts.root ?? \"\").replace(/\\/$/, \"\");\n const compactAfter = opts.compactAfter ?? 64;\n // celld's cell directory: `<Class>:<idHex>` (scopeOf), as the LTX log and celld name it.\n const dirOf = (cell: string) =>\n `${root}/cells/${scopeOf(cell).replace(/[\\\\/]/g, \"_\")}/ltx/e1/0000`;\n const cells = new Map<string, PagedCell | undefined>();\n /** Where the store's time goes (ms), for traces. */\n const stats = {\n commits: 0,\n writeMs: 0,\n appendMs: 0,\n compactions: 0,\n compactMs: 0,\n reads: 0,\n readMs: 0,\n };\n const list = async (dir: string, cleanUp = false) => {\n const out: string[] = [];\n if (!(await files.exists(dir))) return out;\n for await (const e of files.list(dir)) {\n if (e.kind !== \"file\") continue;\n if (e.name.endsWith(\".ltx\")) out.push(e.path);\n // On this store's first open of the cell: a compaction or a write that did not finish\n // (a closed tab, a full disk), its space back.\n else if (cleanUp && /\\.ltx\\.(tmp|part-\\w+)$/.test(e.name))\n await files.remove(e.path).catch(() => false);\n }\n return out;\n };\n /**\n * One writer per cell in the origin: the store holds a Web Lock per cell directory while the\n * cell is open, so another tab's store (or worker) cannot write the same cell meanwhile; it\n * fails instead. (Across machines, celld's epochs and ownership records do this.)\n */\n const owned = new Set<string>();\n /** Ends the held Web Locks (`close()`). */\n const releases: (() => void)[] = [];\n /** Set by `close()`: no request served, no lock taken, no swap made after it. */\n let closed = false;\n const closing = new AbortController();\n const own = async (dir: string) => {\n if (closed) throw new Error(\"ltx: the store is closed\");\n const locks = (globalThis.navigator as { locks?: LockManager } | undefined)?.locks;\n if (!locks || owned.has(dir)) return;\n // Waits a while for it: on a reload, the previous page's store holds it until its worker\n // goes, a moment later (failing at once broke the cell's Durable Object on every reload).\n const got = await new Promise<boolean>((resolve) => {\n void locks\n .request(\n `cellb-ltx:${opts.lockScope ?? \"\"}:${dir}`,\n { signal: AbortSignal.any([AbortSignal.timeout(20_000), closing.signal]) },\n () => {\n resolve(true);\n // Held until close(); one granted after close() (a request was waiting) goes at once.\n return closed\n ? Promise.resolve()\n : new Promise<void>((release) => releases.push(release));\n },\n )\n .catch(() => resolve(false));\n });\n if (!got) throw new Error(`ltx: cell ${dir} is open in another tab or worker (waited 20 s)`);\n if (closed) throw new Error(\"ltx: the store is closed\");\n owned.add(dir);\n };\n const cellOf = async (cell: string) => {\n if (!cells.has(cell)) {\n const dir = dirOf(cell);\n const first = !owned.has(dir);\n await own(dir);\n const paths = await list(dir, first);\n const opened = paths.length ? await openPagedCell(files, paths) : undefined;\n // An interrupted compaction's leftovers, which its snapshot covers: their space back\n // (this store owns the cell, so no one else reads them).\n if (opened?.superseded.length)\n for (const p of opened.superseded) await files.remove(p).catch(() => false);\n if (opened) opened.superseded = [];\n cells.set(cell, opened);\n }\n return cells.get(cell);\n };\n /**\n * The request being answered's sequence number (the VFS's `seq`), tagged onto the state\n * word: a VFS that gave up on a request (its 30 s wait) and sent the next one ignores the\n * late answer instead of reading it as the next one's.\n */\n let answering = 0;\n /**\n * The number of the newest request received (set as it arrives, before its turn in the\n * queue). The VFS has one request in flight unless it gave up on one, so a request whose\n * number is not the newest was abandoned: not served, and a commit not kept (SQLite rolled\n * that transaction back when the VFS reported the failure).\n */\n let newestSeq: number | undefined;\n const answer = (bytes: Uint8Array, state = 1) => {\n payload.set(bytes);\n ctl[1] = bytes.length;\n Atomics.store(ctl, 0, answering ? (answering << 2) | state : state);\n Atomics.notify(ctl, 0);\n };\n const enc = new TextEncoder();\n let queue = Promise.resolve();\n /** `task` in the request queue (between two requests): the cell views change only there. */\n const inQueue = <T>(task: () => Promise<T>): Promise<T> =>\n new Promise<T>((resolve, reject) => {\n queue = queue.then(() => task().then(resolve, reject));\n });\n /** The request being served and its stage: a watchdog names the one that does not end. */\n const current = { op: \"\", cell: \"\", stage: \"\", since: 0 };\n const stage = (s: string) => {\n current.stage = s;\n };\n const watchdog = setInterval(() => {\n if (current.op && performance.now() - current.since > 10_000)\n console.warn(\n `cellb ltx-store: ${current.op} of ${current.cell} at \"${current.stage}\" for ${Math.round((performance.now() - current.since) / 1000)} s`,\n );\n }, 10_000);\n (watchdog as { unref?: () => void }).unref?.();\n\n /**\n * Folds a cell's files into one snapshot WITHOUT holding up its requests: the snapshot is\n * written from a frozen view of the files as they are now (later commits add files after\n * them), then, between two requests, the files it replaces are removed and the cell reopened\n * (the snapshot plus the files written meanwhile). One compaction per cell at a time.\n */\n const compacting = new Map<string, Promise<number>>();\n const compact = (cell: string): Promise<number> => {\n const running = compacting.get(cell);\n if (running) return running;\n const job = (async () => {\n const c = await inQueue(() => cellOf(cell));\n if (!c || c.files.length < 2) return c?.files.length ?? 0;\n const dir = dirOf(cell);\n const replaced = c.files.map((f) => f.path);\n // The new snapshot exists beside the files it replaces until the swap: without the room\n // for it, the compaction would only fill the disk (and fail the commits after it).\n const needed = pageBytes(c);\n const free = await (opts.room ?? room)();\n if (free !== undefined && free < needed * 1.25 + (64 << 20)) {\n console.warn(\n `cellb ltx-store: ${cell}: no compaction, ${Math.round(free / 1e6)} MB free for a snapshot of up to ${Math.round(needed / 1e6)} MB`,\n );\n return c.files.length;\n }\n if (closed) throw new Error(\"ltx: the store closed during a compaction\");\n const frozen = await openPagedCell(files, replaced);\n const final = `${dir}/${ltxFileName(1n, frozen.maxTXID)}`;\n const tmp = `${final}.tmp`;\n try {\n await compactPagedCellTo(frozen, files, tmp); // page runs: memory stays at a run + index\n } catch (err) {\n await files.remove(tmp).catch(() => false);\n throw err;\n }\n // The swap and the reopen in one turn of the queue: a reopen outside it could finish\n // after a commit that came between, and put back a view without that commit's file (the\n // next commit would then take its TXID and overwrite it).\n return inQueue(async () => {\n if (closed) {\n await files.remove(tmp).catch(() => false);\n throw new Error(\"ltx: the store closed during a compaction\");\n }\n // The snapshot in place FIRST, then the files it replaces removed: stopped between the\n // two (a closed tab, a lost connection to a remote FilesApi), the cell still opens from\n // the snapshot (openPagedCell skips the files it covers). The other order could leave\n // only the `.tmp`, which the next open deletes: the cell lost.\n await files.move(tmp, final);\n try {\n for (const p of replaced) await files.remove(p);\n } finally {\n cells.delete(cell); // the moved snapshot is the cell now, whatever the removes did\n }\n return (await cellOf(cell))?.files.length ?? 0;\n });\n })().finally(() => compacting.delete(cell));\n compacting.set(cell, job);\n return job;\n };\n\n /**\n * When to compact: past `compactAfter` files, or once the files after the snapshot hold more\n * bytes than half of it (a large cell is not rewritten for a few small commits).\n */\n const deltaBytes = new Map<string, number>();\n const wantsCompaction = (cell: string, c: PagedCell) => {\n if (opts.autoCompact === false) return false;\n if (c.files.length >= compactAfter) return true;\n const snapshot = c.files[0];\n const snapshotBytes = [...snapshot.index.values()].reduce((n, [, size]) => n + size, 0);\n return c.files.length > 1 && (deltaBytes.get(cell) ?? 0) > Math.max(1 << 20, snapshotBytes / 2);\n };\n\n return {\n /** Answers one request of a VFS (requests are served in order). */\n handle(msg: LtxStoreMessage & { seq?: number; until?: number }) {\n if (msg.seq !== undefined) newestSeq = msg.seq;\n const abandoned = () => msg.seq !== undefined && msg.seq !== newestSeq;\n queue = queue.then(async () => {\n if (abandoned()) return;\n answering = msg.seq ?? 0;\n if (closed) return answer(enc.encode(\"ltx: the store is closed\"), 2);\n Object.assign(current, {\n op: msg.op,\n cell: msg.cell,\n stage: \"open\",\n since: performance.now(),\n });\n try {\n const c = await cellOf(msg.cell);\n stage(\"serve\");\n if (msg.op === \"meta\")\n return answer(\n enc.encode(\n JSON.stringify({ pageSize: c?.pageSize ?? 0, pageCount: c?.pageCount ?? 0 }),\n ),\n );\n // A read of a file that changed under the cell's view: reopen the cell, read again.\n const retried = async <T>(read: (c: PagedCell) => Promise<T>): Promise<T> => {\n try {\n return await read(c as PagedCell);\n } catch (err) {\n if (!(err instanceof LtxShortReadError)) throw err;\n console.warn(`cellb ltx-store: ${msg.cell}: ${err.message}; reopening the cell`);\n cells.delete(msg.cell);\n stage(\"reopen\");\n return read((await cellOf(msg.cell)) as PagedCell);\n }\n };\n if (msg.op === \"read\")\n return answer(await retried((c) => c.readPage(msg.pgno as number)));\n if (msg.op === \"readRun\") {\n stats.reads++;\n const tr = performance.now();\n try {\n const run = await retried((c) => c.readRun(msg.pgno as number, msg.count as number));\n const size = (c as PagedCell).pageSize;\n const out = new Uint8Array(run.size * size);\n const pgnos = [...run.keys()].sort((a, b) => a - b);\n for (const [i, p] of pgnos.entries()) out.set(run.get(p) as Uint8Array, i * size);\n return answer(out);\n } finally {\n stats.readMs += performance.now() - tr;\n }\n }\n if (msg.op === \"stats\") return answer(enc.encode(JSON.stringify(stats)));\n if (msg.op === \"commit\") {\n const t0 = performance.now();\n const txid = (c?.maxTXID ?? 0n) + 1n;\n const path = `${dirOf(msg.cell)}/${ltxFileName(txid, txid)}`;\n const pages = msg.pages as [number, Uint8Array][];\n const written = { index: new Map<number, [number, number]>() };\n const header = {\n pageSize: msg.pageSize as number,\n commit: msg.pageCount as number,\n minTXID: txid,\n maxTXID: txid,\n timestamp: Date.now(),\n preApplyChecksum: 0n,\n };\n stage(`write ${path}`);\n await files.write(\n path,\n encodeLtxStream(\n header,\n (async function* () {\n yield* pages;\n })(),\n 0n,\n written,\n ),\n );\n const t1 = performance.now();\n // Given up on while it was written (a newer request came), or written too late\n // for the VFS to still be waiting (its `until`, less a second for the append and\n // the answer): not a transaction of the cell, since SQLite rolled it back.\n const late = msg.until !== undefined && Date.now() > msg.until - 1000;\n if (abandoned() || late) {\n await files.remove(path).catch(() => false);\n if (abandoned()) return;\n throw new Error(`ltx: commit of ${msg.cell} took too long; not kept`);\n }\n stage(\"append\");\n // The first commit is the snapshot; later ones extend the overlay by one file.\n if (c) await c.append(path, { header: { flags: 2, ...header }, index: written.index });\n else cells.delete(msg.cell);\n answer(new Uint8Array());\n stats.commits++;\n stats.writeMs += t1 - t0;\n stats.appendMs += performance.now() - t1;\n // Past the threshold, fold the cell into one snapshot (after answering the commit).\n deltaBytes.set(\n msg.cell,\n (deltaBytes.get(msg.cell) ?? 0) +\n [...written.index.values()].reduce((n, [, size]) => n + size, 0),\n );\n if (c && wantsCompaction(msg.cell, c) && !compacting.has(msg.cell)) {\n const t2 = performance.now();\n deltaBytes.set(msg.cell, 0);\n void compact(msg.cell).then(\n () => {\n stats.compactMs += performance.now() - t2;\n },\n (err) => console.warn(`cellb ltx-store: compaction of ${msg.cell} failed:`, err),\n );\n stats.compactions++;\n }\n return;\n }\n throw new Error(`unknown op ${msg.op}`);\n } catch (err) {\n console.warn(\n `cellb ltx-store: ${msg.op} of ${msg.cell} failed at \"${current.stage}\":`,\n err,\n );\n answer(enc.encode(String((err as Error)?.message ?? err)), 2);\n } finally {\n current.op = \"\";\n }\n });\n },\n /** Folds a cell's files into one snapshot; answers its file count after. */\n /** Folds a cell's files into one snapshot (not through the request queue: it takes its\n * own turn there to swap the files); answers the cell's file count after. */\n compact: (cell: string) => compact(cell),\n stats,\n /**\n * Stops the store: its Web Locks released (another store may open its cells), its open\n * cells forgotten. A store in a worker of its own ends with the worker instead.\n */\n async close() {\n closed = true;\n closing.abort(); // a request waiting for a cell's Web Lock stops waiting\n // What is under way ends first (a compaction's swap is refused), then the locks go.\n await Promise.allSettled([queue, ...compacting.values()]);\n await queue.catch(() => {});\n clearInterval(watchdog);\n for (const release of releases.splice(0)) release();\n owned.clear();\n cells.clear();\n },\n /** The paths of a cell's current files. */\n files: (cell: string) =>\n inQueue(async () => (await cellOf(cell))?.files.map((f) => f.path) ?? []),\n };\n}\n\n/** One cell at `dir` (the earlier single-cell form): messages may omit `cell`. */\nexport function ltxPageServer(files: FilesApi, dir: string, sab: SharedArrayBuffer) {\n const m = /^(.*)\\/cells\\/([^/]+)\\/ltx\\/e1\\/0000$/.exec(dir);\n const root = m ? m[1] : dir;\n const name = m ? m[2] : \"cell\";\n const store = ltxCellStore(files, sab, { root, autoCompact: false });\n let count = 0;\n const refresh = async () => {\n count = (await store.files(name)).length;\n };\n return {\n // A cell another store owns fails its requests (the VFS gets the error), not the start.\n ready: refresh().catch(() => {}),\n handle: (msg: Omit<LtxStoreMessage, \"cell\"> & { cell?: string }) => {\n store.handle({ ...msg, cell: name } as LtxStoreMessage);\n void refresh();\n },\n compact: async () => {\n const n = await store.compact(name);\n count = n;\n return n;\n },\n get files() {\n return Array.from({ length: count });\n },\n stats: store.stats,\n /** The cell's file count, after the requests sent so far. */\n fileCount: async () => (await store.files(name)).length,\n };\n}\n"]}
@@ -39,6 +39,11 @@ export interface PagedCell {
39
39
  maxTXID: bigint;
40
40
  /** The files, oldest first (the snapshot, then the incremental ones). */
41
41
  files: LtxPages[];
42
+ /**
43
+ * Older files the newest snapshot already holds (an interrupted compaction's): never read;
44
+ * a writer that owns the cell removes them.
45
+ */
46
+ superseded: string[];
42
47
  /** The page as of the newest file holding it (zeros past the database end). */
43
48
  readPage(pgno: number): Promise<Uint8Array>;
44
49
  /** Pages `from`..`from+count-1` (within the database), one range read per file they live in. */