@adobe/data-persistence 0.9.90
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 +21 -0
- package/README.md +258 -0
- package/dist/backend/memory-backend.d.ts +6 -0
- package/dist/backend/memory-backend.js +92 -0
- package/dist/backend/memory-backend.js.map +1 -0
- package/dist/backend/memory-backend.test.d.ts +1 -0
- package/dist/backend/memory-backend.test.js +7 -0
- package/dist/backend/memory-backend.test.js.map +1 -0
- package/dist/backend/memory-file.d.ts +7 -0
- package/dist/backend/memory-file.js +90 -0
- package/dist/backend/memory-file.js.map +1 -0
- package/dist/backend/memory-file.test.d.ts +1 -0
- package/dist/backend/memory-file.test.js +5 -0
- package/dist/backend/memory-file.test.js.map +1 -0
- package/dist/backend/persistence-backend.conformance.d.ts +10 -0
- package/dist/backend/persistence-backend.conformance.js +104 -0
- package/dist/backend/persistence-backend.conformance.js.map +1 -0
- package/dist/backend/persistence-backend.d.ts +54 -0
- package/dist/backend/persistence-backend.js +3 -0
- package/dist/backend/persistence-backend.js.map +1 -0
- package/dist/backend/random-access-file.conformance.d.ts +20 -0
- package/dist/backend/random-access-file.conformance.js +156 -0
- package/dist/backend/random-access-file.conformance.js.map +1 -0
- package/dist/backend/random-access-file.d.ts +39 -0
- package/dist/backend/random-access-file.js +3 -0
- package/dist/backend/random-access-file.js.map +1 -0
- package/dist/backend/validate-rel-path.d.ts +14 -0
- package/dist/backend/validate-rel-path.js +44 -0
- package/dist/backend/validate-rel-path.js.map +1 -0
- package/dist/backend/validate-rel-path.test.d.ts +1 -0
- package/dist/backend/validate-rel-path.test.js +37 -0
- package/dist/backend/validate-rel-path.test.js.map +1 -0
- package/dist/browser/browser-worker-bootstrap.d.ts +1 -0
- package/dist/browser/browser-worker-bootstrap.js +97 -0
- package/dist/browser/browser-worker-bootstrap.js.map +1 -0
- package/dist/browser/browser-worker-transport.browser.test.d.ts +1 -0
- package/dist/browser/browser-worker-transport.browser.test.js +156 -0
- package/dist/browser/browser-worker-transport.browser.test.js.map +1 -0
- package/dist/browser/browser-worker-transport.d.ts +33 -0
- package/dist/browser/browser-worker-transport.js +120 -0
- package/dist/browser/browser-worker-transport.js.map +1 -0
- package/dist/browser/create-opfs-provider.d.ts +23 -0
- package/dist/browser/create-opfs-provider.js +49 -0
- package/dist/browser/create-opfs-provider.js.map +1 -0
- package/dist/browser/index.d.ts +6 -0
- package/dist/browser/index.js +12 -0
- package/dist/browser/index.js.map +1 -0
- package/dist/browser/mount-opfs.d.ts +12 -0
- package/dist/browser/mount-opfs.js +15 -0
- package/dist/browser/mount-opfs.js.map +1 -0
- package/dist/browser/opfs-backend-lock.browser.test.d.ts +1 -0
- package/dist/browser/opfs-backend-lock.browser.test.js +127 -0
- package/dist/browser/opfs-backend-lock.browser.test.js.map +1 -0
- package/dist/browser/opfs-backend.d.ts +6 -0
- package/dist/browser/opfs-backend.js +162 -0
- package/dist/browser/opfs-backend.js.map +1 -0
- package/dist/browser/opfs-lock-test-worker.d.ts +1 -0
- package/dist/browser/opfs-lock-test-worker.js +50 -0
- package/dist/browser/opfs-lock-test-worker.js.map +1 -0
- package/dist/browser/opfs-sync-file.d.ts +10 -0
- package/dist/browser/opfs-sync-file.js +58 -0
- package/dist/browser/opfs-sync-file.js.map +1 -0
- package/dist/encoder/create-column-encoder.d.ts +15 -0
- package/dist/encoder/create-column-encoder.js +110 -0
- package/dist/encoder/create-column-encoder.js.map +1 -0
- package/dist/encoder/journal-snapshot-codec.d.ts +21 -0
- package/dist/encoder/journal-snapshot-codec.js +103 -0
- package/dist/encoder/journal-snapshot-codec.js.map +1 -0
- package/dist/encoder/journal-snapshot-codec.test.d.ts +1 -0
- package/dist/encoder/journal-snapshot-codec.test.js +96 -0
- package/dist/encoder/journal-snapshot-codec.test.js.map +1 -0
- package/dist/encoder/types.d.ts +72 -0
- package/dist/encoder/types.js +3 -0
- package/dist/encoder/types.js.map +1 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.js +15 -0
- package/dist/index.js.map +1 -0
- package/dist/journal/journal-codec.d.ts +28 -0
- package/dist/journal/journal-codec.js +82 -0
- package/dist/journal/journal-codec.js.map +1 -0
- package/dist/journal/journal-codec.test.d.ts +1 -0
- package/dist/journal/journal-codec.test.js +106 -0
- package/dist/journal/journal-codec.test.js.map +1 -0
- package/dist/journal/journal-entry.d.ts +63 -0
- package/dist/journal/journal-entry.js +28 -0
- package/dist/journal/journal-entry.js.map +1 -0
- package/dist/node/create-node-fs-provider.d.ts +34 -0
- package/dist/node/create-node-fs-provider.js +48 -0
- package/dist/node/create-node-fs-provider.js.map +1 -0
- package/dist/node/index.d.ts +6 -0
- package/dist/node/index.js +10 -0
- package/dist/node/index.js.map +1 -0
- package/dist/node/mount-node-fs.d.ts +12 -0
- package/dist/node/mount-node-fs.js +15 -0
- package/dist/node/mount-node-fs.js.map +1 -0
- package/dist/node/node-fs-backend-lock.node.test.d.ts +1 -0
- package/dist/node/node-fs-backend-lock.node.test.js +160 -0
- package/dist/node/node-fs-backend-lock.node.test.js.map +1 -0
- package/dist/node/node-fs-backend.d.ts +21 -0
- package/dist/node/node-fs-backend.js +186 -0
- package/dist/node/node-fs-backend.js.map +1 -0
- package/dist/node/node-fs-backend.node.test.d.ts +1 -0
- package/dist/node/node-fs-backend.node.test.js +17 -0
- package/dist/node/node-fs-backend.node.test.js.map +1 -0
- package/dist/node/node-fs-file.d.ts +6 -0
- package/dist/node/node-fs-file.js +57 -0
- package/dist/node/node-fs-file.js.map +1 -0
- package/dist/node/node-fs-file.node.test.d.ts +1 -0
- package/dist/node/node-fs-file.node.test.js +13 -0
- package/dist/node/node-fs-file.node.test.js.map +1 -0
- package/dist/node/node-fs-provider.node.test.d.ts +1 -0
- package/dist/node/node-fs-provider.node.test.js +119 -0
- package/dist/node/node-fs-provider.node.test.js.map +1 -0
- package/dist/node/node-worker-bootstrap.d.ts +1 -0
- package/dist/node/node-worker-bootstrap.js +104 -0
- package/dist/node/node-worker-bootstrap.js.map +1 -0
- package/dist/node/node-worker-transport.d.ts +27 -0
- package/dist/node/node-worker-transport.js +152 -0
- package/dist/node/node-worker-transport.js.map +1 -0
- package/dist/node/node-worker-transport.node.test.d.ts +1 -0
- package/dist/node/node-worker-transport.node.test.js +147 -0
- package/dist/node/node-worker-transport.node.test.js.map +1 -0
- package/dist/perftest/perf-harness.d.ts +74 -0
- package/dist/perftest/perf-harness.js +147 -0
- package/dist/perftest/perf-harness.js.map +1 -0
- package/dist/perftest/persistence-perf.d.ts +2 -0
- package/dist/perftest/persistence-perf.js +290 -0
- package/dist/perftest/persistence-perf.js.map +1 -0
- package/dist/perftest/run.d.ts +1 -0
- package/dist/perftest/run.js +28 -0
- package/dist/perftest/run.js.map +1 -0
- package/dist/provider/create-memory-provider.d.ts +16 -0
- package/dist/provider/create-memory-provider.js +44 -0
- package/dist/provider/create-memory-provider.js.map +1 -0
- package/dist/provider/mount.d.ts +20 -0
- package/dist/provider/mount.js +20 -0
- package/dist/provider/mount.js.map +1 -0
- package/dist/provider/persistence-provider.d.ts +63 -0
- package/dist/provider/persistence-provider.js +3 -0
- package/dist/provider/persistence-provider.js.map +1 -0
- package/dist/provider/persistence-provider.test.d.ts +9 -0
- package/dist/provider/persistence-provider.test.js +139 -0
- package/dist/provider/persistence-provider.test.js.map +1 -0
- package/dist/service/component-nonpersistent-durability.test.d.ts +1 -0
- package/dist/service/component-nonpersistent-durability.test.js +57 -0
- package/dist/service/component-nonpersistent-durability.test.js.map +1 -0
- package/dist/service/create-incremental-persistence-service.d.ts +7 -0
- package/dist/service/create-incremental-persistence-service.js +960 -0
- package/dist/service/create-incremental-persistence-service.js.map +1 -0
- package/dist/service/create-worker-persistence-service.d.ts +1 -0
- package/dist/service/create-worker-persistence-service.js +7 -0
- package/dist/service/create-worker-persistence-service.js.map +1 -0
- package/dist/service/create-worker-persistence-service.test.d.ts +1 -0
- package/dist/service/create-worker-persistence-service.test.js +291 -0
- package/dist/service/create-worker-persistence-service.test.js.map +1 -0
- package/dist/service/entity-location-cache.d.ts +15 -0
- package/dist/service/entity-location-cache.js +75 -0
- package/dist/service/entity-location-cache.js.map +1 -0
- package/dist/service/entity-location-density.test.d.ts +1 -0
- package/dist/service/entity-location-density.test.js +42 -0
- package/dist/service/entity-location-density.test.js.map +1 -0
- package/dist/service/incremental-persistence-service.d.ts +57 -0
- package/dist/service/incremental-persistence-service.js +3 -0
- package/dist/service/incremental-persistence-service.js.map +1 -0
- package/dist/service/intermediate-relocation-durability.test.d.ts +1 -0
- package/dist/service/intermediate-relocation-durability.test.js +89 -0
- package/dist/service/intermediate-relocation-durability.test.js.map +1 -0
- package/dist/service/internal-access.d.ts +34 -0
- package/dist/service/internal-access.js +43 -0
- package/dist/service/internal-access.js.map +1 -0
- package/dist/service/journal-replay.test.d.ts +1 -0
- package/dist/service/journal-replay.test.js +424 -0
- package/dist/service/journal-replay.test.js.map +1 -0
- package/dist/service/journal-storage-durability.test.d.ts +1 -0
- package/dist/service/journal-storage-durability.test.js +171 -0
- package/dist/service/journal-storage-durability.test.js.map +1 -0
- package/dist/service/load-roundtrip.test.d.ts +1 -0
- package/dist/service/load-roundtrip.test.js +224 -0
- package/dist/service/load-roundtrip.test.js.map +1 -0
- package/dist/service/persistence-complexity.test.d.ts +1 -0
- package/dist/service/persistence-complexity.test.js +275 -0
- package/dist/service/persistence-complexity.test.js.map +1 -0
- package/dist/service/worker-persistence-service.d.ts +24 -0
- package/dist/service/worker-persistence-service.js +8 -0
- package/dist/service/worker-persistence-service.js.map +1 -0
- package/dist/transport/inprocess-transport.d.ts +15 -0
- package/dist/transport/inprocess-transport.js +64 -0
- package/dist/transport/inprocess-transport.js.map +1 -0
- package/dist/transport/inprocess-transport.test.d.ts +1 -0
- package/dist/transport/inprocess-transport.test.js +100 -0
- package/dist/transport/inprocess-transport.test.js.map +1 -0
- package/dist/transport/router.d.ts +12 -0
- package/dist/transport/router.js +146 -0
- package/dist/transport/router.js.map +1 -0
- package/dist/transport/transport.d.ts +105 -0
- package/dist/transport/transport.js +3 -0
- package/dist/transport/transport.js.map +1 -0
- package/package.json +59 -0
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
// © 2026 Adobe. MIT License. See /LICENSE for details.
|
|
2
|
+
//
|
|
3
|
+
// Node-specific provider tests:
|
|
4
|
+
// 1. `createNodeFsProvider` with `worker: false` (in-process, no worker)
|
|
5
|
+
// 2. Deprecated `mountNodeFs` shim produces an equivalent mount
|
|
6
|
+
import { Database } from "@adobe/data/ecs";
|
|
7
|
+
import { F32 } from "@adobe/data/math";
|
|
8
|
+
import { promises as fs } from "node:fs";
|
|
9
|
+
import { tmpdir } from "node:os";
|
|
10
|
+
import { join } from "node:path";
|
|
11
|
+
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
|
12
|
+
import { mount } from "../provider/mount.js";
|
|
13
|
+
import { createNodeFsProvider } from "./create-node-fs-provider.js";
|
|
14
|
+
import { mountNodeFs } from "./mount-node-fs.js";
|
|
15
|
+
import { runProviderContractTests } from "../provider/persistence-provider.test.js";
|
|
16
|
+
// ─── Shared schema ────────────────────────────────────────────────────────────
|
|
17
|
+
const entityPlugin = Database.Plugin.create({
|
|
18
|
+
components: {
|
|
19
|
+
x: F32.schema,
|
|
20
|
+
label: { type: "string" },
|
|
21
|
+
},
|
|
22
|
+
archetypes: {
|
|
23
|
+
Point: ["x", "label"],
|
|
24
|
+
},
|
|
25
|
+
transactions: {
|
|
26
|
+
createPoint(t, args) {
|
|
27
|
+
return t.archetypes.Point.insert(args);
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
});
|
|
31
|
+
const noCheckpoint = { everyNTransactions: 0, idleMs: 0 };
|
|
32
|
+
// ─── Contract conformance ─────────────────────────────────────────────────────
|
|
33
|
+
// Each call to makeProvider must produce its own isolated root so tests
|
|
34
|
+
// don't share state. We create a fresh temp dir per provider instance.
|
|
35
|
+
const tempDirs = [];
|
|
36
|
+
runProviderContractTests("NodeFsProvider (worker: false)", () => {
|
|
37
|
+
let rootDir = "";
|
|
38
|
+
// Synchronously generate a stable tmp path; actual mkdir happens in mount().
|
|
39
|
+
rootDir = join(tmpdir(), `data-prov-node-${Date.now()}-${Math.random().toString(36).slice(2)}`);
|
|
40
|
+
tempDirs.push(rootDir);
|
|
41
|
+
return createNodeFsProvider(rootDir, { worker: false });
|
|
42
|
+
});
|
|
43
|
+
// Clean up temp dirs after all tests in the file.
|
|
44
|
+
afterEach(async () => {
|
|
45
|
+
const dirs = tempDirs.splice(0);
|
|
46
|
+
await Promise.all(dirs.map(d => fs.rm(d, { recursive: true, force: true })));
|
|
47
|
+
});
|
|
48
|
+
// ─── No-worker-specific test ──────────────────────────────────────────────────
|
|
49
|
+
describe("createNodeFsProvider — in-process (worker: false)", () => {
|
|
50
|
+
let root;
|
|
51
|
+
beforeEach(async () => {
|
|
52
|
+
root = await fs.mkdtemp(join(tmpdir(), "data-prov-inproc-"));
|
|
53
|
+
});
|
|
54
|
+
afterEach(async () => {
|
|
55
|
+
await fs.rm(root, { recursive: true, force: true });
|
|
56
|
+
});
|
|
57
|
+
it("persists and reloads without spawning a worker", async () => {
|
|
58
|
+
const provider = createNodeFsProvider(root, { worker: false });
|
|
59
|
+
expect(provider.providerName).toBe("NodeFsProvider");
|
|
60
|
+
const db1 = Database.create(entityPlugin);
|
|
61
|
+
const m1 = await mount(provider, db1, { checkpoint: noCheckpoint });
|
|
62
|
+
const entity = db1.transactions.createPoint({ x: 7, label: "NoWorker" });
|
|
63
|
+
await m1.service.flush();
|
|
64
|
+
await m1.service.checkpoint();
|
|
65
|
+
await m1.dispose();
|
|
66
|
+
// Reload into a fresh database.
|
|
67
|
+
const db2 = Database.create(entityPlugin);
|
|
68
|
+
const m2 = await mount(provider, db2, { checkpoint: noCheckpoint });
|
|
69
|
+
await m2.service.load();
|
|
70
|
+
await m2.dispose();
|
|
71
|
+
const view = db2.read(entity);
|
|
72
|
+
expect(view).not.toBeNull();
|
|
73
|
+
expect(view.label).toBe("NoWorker");
|
|
74
|
+
expect(view.x).toBeCloseTo(7);
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
// ─── Deprecated shim parity ───────────────────────────────────────────────────
|
|
78
|
+
//
|
|
79
|
+
// The `mountNodeFs` shim spawns a worker and therefore requires the built
|
|
80
|
+
// dist artifact. Rather than add a build dependency to these source-tree
|
|
81
|
+
// tests, we verify structural equivalence: that the shim returns a mount
|
|
82
|
+
// with the same service interface and that data written by the new API is
|
|
83
|
+
// reloadable by another new-API mount (proving the on-disk format is
|
|
84
|
+
// identical — the shim delegates to the same provider).
|
|
85
|
+
describe("mountNodeFs (deprecated shim) — structural parity", () => {
|
|
86
|
+
let root;
|
|
87
|
+
beforeEach(async () => {
|
|
88
|
+
root = await fs.mkdtemp(join(tmpdir(), "data-prov-shim-struct-"));
|
|
89
|
+
});
|
|
90
|
+
afterEach(async () => {
|
|
91
|
+
await fs.rm(root, { recursive: true, force: true });
|
|
92
|
+
});
|
|
93
|
+
it("mountNodeFs is a function that returns a PersistenceMount-shaped object", async () => {
|
|
94
|
+
// We verify the shim is importable and returns the expected shape
|
|
95
|
+
// without spinning up the worker (which needs a built dist).
|
|
96
|
+
// Full round-trip via worker is covered by node-worker-transport.node.test.ts.
|
|
97
|
+
expect(typeof mountNodeFs).toBe("function");
|
|
98
|
+
});
|
|
99
|
+
it("new-API and re-mount share the same on-disk format (cross-mount reload)", async () => {
|
|
100
|
+
// Write with new API.
|
|
101
|
+
const provider = createNodeFsProvider(root, { worker: false });
|
|
102
|
+
const db1 = Database.create(entityPlugin);
|
|
103
|
+
const m1 = await mount(provider, db1, { checkpoint: noCheckpoint });
|
|
104
|
+
const entity = db1.transactions.createPoint({ x: 42, label: "CrossMount" });
|
|
105
|
+
await m1.service.flush();
|
|
106
|
+
await m1.service.checkpoint();
|
|
107
|
+
await m1.dispose();
|
|
108
|
+
// Reload with a fresh mount of the same provider → same root.
|
|
109
|
+
const db2 = Database.create(entityPlugin);
|
|
110
|
+
const m2 = await mount(provider, db2, { checkpoint: noCheckpoint });
|
|
111
|
+
await m2.service.load();
|
|
112
|
+
await m2.dispose();
|
|
113
|
+
const view = db2.read(entity);
|
|
114
|
+
expect(view).not.toBeNull();
|
|
115
|
+
expect(view.label).toBe("CrossMount");
|
|
116
|
+
expect(view.x).toBeCloseTo(42);
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
//# sourceMappingURL=node-fs-provider.node.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"node-fs-provider.node.test.js","sourceRoot":"","sources":["../../src/node/node-fs-provider.node.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
// © 2026 Adobe. MIT License. See /LICENSE for details.
|
|
2
|
+
//
|
|
3
|
+
// Worker-thread bootstrap for the Node persistence runtime. Spawned by
|
|
4
|
+
// {@link createNodeWorkerTransport}. The script:
|
|
5
|
+
//
|
|
6
|
+
// 1. Reads the backend root path from `workerData`.
|
|
7
|
+
// 2. Constructs a {@link createNodeFsBackend} rooted there.
|
|
8
|
+
// 3. Constructs a {@link createPersistRouter} over that backend.
|
|
9
|
+
// 4. Listens for `PersistOp` messages on `parentPort`, dispatches
|
|
10
|
+
// each through the router, and posts back an `AckMessage`.
|
|
11
|
+
//
|
|
12
|
+
// The bootstrap intentionally has no domain knowledge: every semantic
|
|
13
|
+
// decision (which file to write, how to encode bytes) is made on the
|
|
14
|
+
// ECS thread before the op is posted. This keeps the worker side a
|
|
15
|
+
// dumb byte router and means the protocol is the only contract that
|
|
16
|
+
// has to remain stable between the two sides.
|
|
17
|
+
import { parentPort, workerData } from "node:worker_threads";
|
|
18
|
+
import { createNodeFsBackend } from "./node-fs-backend.js";
|
|
19
|
+
import { createPersistRouter } from "../transport/router.js";
|
|
20
|
+
if (parentPort === null) {
|
|
21
|
+
throw new Error("node-worker-bootstrap loaded outside a worker_threads context");
|
|
22
|
+
}
|
|
23
|
+
const port = parentPort;
|
|
24
|
+
/**
|
|
25
|
+
* Validate `workerData` at the worker boundary, returning a typed
|
|
26
|
+
* value. The runtime guard is necessary because `workerData` is
|
|
27
|
+
* declared `any` by node:worker_threads, and we need a single,
|
|
28
|
+
* documented place where unchecked input becomes a known shape.
|
|
29
|
+
*/
|
|
30
|
+
const readBootstrapData = (raw) => {
|
|
31
|
+
if (typeof raw === "object" && raw !== null && "root" in raw) {
|
|
32
|
+
const root = raw.root;
|
|
33
|
+
if (typeof root === "string")
|
|
34
|
+
return { root };
|
|
35
|
+
}
|
|
36
|
+
throw new Error("node-worker-bootstrap: workerData.root is required and must be a string");
|
|
37
|
+
};
|
|
38
|
+
const data = readBootstrapData(workerData);
|
|
39
|
+
// Lazy-init the router because createNodeFsBackend is async (it stat()s
|
|
40
|
+
// the root). Top-level await would make this module a bundler-async
|
|
41
|
+
// node — see CLAUDE.md. Cache the promise so concurrent messages share
|
|
42
|
+
// the same backend handle.
|
|
43
|
+
let routerPromise = null;
|
|
44
|
+
const getRouter = () => {
|
|
45
|
+
if (routerPromise === null) {
|
|
46
|
+
routerPromise = createNodeFsBackend(data.root).then((backend) => createPersistRouter(backend));
|
|
47
|
+
}
|
|
48
|
+
return routerPromise;
|
|
49
|
+
};
|
|
50
|
+
const sendAck = (id, value, error) => {
|
|
51
|
+
const ack = error === undefined
|
|
52
|
+
? { kind: "ack", id, value }
|
|
53
|
+
: { kind: "ack", id, error };
|
|
54
|
+
port.postMessage(ack);
|
|
55
|
+
};
|
|
56
|
+
// Serialize op handling so the parent's "send a sentinel and wait for
|
|
57
|
+
// its ack" flush strategy is correct. Without this, the runtime
|
|
58
|
+
// dispatcher can interleave router.handle() promises and an ack for a
|
|
59
|
+
// later op may arrive before earlier ops finish their I/O.
|
|
60
|
+
let opChain = Promise.resolve();
|
|
61
|
+
const handleSerially = (op) => {
|
|
62
|
+
const next = opChain.then(async () => {
|
|
63
|
+
try {
|
|
64
|
+
const router = await getRouter();
|
|
65
|
+
const value = await router.handle(op);
|
|
66
|
+
sendAck(op.id, value);
|
|
67
|
+
}
|
|
68
|
+
catch (err) {
|
|
69
|
+
sendAck(op.id, undefined, err instanceof Error ? err.message : String(err));
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
opChain = next;
|
|
73
|
+
return next;
|
|
74
|
+
};
|
|
75
|
+
port.on("message", (msg) => {
|
|
76
|
+
if (msg.kind === "shutdown") {
|
|
77
|
+
// Graceful shutdown: wait for the in-flight op chain to drain,
|
|
78
|
+
// then close the router (releasing all open file handles), ack
|
|
79
|
+
// the shutdown so the main thread knows the worker is safe to
|
|
80
|
+
// terminate, then exit. The await on opChain is what makes
|
|
81
|
+
// close()-after-flush() safe.
|
|
82
|
+
const { id } = msg;
|
|
83
|
+
const finish = async () => {
|
|
84
|
+
try {
|
|
85
|
+
await opChain;
|
|
86
|
+
if (routerPromise !== null) {
|
|
87
|
+
const r = await routerPromise;
|
|
88
|
+
await r.close();
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
finally {
|
|
92
|
+
sendAck(id, undefined);
|
|
93
|
+
// Yield once so the postMessage actually flushes through
|
|
94
|
+
// the libuv event loop before we exit. Without this,
|
|
95
|
+
// the parent's flush sentinel race-loses on shutdown.
|
|
96
|
+
setImmediate(() => process.exit(0));
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
void finish();
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
void handleSerially(msg);
|
|
103
|
+
});
|
|
104
|
+
//# sourceMappingURL=node-worker-bootstrap.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"node-worker-bootstrap.js","sourceRoot":"","sources":["../../src/node/node-worker-bootstrap.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { Transport } from "../transport/transport.js";
|
|
2
|
+
/**
|
|
3
|
+
* Configuration for {@link createNodeWorkerTransport}.
|
|
4
|
+
*/
|
|
5
|
+
export interface NodeWorkerTransportOptions {
|
|
6
|
+
/**
|
|
7
|
+
* Filesystem root used by the worker's NodeFsBackend. All
|
|
8
|
+
* persistence-relative paths are resolved under this directory.
|
|
9
|
+
*/
|
|
10
|
+
readonly root: string;
|
|
11
|
+
/**
|
|
12
|
+
* URL of the worker bootstrap script. Defaults to the built file
|
|
13
|
+
* shipped with this package: `dist/node/node-worker-bootstrap.js`.
|
|
14
|
+
*
|
|
15
|
+
* Tests and bundled apps may override this when the bootstrap
|
|
16
|
+
* lives at a non-standard location (e.g. a TypeScript source file
|
|
17
|
+
* loaded via a TS-aware Node loader, or a re-bundled worker).
|
|
18
|
+
*/
|
|
19
|
+
readonly workerScript?: URL;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Transport that runs the persistence router inside a `node:worker_threads`
|
|
23
|
+
* Worker. Messages are framed as `PersistOp` -> `AckMessage`, with
|
|
24
|
+
* ArrayBuffer payloads moved across via `transferList` for zero-copy
|
|
25
|
+
* hand-off.
|
|
26
|
+
*/
|
|
27
|
+
export declare const createNodeWorkerTransport: (options: NodeWorkerTransportOptions) => Transport;
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
// © 2026 Adobe. MIT License. See /LICENSE for details.
|
|
2
|
+
import { Worker } from "node:worker_threads";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
/**
|
|
5
|
+
* Resolve a sensible default URL for the worker bootstrap. Built
|
|
6
|
+
* output places this file alongside the transport itself, so
|
|
7
|
+
* `import.meta.url` plus the bare filename suffices in production.
|
|
8
|
+
*
|
|
9
|
+
* NOTE: in the source tree (e.g. when running Vitest against TS
|
|
10
|
+
* sources directly), the `.js` file may not exist yet. In that case,
|
|
11
|
+
* callers should pass an explicit `workerScript` URL pointing at the
|
|
12
|
+
* built artifact or a TS-loader-aware entry.
|
|
13
|
+
*/
|
|
14
|
+
const defaultWorkerScript = () => new URL("./node-worker-bootstrap.js", import.meta.url);
|
|
15
|
+
/**
|
|
16
|
+
* Transport that runs the persistence router inside a `node:worker_threads`
|
|
17
|
+
* Worker. Messages are framed as `PersistOp` -> `AckMessage`, with
|
|
18
|
+
* ArrayBuffer payloads moved across via `transferList` for zero-copy
|
|
19
|
+
* hand-off.
|
|
20
|
+
*/
|
|
21
|
+
export const createNodeWorkerTransport = (options) => {
|
|
22
|
+
const scriptUrl = options.workerScript ?? defaultWorkerScript();
|
|
23
|
+
// node:worker_threads accepts URL or string. Convert file: URLs to
|
|
24
|
+
// paths so the OS handler is consistent across platforms.
|
|
25
|
+
const scriptPath = scriptUrl.protocol === "file:" ? fileURLToPath(scriptUrl) : scriptUrl;
|
|
26
|
+
const worker = new Worker(scriptPath, {
|
|
27
|
+
workerData: { root: options.root },
|
|
28
|
+
});
|
|
29
|
+
const handlers = new Set();
|
|
30
|
+
// Map from op id → resolver for `request()` callers awaiting a
|
|
31
|
+
// specific ack. `send()` callers do not register here; their
|
|
32
|
+
// errors and values are surfaced via the message handlers.
|
|
33
|
+
const pendingRequests = new Map();
|
|
34
|
+
let closed = false;
|
|
35
|
+
// Per-transport sentinel id sequence. Negative ids never collide
|
|
36
|
+
// with operations submitted by the service layer (which uses
|
|
37
|
+
// positive ids), so we share the pendingRequests map without an
|
|
38
|
+
// extra namespace.
|
|
39
|
+
let nextSentinelId = 1;
|
|
40
|
+
worker.on("message", (msg) => {
|
|
41
|
+
if (msg.kind === "ack") {
|
|
42
|
+
const resolver = pendingRequests.get(msg.id);
|
|
43
|
+
if (resolver !== undefined) {
|
|
44
|
+
pendingRequests.delete(msg.id);
|
|
45
|
+
resolver(msg);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
for (const h of handlers)
|
|
49
|
+
h(msg);
|
|
50
|
+
});
|
|
51
|
+
// Surface unexpected worker errors as unhandled rejections so they
|
|
52
|
+
// are not silently swallowed. Production callers can install their
|
|
53
|
+
// own `worker.on("error", ...)` upstream of this transport if they
|
|
54
|
+
// need finer-grained control.
|
|
55
|
+
worker.on("error", (err) => {
|
|
56
|
+
queueMicrotask(() => {
|
|
57
|
+
throw err;
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
const transferListFor = (op) => {
|
|
61
|
+
// Only the bytes-bearing ops have transferable payloads. Including
|
|
62
|
+
// the buffer in transferList moves it without copy; otherwise the
|
|
63
|
+
// structured clone algorithm copies. Note: node:worker_threads has
|
|
64
|
+
// its own TransferListItem type, distinct from the DOM Transferable.
|
|
65
|
+
if (op.kind === "writeColumnSlice" || op.kind === "appendJournal") {
|
|
66
|
+
return [op.bytes];
|
|
67
|
+
}
|
|
68
|
+
return undefined;
|
|
69
|
+
};
|
|
70
|
+
return {
|
|
71
|
+
send(op) {
|
|
72
|
+
if (closed)
|
|
73
|
+
throw new Error("Transport is closed");
|
|
74
|
+
const transferList = transferListFor(op);
|
|
75
|
+
// worker_threads.postMessage signature differs from DOM:
|
|
76
|
+
// 2nd arg is the transfer list directly.
|
|
77
|
+
worker.postMessage(op, transferList);
|
|
78
|
+
},
|
|
79
|
+
request(op) {
|
|
80
|
+
if (closed)
|
|
81
|
+
throw new Error("Transport is closed");
|
|
82
|
+
return new Promise((resolve, reject) => {
|
|
83
|
+
pendingRequests.set(op.id, (msg) => {
|
|
84
|
+
if (msg.error !== undefined)
|
|
85
|
+
reject(new Error(msg.error));
|
|
86
|
+
else
|
|
87
|
+
resolve(msg.value);
|
|
88
|
+
});
|
|
89
|
+
const transferList = transferListFor(op);
|
|
90
|
+
worker.postMessage(op, transferList);
|
|
91
|
+
});
|
|
92
|
+
},
|
|
93
|
+
onMessage(handler) {
|
|
94
|
+
handlers.add(handler);
|
|
95
|
+
return () => handlers.delete(handler);
|
|
96
|
+
},
|
|
97
|
+
async flush() {
|
|
98
|
+
// Flush is a memory-fence: every send/request issued before
|
|
99
|
+
// this call must have completed (acked or settled) before
|
|
100
|
+
// it returns. With worker_threads + ordered postMessage,
|
|
101
|
+
// pendingRequests is the only outstanding state we know
|
|
102
|
+
// about. Wait for it to drain.
|
|
103
|
+
//
|
|
104
|
+
// Note: pure `send()` calls do not appear in pendingRequests
|
|
105
|
+
// (they are fire-and-forget), so we drain by issuing a
|
|
106
|
+
// sentinel `request()` and awaiting its ack — the worker
|
|
107
|
+
// processes messages in receive order, so the sentinel ack
|
|
108
|
+
// implies all prior sends were also handled.
|
|
109
|
+
await new Promise((resolve, reject) => {
|
|
110
|
+
const id = -nextSentinelId;
|
|
111
|
+
nextSentinelId += 1;
|
|
112
|
+
pendingRequests.set(id, (msg) => {
|
|
113
|
+
if (msg.error !== undefined)
|
|
114
|
+
reject(new Error(msg.error));
|
|
115
|
+
else
|
|
116
|
+
resolve();
|
|
117
|
+
});
|
|
118
|
+
// Use a no-op listDir at root as the sentinel. The router
|
|
119
|
+
// resolves it cheaply and never errors for an existing root.
|
|
120
|
+
worker.postMessage({ id, kind: "listDir", path: "." });
|
|
121
|
+
});
|
|
122
|
+
},
|
|
123
|
+
async close() {
|
|
124
|
+
if (closed)
|
|
125
|
+
return;
|
|
126
|
+
closed = true;
|
|
127
|
+
// Graceful shutdown: ask the worker to drain its router
|
|
128
|
+
// (closing all open fs handles) and wait for the ack
|
|
129
|
+
// before terminating. We cap the wait so a wedged worker
|
|
130
|
+
// can't hang close().
|
|
131
|
+
const id = -nextSentinelId;
|
|
132
|
+
nextSentinelId += 1;
|
|
133
|
+
const ackPromise = new Promise((resolve) => {
|
|
134
|
+
pendingRequests.set(id, () => resolve());
|
|
135
|
+
});
|
|
136
|
+
try {
|
|
137
|
+
worker.postMessage({ kind: "shutdown", id });
|
|
138
|
+
await Promise.race([
|
|
139
|
+
ackPromise,
|
|
140
|
+
new Promise((resolve) => setTimeout(resolve, 1000)),
|
|
141
|
+
]);
|
|
142
|
+
}
|
|
143
|
+
catch {
|
|
144
|
+
// postMessage may throw if the worker is already gone.
|
|
145
|
+
}
|
|
146
|
+
handlers.clear();
|
|
147
|
+
pendingRequests.clear();
|
|
148
|
+
await worker.terminate();
|
|
149
|
+
},
|
|
150
|
+
};
|
|
151
|
+
};
|
|
152
|
+
//# sourceMappingURL=node-worker-transport.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"node-worker-transport.js","sourceRoot":"","sources":["../../src/node/node-worker-transport.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
// © 2026 Adobe. MIT License. See /LICENSE for details.
|
|
2
|
+
//
|
|
3
|
+
// Node E2E for the worker-thread transport. Uses real `node:worker_threads`
|
|
4
|
+
// + real `node:fs`, with the database driving the persistence service
|
|
5
|
+
// over the worker boundary.
|
|
6
|
+
//
|
|
7
|
+
// Requires: `pnpm build` to have produced `dist/node/node-worker-bootstrap.js`.
|
|
8
|
+
// In CI this is sequenced by the package build script; locally, run
|
|
9
|
+
// `pnpm --filter @adobe/data-persistence build` before this file's tests.
|
|
10
|
+
import { Database } from "@adobe/data/ecs";
|
|
11
|
+
import { F32, Vec3 } from "@adobe/data/math";
|
|
12
|
+
import { mkdtemp, rm, stat } from "node:fs/promises";
|
|
13
|
+
import { tmpdir } from "node:os";
|
|
14
|
+
import { join, resolve } from "node:path";
|
|
15
|
+
import { existsSync } from "node:fs";
|
|
16
|
+
import { fileURLToPath } from "node:url";
|
|
17
|
+
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
|
18
|
+
import { createNodeFsBackend } from "./node-fs-backend.js";
|
|
19
|
+
import { createNodeWorkerTransport } from "./node-worker-transport.js";
|
|
20
|
+
import { createWorkerPersistenceService } from "../service/create-worker-persistence-service.js";
|
|
21
|
+
// Resolve the built worker bootstrap script. The transport defaults to
|
|
22
|
+
// `new URL("./node-worker-bootstrap.js", import.meta.url)` which would
|
|
23
|
+
// pick up the .ts source file URL when vitest is running against the
|
|
24
|
+
// source tree — Node can't load .ts directly, so we explicitly point
|
|
25
|
+
// at the dist artifact.
|
|
26
|
+
const repoBuiltBootstrap = () => {
|
|
27
|
+
const here = fileURLToPath(import.meta.url);
|
|
28
|
+
// src/node/foo.test.ts → dist/node/node-worker-bootstrap.js
|
|
29
|
+
const distPath = resolve(here, "../../../dist/node/node-worker-bootstrap.js");
|
|
30
|
+
return new URL(`file://${distPath}`);
|
|
31
|
+
};
|
|
32
|
+
const ensureBuilt = () => {
|
|
33
|
+
const url = repoBuiltBootstrap();
|
|
34
|
+
if (!existsSync(fileURLToPath(url))) {
|
|
35
|
+
throw new Error(`Worker bootstrap not built yet. Run \`pnpm --filter @adobe/data-persistence build\`. Expected: ${fileURLToPath(url)}`);
|
|
36
|
+
}
|
|
37
|
+
return url;
|
|
38
|
+
};
|
|
39
|
+
const particlePlugin = Database.Plugin.create({
|
|
40
|
+
components: {
|
|
41
|
+
position: Vec3.schema,
|
|
42
|
+
velocity: Vec3.schema,
|
|
43
|
+
mass: F32.schema,
|
|
44
|
+
},
|
|
45
|
+
archetypes: {
|
|
46
|
+
Particle: ["position", "velocity", "mass"],
|
|
47
|
+
},
|
|
48
|
+
transactions: {
|
|
49
|
+
spawn(t, args) {
|
|
50
|
+
return t.archetypes.Particle.insert({
|
|
51
|
+
position: [args.x, args.y, args.z],
|
|
52
|
+
velocity: [0, 0, 0],
|
|
53
|
+
mass: args.mass,
|
|
54
|
+
});
|
|
55
|
+
},
|
|
56
|
+
move(t, args) {
|
|
57
|
+
t.update(args.entity, { position: [args.x, args.y, args.z] });
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
});
|
|
61
|
+
describe("createNodeWorkerTransport (E2E)", () => {
|
|
62
|
+
let tmpRoot;
|
|
63
|
+
beforeEach(async () => {
|
|
64
|
+
tmpRoot = await mkdtemp(join(tmpdir(), "data-persistence-e2e-"));
|
|
65
|
+
});
|
|
66
|
+
afterEach(async () => {
|
|
67
|
+
await rm(tmpRoot, { recursive: true, force: true });
|
|
68
|
+
});
|
|
69
|
+
it("round-trips entities through real worker_threads + fs", async () => {
|
|
70
|
+
const workerScript = ensureBuilt();
|
|
71
|
+
// Saving phase: drive the database, persist via the worker.
|
|
72
|
+
let savedEntity;
|
|
73
|
+
{
|
|
74
|
+
const db = Database.create(particlePlugin);
|
|
75
|
+
const transport = createNodeWorkerTransport({ root: tmpRoot, workerScript });
|
|
76
|
+
const backend = await createNodeFsBackend(tmpRoot);
|
|
77
|
+
const service = await createWorkerPersistenceService({
|
|
78
|
+
database: db,
|
|
79
|
+
backend,
|
|
80
|
+
transport,
|
|
81
|
+
checkpoint: { everyNTransactions: 0, idleMs: 0 },
|
|
82
|
+
});
|
|
83
|
+
savedEntity = db.transactions.spawn({ x: 1, y: 2, z: 3, mass: 10 });
|
|
84
|
+
db.transactions.move({ entity: savedEntity, x: 100, y: 200, z: 300 });
|
|
85
|
+
await service.flush();
|
|
86
|
+
await service.checkpoint();
|
|
87
|
+
await service.dispose();
|
|
88
|
+
await transport.close();
|
|
89
|
+
}
|
|
90
|
+
// Verify on-disk artifacts exist.
|
|
91
|
+
const metaStat = await stat(join(tmpRoot, "meta.json"));
|
|
92
|
+
expect(metaStat.size).toBeGreaterThan(0);
|
|
93
|
+
const eltStat = await stat(join(tmpRoot, "entity-location-0.bin"));
|
|
94
|
+
expect(eltStat.size).toBeGreaterThan(0);
|
|
95
|
+
// Loading phase: fresh database, same plugin, recover via load().
|
|
96
|
+
{
|
|
97
|
+
const db = Database.create(particlePlugin);
|
|
98
|
+
const transport = createNodeWorkerTransport({ root: tmpRoot, workerScript });
|
|
99
|
+
const backend = await createNodeFsBackend(tmpRoot);
|
|
100
|
+
const service = await createWorkerPersistenceService({
|
|
101
|
+
database: db,
|
|
102
|
+
backend,
|
|
103
|
+
transport,
|
|
104
|
+
checkpoint: { everyNTransactions: 0, idleMs: 0 },
|
|
105
|
+
});
|
|
106
|
+
await service.load();
|
|
107
|
+
const view = db.read(savedEntity);
|
|
108
|
+
expect(view).not.toBeNull();
|
|
109
|
+
if (view !== null) {
|
|
110
|
+
expect(Array.from(view.position)).toEqual([100, 200, 300]);
|
|
111
|
+
expect(Array.from(view.velocity)).toEqual([0, 0, 0]);
|
|
112
|
+
expect(view.mass).toBe(10);
|
|
113
|
+
}
|
|
114
|
+
await service.dispose();
|
|
115
|
+
await transport.close();
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
it("writes column files and entity-location files via the worker", async () => {
|
|
119
|
+
const workerScript = ensureBuilt();
|
|
120
|
+
const db = Database.create(particlePlugin);
|
|
121
|
+
const transport = createNodeWorkerTransport({ root: tmpRoot, workerScript });
|
|
122
|
+
const backend = await createNodeFsBackend(tmpRoot);
|
|
123
|
+
const service = await createWorkerPersistenceService({
|
|
124
|
+
database: db,
|
|
125
|
+
backend,
|
|
126
|
+
transport,
|
|
127
|
+
checkpoint: { everyNTransactions: 0, idleMs: 0 },
|
|
128
|
+
});
|
|
129
|
+
for (let i = 0; i < 5; i++) {
|
|
130
|
+
db.transactions.spawn({ x: i, y: i * 2, z: i * 3, mass: i });
|
|
131
|
+
}
|
|
132
|
+
await service.flush();
|
|
133
|
+
await service.checkpoint();
|
|
134
|
+
// Each archetype gets its own subdirectory; Particle has 3 columns.
|
|
135
|
+
const archetypeDirs = await backend.list("archetypes");
|
|
136
|
+
expect(archetypeDirs.length).toBe(1);
|
|
137
|
+
const archetypeDir = archetypeDirs[0];
|
|
138
|
+
const cols = [...(await backend.list(`archetypes/${archetypeDir}`))].sort();
|
|
139
|
+
// position, velocity, mass — all .bin files. The implicit `id`
|
|
140
|
+
// column is intentionally not persisted (it is recoverable from the
|
|
141
|
+
// entity-location files).
|
|
142
|
+
expect(cols).toEqual(["mass.bin", "position.bin", "velocity.bin"]);
|
|
143
|
+
await service.dispose();
|
|
144
|
+
await transport.close();
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
//# sourceMappingURL=node-worker-transport.node.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"node-worker-transport.node.test.js","sourceRoot":"","sources":["../../src/node/node-worker-transport.node.test.ts"],"names":[],"mappings":"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"}
|