@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,146 @@
|
|
|
1
|
+
// © 2026 Adobe. MIT License. See /LICENSE for details.
|
|
2
|
+
import { Entity } from "@adobe/data/ecs";
|
|
3
|
+
// One entity-location file per persistent quadrant (document = 0, settings = 2),
|
|
4
|
+
// each indexed by the entity's per-quadrant local index so it packs fully
|
|
5
|
+
// densely with no gaps.
|
|
6
|
+
const entityLocationFile = (quadrant) => `entity-location-${quadrant}.bin`;
|
|
7
|
+
const JOURNAL_FILE = "journal.bin";
|
|
8
|
+
const META_FILE = "meta.json";
|
|
9
|
+
// Packed entity-location entry: u32 (archetypeId + 1, so 0 = empty) + u32
|
|
10
|
+
// rowIndex. 8 bytes per persistent entity. An unwritten slot reads as the zero
|
|
11
|
+
// (empty) entry; deletions write a zero entry.
|
|
12
|
+
const ELT_STRIDE = 8;
|
|
13
|
+
const columnPath = (archetypeId, component) => `archetypes/${archetypeId}/${component}.bin`;
|
|
14
|
+
export const createPersistRouter = (backend) => {
|
|
15
|
+
const fileCache = new Map();
|
|
16
|
+
// Concurrent opens for the same path must share a single in-flight
|
|
17
|
+
// backend.open promise; otherwise each caller gets its own handle
|
|
18
|
+
// and writes interleave (the snapshotting memory backend in
|
|
19
|
+
// particular surfaces this as lost data).
|
|
20
|
+
const inflightOpens = new Map();
|
|
21
|
+
const openCached = (relPath) => {
|
|
22
|
+
const existing = fileCache.get(relPath);
|
|
23
|
+
if (existing !== undefined)
|
|
24
|
+
return Promise.resolve(existing);
|
|
25
|
+
const inflight = inflightOpens.get(relPath);
|
|
26
|
+
if (inflight !== undefined)
|
|
27
|
+
return inflight;
|
|
28
|
+
const pending = backend.open(relPath).then((file) => {
|
|
29
|
+
fileCache.set(relPath, file);
|
|
30
|
+
inflightOpens.delete(relPath);
|
|
31
|
+
return file;
|
|
32
|
+
});
|
|
33
|
+
inflightOpens.set(relPath, pending);
|
|
34
|
+
return pending;
|
|
35
|
+
};
|
|
36
|
+
const handle = async (op) => {
|
|
37
|
+
switch (op.kind) {
|
|
38
|
+
case "writeColumnSlice": {
|
|
39
|
+
const file = await openCached(columnPath(op.archetypeId, op.component));
|
|
40
|
+
await file.writeAt(op.rowOffset, new Uint8Array(op.bytes));
|
|
41
|
+
return undefined;
|
|
42
|
+
}
|
|
43
|
+
case "appendJournal": {
|
|
44
|
+
const file = await openCached(JOURNAL_FILE);
|
|
45
|
+
return file.appendAt(new Uint8Array(op.bytes));
|
|
46
|
+
}
|
|
47
|
+
case "writeEntityLocation": {
|
|
48
|
+
if (Entity.isNonPersistent(op.entity)) {
|
|
49
|
+
throw new Error(`writeEntityLocation: non-persistent entity not persistable (entity=${op.entity})`);
|
|
50
|
+
}
|
|
51
|
+
const file = await openCached(entityLocationFile(Entity.quadrantOf(op.entity)));
|
|
52
|
+
const entry = new ArrayBuffer(ELT_STRIDE);
|
|
53
|
+
const view = new DataView(entry);
|
|
54
|
+
// Store archetypeId biased by +1 so a zero-filled (never-written)
|
|
55
|
+
// slot reads as "empty" rather than a valid archetype 0 (the
|
|
56
|
+
// backend zero-fills any gap left before the first write).
|
|
57
|
+
view.setUint32(0, op.archetypeId + 1, true);
|
|
58
|
+
view.setUint32(4, op.rowIndex, true);
|
|
59
|
+
await file.writeAt(Entity.toLocalIndex(op.entity) * ELT_STRIDE, new Uint8Array(entry));
|
|
60
|
+
return undefined;
|
|
61
|
+
}
|
|
62
|
+
case "deleteEntityLocation": {
|
|
63
|
+
if (Entity.isNonPersistent(op.entity)) {
|
|
64
|
+
throw new Error(`deleteEntityLocation: non-persistent entity not persistable (entity=${op.entity})`);
|
|
65
|
+
}
|
|
66
|
+
const file = await openCached(entityLocationFile(Entity.quadrantOf(op.entity)));
|
|
67
|
+
// Zero entry = empty slot (biased archetype 0 means "absent").
|
|
68
|
+
const tombstone = new Uint8Array(ELT_STRIDE);
|
|
69
|
+
await file.writeAt(Entity.toLocalIndex(op.entity) * ELT_STRIDE, tombstone);
|
|
70
|
+
return undefined;
|
|
71
|
+
}
|
|
72
|
+
case "writeJournalSnapshot": {
|
|
73
|
+
// Always a full rewrite at offset 0; truncate removes any
|
|
74
|
+
// stale tail from a previous checkpoint with more rows.
|
|
75
|
+
const path = columnPath(op.archetypeId, op.component);
|
|
76
|
+
const file = await openCached(path);
|
|
77
|
+
const bytes = new Uint8Array(op.bytes);
|
|
78
|
+
await file.writeAt(0, bytes);
|
|
79
|
+
await file.truncate(bytes.byteLength);
|
|
80
|
+
return undefined;
|
|
81
|
+
}
|
|
82
|
+
case "checkpoint": {
|
|
83
|
+
// Atomic-ish: write the manifest to a temp file, then rename.
|
|
84
|
+
// OPFS rename is implemented as copy+delete in our backend
|
|
85
|
+
// (lossy of atomicity); on POSIX the rename is atomic.
|
|
86
|
+
const text = JSON.stringify(op.manifest);
|
|
87
|
+
const bytes = new TextEncoder().encode(text);
|
|
88
|
+
const tmp = META_FILE + ".tmp";
|
|
89
|
+
const tmpFile = await backend.open(tmp);
|
|
90
|
+
await tmpFile.truncate(0);
|
|
91
|
+
await tmpFile.writeAt(0, bytes);
|
|
92
|
+
await tmpFile.sync();
|
|
93
|
+
await tmpFile.close();
|
|
94
|
+
await backend.remove(META_FILE);
|
|
95
|
+
await backend.rename(tmp, META_FILE);
|
|
96
|
+
// Truncate the journal once the new manifest is durable.
|
|
97
|
+
const journal = await openCached(JOURNAL_FILE);
|
|
98
|
+
await journal.truncate(0);
|
|
99
|
+
await journal.sync();
|
|
100
|
+
return undefined;
|
|
101
|
+
}
|
|
102
|
+
case "readFile": {
|
|
103
|
+
// Open via openCached so subsequent writes share the handle.
|
|
104
|
+
// Backends create-on-open (matching POSIX O_CREAT semantics);
|
|
105
|
+
// non-existence is signaled by a zero-byte size. Callers that
|
|
106
|
+
// need to distinguish missing from empty should use `listDir`.
|
|
107
|
+
const file = await openCached(op.path);
|
|
108
|
+
const size = await file.size();
|
|
109
|
+
if (size === 0)
|
|
110
|
+
return { bytes: new ArrayBuffer(0) };
|
|
111
|
+
const data = await file.readAt(0, size);
|
|
112
|
+
// Copy into a fresh ArrayBuffer so it is transferable
|
|
113
|
+
// across postMessage and never aliases an internal buffer.
|
|
114
|
+
const out = new ArrayBuffer(data.byteLength);
|
|
115
|
+
new Uint8Array(out).set(data);
|
|
116
|
+
return { bytes: out };
|
|
117
|
+
}
|
|
118
|
+
case "listDir": {
|
|
119
|
+
try {
|
|
120
|
+
const entries = await backend.list(op.path);
|
|
121
|
+
return { entries };
|
|
122
|
+
}
|
|
123
|
+
catch {
|
|
124
|
+
return { entries: [] };
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
default: {
|
|
128
|
+
const exhaustive = op;
|
|
129
|
+
throw new Error(`Unknown PersistOp kind: ${exhaustive.kind}`);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
const close = async () => {
|
|
134
|
+
for (const file of fileCache.values()) {
|
|
135
|
+
await file.close();
|
|
136
|
+
}
|
|
137
|
+
fileCache.clear();
|
|
138
|
+
// Release process-wide backend resources (lock files, etc.) AFTER
|
|
139
|
+
// file handles are closed: backends that use a sync access handle
|
|
140
|
+
// as their lock would otherwise see a stale handle still open
|
|
141
|
+
// when they try to release it.
|
|
142
|
+
await backend.dispose?.();
|
|
143
|
+
};
|
|
144
|
+
return { handle, close };
|
|
145
|
+
};
|
|
146
|
+
//# sourceMappingURL=router.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"router.js","sourceRoot":"","sources":["../../src/transport/router.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import type { Manifest } from "../encoder/types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Operations sent from the ECS thread to the persistence worker. The
|
|
4
|
+
* worker is a dumb byte router; all semantic mapping (component → file,
|
|
5
|
+
* row → byte offset) is done by the encoder on the ECS thread before
|
|
6
|
+
* the op is posted.
|
|
7
|
+
*/
|
|
8
|
+
export type PersistOp = {
|
|
9
|
+
readonly id: number;
|
|
10
|
+
readonly kind: "writeColumnSlice";
|
|
11
|
+
readonly archetypeId: number;
|
|
12
|
+
readonly component: string;
|
|
13
|
+
readonly rowOffset: number;
|
|
14
|
+
readonly bytes: ArrayBuffer;
|
|
15
|
+
} | {
|
|
16
|
+
readonly id: number;
|
|
17
|
+
readonly kind: "appendJournal";
|
|
18
|
+
readonly bytes: ArrayBuffer;
|
|
19
|
+
} | {
|
|
20
|
+
readonly id: number;
|
|
21
|
+
readonly kind: "writeEntityLocation";
|
|
22
|
+
readonly entity: number;
|
|
23
|
+
readonly archetypeId: number;
|
|
24
|
+
readonly rowIndex: number;
|
|
25
|
+
} | {
|
|
26
|
+
readonly id: number;
|
|
27
|
+
readonly kind: "deleteEntityLocation";
|
|
28
|
+
readonly entity: number;
|
|
29
|
+
} | {
|
|
30
|
+
readonly id: number;
|
|
31
|
+
readonly kind: "checkpoint";
|
|
32
|
+
readonly manifest: Manifest;
|
|
33
|
+
} | {
|
|
34
|
+
readonly id: number;
|
|
35
|
+
readonly kind: "writeJournalSnapshot";
|
|
36
|
+
readonly archetypeId: number;
|
|
37
|
+
readonly component: string;
|
|
38
|
+
/** Full snapshot buffer from encodeJournalSnapshot — always written at offset 0 and truncated to this length. */
|
|
39
|
+
readonly bytes: ArrayBuffer;
|
|
40
|
+
} | {
|
|
41
|
+
readonly id: number;
|
|
42
|
+
readonly kind: "readFile";
|
|
43
|
+
readonly path: string;
|
|
44
|
+
} | {
|
|
45
|
+
readonly id: number;
|
|
46
|
+
readonly kind: "listDir";
|
|
47
|
+
readonly path: string;
|
|
48
|
+
};
|
|
49
|
+
/**
|
|
50
|
+
* Reply payload for `readFile` requests. Carries the entire file
|
|
51
|
+
* contents as an ArrayBuffer (zero-length for empty or non-existent
|
|
52
|
+
* files; callers detect existence separately via `listDir`).
|
|
53
|
+
* Returned via `transport.request()`.
|
|
54
|
+
*/
|
|
55
|
+
export interface ReadFileResult {
|
|
56
|
+
readonly bytes: ArrayBuffer;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Reply payload for `listDir` requests. Returns directory entry names
|
|
60
|
+
* (not full paths). Empty array when the directory does not exist.
|
|
61
|
+
*/
|
|
62
|
+
export interface ListDirResult {
|
|
63
|
+
readonly entries: readonly string[];
|
|
64
|
+
}
|
|
65
|
+
/** Acknowledgement message sent from worker back to the ECS thread. */
|
|
66
|
+
export interface AckMessage {
|
|
67
|
+
readonly kind: "ack";
|
|
68
|
+
readonly id: number;
|
|
69
|
+
readonly value?: unknown;
|
|
70
|
+
readonly error?: string;
|
|
71
|
+
}
|
|
72
|
+
export type PersistMessage = AckMessage;
|
|
73
|
+
/**
|
|
74
|
+
* The transport layer between the ECS thread and the persistence
|
|
75
|
+
* worker. Three implementations exist:
|
|
76
|
+
*
|
|
77
|
+
* - InprocessTransport - direct in-thread call (Node servers, tests)
|
|
78
|
+
* - BrowserWorkerTransport - DOM Worker + postMessage
|
|
79
|
+
* - NodeWorkerTransport - node:worker_threads + parentPort
|
|
80
|
+
*/
|
|
81
|
+
export interface Transport {
|
|
82
|
+
/**
|
|
83
|
+
* Send an operation that does not require a response.
|
|
84
|
+
*/
|
|
85
|
+
send(op: PersistOp, transfer?: readonly Transferable[]): void;
|
|
86
|
+
/**
|
|
87
|
+
* Send an operation and await its acknowledgement.
|
|
88
|
+
*/
|
|
89
|
+
request<T = unknown>(op: PersistOp, transfer?: readonly Transferable[]): Promise<T>;
|
|
90
|
+
/**
|
|
91
|
+
* Subscribe to incoming messages from the worker side. Returns an
|
|
92
|
+
* unsubscribe function.
|
|
93
|
+
*/
|
|
94
|
+
onMessage(handler: (msg: PersistMessage) => void): () => void;
|
|
95
|
+
/**
|
|
96
|
+
* Wait for all currently-enqueued operations (sent via `send` or
|
|
97
|
+
* `request`) to complete. Used by the service to drain pending
|
|
98
|
+
* writes before checkpoints, snapshots, or shutdown.
|
|
99
|
+
*/
|
|
100
|
+
flush(): Promise<void>;
|
|
101
|
+
/**
|
|
102
|
+
* Tear down the transport. Pending requests reject.
|
|
103
|
+
*/
|
|
104
|
+
close(): Promise<void>;
|
|
105
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transport.js","sourceRoot":"","sources":["../../src/transport/transport.ts"],"names":[],"mappings":"AAAA,uDAAuD"}
|
package/package.json
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@adobe/data-persistence",
|
|
3
|
+
"version": "0.9.90",
|
|
4
|
+
"description": "Worker-based incremental persistence layer for @adobe/data ECS over OPFS (browser) and node:fs (server).",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"sideEffects": false,
|
|
7
|
+
"private": false,
|
|
8
|
+
"publishConfig": {
|
|
9
|
+
"registry": "https://registry.npmjs.org/",
|
|
10
|
+
"access": "public"
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"dist",
|
|
14
|
+
"README.md",
|
|
15
|
+
"LICENSE"
|
|
16
|
+
],
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"@adobe/data": "0.9.90"
|
|
19
|
+
},
|
|
20
|
+
"devDependencies": {
|
|
21
|
+
"@vitest/browser": "^1.6.0",
|
|
22
|
+
"playwright": "^1.46.1",
|
|
23
|
+
"typescript": "^5.8.3",
|
|
24
|
+
"vite": "^5.1.1",
|
|
25
|
+
"vitest": "^1.6.0"
|
|
26
|
+
},
|
|
27
|
+
"exports": {
|
|
28
|
+
".": {
|
|
29
|
+
"import": "./dist/index.js",
|
|
30
|
+
"types": "./dist/index.d.ts"
|
|
31
|
+
},
|
|
32
|
+
"./node": {
|
|
33
|
+
"import": "./dist/node/index.js",
|
|
34
|
+
"types": "./dist/node/index.d.ts"
|
|
35
|
+
},
|
|
36
|
+
"./browser": {
|
|
37
|
+
"import": "./dist/browser/index.js",
|
|
38
|
+
"types": "./dist/browser/index.d.ts"
|
|
39
|
+
},
|
|
40
|
+
"./browser-worker": {
|
|
41
|
+
"import": "./dist/browser/browser-worker-bootstrap.js",
|
|
42
|
+
"types": "./dist/browser/browser-worker-bootstrap.d.ts"
|
|
43
|
+
},
|
|
44
|
+
"./node-worker": {
|
|
45
|
+
"import": "./dist/node/node-worker-bootstrap.js",
|
|
46
|
+
"types": "./dist/node/node-worker-bootstrap.d.ts"
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
"scripts": {
|
|
50
|
+
"build": "cp ../../LICENSE . 2>/dev/null || true; tsc -b",
|
|
51
|
+
"clean": "rm -rf dist node_modules",
|
|
52
|
+
"dev": "tsc -b -w --preserveWatchOutput",
|
|
53
|
+
"test": "pnpm exec playwright install chromium && vitest --run",
|
|
54
|
+
"test:node": "vitest --run --project=node",
|
|
55
|
+
"test:browser": "vitest --run --project=browser",
|
|
56
|
+
"perf": "tsc -b && node --expose-gc dist/perftest/run.js",
|
|
57
|
+
"typecheck": "tsc -b"
|
|
58
|
+
}
|
|
59
|
+
}
|