@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,162 @@
|
|
|
1
|
+
// © 2026 Adobe. MIT License. See /LICENSE for details.
|
|
2
|
+
import { validateRelPath } from "../backend/validate-rel-path.js";
|
|
3
|
+
import { createOpfsSyncFile } from "./opfs-sync-file.js";
|
|
4
|
+
const splitPath = (relPath) => relPath.split("/").filter(seg => seg !== "" && seg !== ".");
|
|
5
|
+
/**
|
|
6
|
+
* Returns true when `err` is a "this OPFS entry doesn't exist" error.
|
|
7
|
+
* Cross-runtime safe: in workers without DOMException at the global
|
|
8
|
+
* (vanishingly rare but possible in older browsers), `instanceof`
|
|
9
|
+
* falls back to a duck-typed `name === "NotFoundError"` check rather
|
|
10
|
+
* than throwing.
|
|
11
|
+
*/
|
|
12
|
+
const isNotFoundError = (err) => {
|
|
13
|
+
if (typeof DOMException !== "undefined" && err instanceof DOMException) {
|
|
14
|
+
return err.name === "NotFoundError";
|
|
15
|
+
}
|
|
16
|
+
return typeof err === "object" && err !== null && err.name === "NotFoundError";
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* Walk an OPFS directory tree, optionally creating intermediate
|
|
20
|
+
* directories. Returns the resolved directory handle.
|
|
21
|
+
*/
|
|
22
|
+
const resolveDir = async (root, segments, create) => {
|
|
23
|
+
let dir = root;
|
|
24
|
+
for (const seg of segments) {
|
|
25
|
+
dir = await dir.getDirectoryHandle(seg, { create });
|
|
26
|
+
}
|
|
27
|
+
return dir;
|
|
28
|
+
};
|
|
29
|
+
const LOCK_FILENAME = "lock.json";
|
|
30
|
+
/**
|
|
31
|
+
* Acquire the OPFS single-writer lock by opening a sync access handle
|
|
32
|
+
* on `lock.json`. Sync access handles are exclusive per file by spec
|
|
33
|
+
* — opening a second one for the same file throws `NoModificationAllowedError`
|
|
34
|
+
* (or `InvalidStateError` in some implementations). That IS the
|
|
35
|
+
* contention signal; we just turn it into a clear error.
|
|
36
|
+
*
|
|
37
|
+
* The handle is held for the lifetime of the backend. When the
|
|
38
|
+
* worker terminates (page navigation, tab close, explicit
|
|
39
|
+
* `worker.terminate()`), the handle is released by the user-agent
|
|
40
|
+
* and the lock auto-clears — no stale-lock recovery needed on the
|
|
41
|
+
* browser side.
|
|
42
|
+
*/
|
|
43
|
+
const acquireOpfsLock = async (root) => {
|
|
44
|
+
const handle = await root.getFileHandle(LOCK_FILENAME, { create: true });
|
|
45
|
+
const handleAsSync = handle;
|
|
46
|
+
if (typeof handleAsSync.createSyncAccessHandle !== "function") {
|
|
47
|
+
// Older browsers without sync handles can't enforce the lock.
|
|
48
|
+
// Returning null disables it rather than crashing the backend.
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
let sync;
|
|
52
|
+
try {
|
|
53
|
+
sync = await handleAsSync.createSyncAccessHandle();
|
|
54
|
+
}
|
|
55
|
+
catch (err) {
|
|
56
|
+
if (typeof DOMException !== "undefined" && err instanceof DOMException) {
|
|
57
|
+
throw new Error(`OpfsBackend: lock contended (${err.name}). Another worker in this origin ` +
|
|
58
|
+
`is currently using this OPFS root. Make sure prior workers were terminated ` +
|
|
59
|
+
`before constructing a new backend.`);
|
|
60
|
+
}
|
|
61
|
+
throw err;
|
|
62
|
+
}
|
|
63
|
+
// Stamp the handle with a timestamp + UA hint for forensic debugging
|
|
64
|
+
// when a developer inspects OPFS in DevTools. Failure to write here
|
|
65
|
+
// doesn't matter — the lock is held by the SyncAccessHandle itself.
|
|
66
|
+
try {
|
|
67
|
+
const stamp = JSON.stringify({
|
|
68
|
+
acquiredAtMs: Date.now(),
|
|
69
|
+
userAgent: typeof navigator !== "undefined" ? navigator.userAgent : "unknown",
|
|
70
|
+
});
|
|
71
|
+
sync.write(new TextEncoder().encode(stamp));
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
// Best-effort; ignore.
|
|
75
|
+
}
|
|
76
|
+
return {
|
|
77
|
+
async release() {
|
|
78
|
+
try {
|
|
79
|
+
sync.close();
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
// Worker is shutting down anyway.
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
};
|
|
87
|
+
/**
|
|
88
|
+
* OPFS backend. Must be constructed inside a dedicated worker because
|
|
89
|
+
* the underlying RandomAccessFile uses sync access handles.
|
|
90
|
+
*/
|
|
91
|
+
export const createOpfsBackend = async (root) => {
|
|
92
|
+
const lock = await acquireOpfsLock(root);
|
|
93
|
+
const open = async (relPath) => {
|
|
94
|
+
validateRelPath(relPath);
|
|
95
|
+
const parts = splitPath(relPath);
|
|
96
|
+
if (parts.length === 0) {
|
|
97
|
+
throw new Error(`open: path resolves to empty: ${JSON.stringify(relPath)}`);
|
|
98
|
+
}
|
|
99
|
+
const filename = parts.pop();
|
|
100
|
+
const dir = await resolveDir(root, parts, true);
|
|
101
|
+
const fileHandle = await dir.getFileHandle(filename, { create: true });
|
|
102
|
+
return createOpfsSyncFile(fileHandle);
|
|
103
|
+
};
|
|
104
|
+
const list = async (relPath) => {
|
|
105
|
+
const parts = relPath === "." ? [] : (validateRelPath(relPath), splitPath(relPath));
|
|
106
|
+
try {
|
|
107
|
+
const dir = await resolveDir(root, parts, false);
|
|
108
|
+
const out = [];
|
|
109
|
+
for await (const name of dir.keys()) {
|
|
110
|
+
// Hide the lock file from listings at the root so user
|
|
111
|
+
// code sees a consistent view independent of whether
|
|
112
|
+
// locking is supported by the runtime.
|
|
113
|
+
if (parts.length === 0 && name === LOCK_FILENAME)
|
|
114
|
+
continue;
|
|
115
|
+
out.push(name);
|
|
116
|
+
}
|
|
117
|
+
return out;
|
|
118
|
+
}
|
|
119
|
+
catch (err) {
|
|
120
|
+
if (isNotFoundError(err))
|
|
121
|
+
return [];
|
|
122
|
+
throw err;
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
const remove = async (relPath) => {
|
|
126
|
+
validateRelPath(relPath);
|
|
127
|
+
const parts = splitPath(relPath);
|
|
128
|
+
if (parts.length === 0)
|
|
129
|
+
return;
|
|
130
|
+
const filename = parts.pop();
|
|
131
|
+
try {
|
|
132
|
+
const dir = await resolveDir(root, parts, false);
|
|
133
|
+
await dir.removeEntry(filename);
|
|
134
|
+
}
|
|
135
|
+
catch (err) {
|
|
136
|
+
if (isNotFoundError(err))
|
|
137
|
+
return;
|
|
138
|
+
throw err;
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
const rename = async (from, to) => {
|
|
142
|
+
// OPFS does not support rename directly. Fall back to copy+delete.
|
|
143
|
+
// This loses atomicity; for checkpoint manifests we rely on writing
|
|
144
|
+
// the manifest under a temporary name and using `move` on browsers
|
|
145
|
+
// that support it. For now, copy-and-delete is sufficient.
|
|
146
|
+
validateRelPath(from);
|
|
147
|
+
validateRelPath(to);
|
|
148
|
+
const fromHandle = await open(from);
|
|
149
|
+
const size = await fromHandle.size();
|
|
150
|
+
const data = await fromHandle.readAt(0, size);
|
|
151
|
+
await fromHandle.close();
|
|
152
|
+
const toHandle = await open(to);
|
|
153
|
+
await toHandle.writeAt(0, data);
|
|
154
|
+
await toHandle.close();
|
|
155
|
+
await remove(from);
|
|
156
|
+
};
|
|
157
|
+
const dispose = async () => {
|
|
158
|
+
await lock?.release();
|
|
159
|
+
};
|
|
160
|
+
return { open, list, remove, rename, dispose };
|
|
161
|
+
};
|
|
162
|
+
//# sourceMappingURL=opfs-backend.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"opfs-backend.js","sourceRoot":"","sources":["../../src/browser/opfs-backend.ts"],"names":[],"mappings":"AAAA,uDAAuD;AAIvD,OAAO,EAAE,eAAe,EAAE,MAAM,iCAAiC,CAAC;AAClE,OAAO,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAEzD,MAAM,SAAS,GAAG,CAAC,OAAe,EAAY,EAAE,CAC5C,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,EAAE,IAAI,GAAG,KAAK,GAAG,CAAC,CAAC;AAEhE;;;;;;GAMG;AACH,MAAM,eAAe,GAAG,CAAC,GAAY,EAAW,EAAE;IAC9C,IAAI,OAAO,YAAY,KAAK,WAAW,IAAI,GAAG,YAAY,YAAY,EAAE,CAAC;QACrE,OAAO,GAAG,CAAC,IAAI,KAAK,eAAe,CAAC;IACxC,CAAC;IACD,OAAO,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,KAAK,IAAI,IAAK,GAA0B,CAAC,IAAI,KAAK,eAAe,CAAC;AAC3G,CAAC,CAAC;AAEF;;;GAGG;AACH,MAAM,UAAU,GAAG,KAAK,EACpB,IAA+B,EAC/B,QAA2B,EAC3B,MAAe,EACmB,EAAE;IACpC,IAAI,GAAG,GAAG,IAAI,CAAC;IACf,KAAK,MAAM,GAAG,IAAI,QAAQ,EAAE,CAAC;QACzB,GAAG,GAAG,MAAM,GAAG,CAAC,kBAAkB,CAAC,GAAG,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC;IACxD,CAAC;IACD,OAAO,GAAG,CAAC;AACf,CAAC,CAAC;AAEF,MAAM,aAAa,GAAG,WAAW,CAAC;AAElC;;;;;;;;;;;;GAYG;AACH,MAAM,eAAe,GAAG,KAAK,EACzB,IAA+B,EACa,EAAE;IAC9C,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,aAAa,CAAC,aAAa,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;IACzE,MAAM,YAAY,GAAG,MAEpB,CAAC;IACF,IAAI,OAAO,YAAY,CAAC,sBAAsB,KAAK,UAAU,EAAE,CAAC;QAC5D,8DAA8D;QAC9D,+DAA+D;QAC/D,OAAO,IAAI,CAAC;IAChB,CAAC;IACD,IAAI,IAA0D,CAAC;IAC/D,IAAI,CAAC;QACD,IAAI,GAAG,MAAM,YAAY,CAAC,sBAAsB,EAAE,CAAC;IACvD,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACX,IAAI,OAAO,YAAY,KAAK,WAAW,IAAI,GAAG,YAAY,YAAY,EAAE,CAAC;YACrE,MAAM,IAAI,KAAK,CACX,gCAAgC,GAAG,CAAC,IAAI,mCAAmC;gBAC3E,6EAA6E;gBAC7E,oCAAoC,CACvC,CAAC;QACN,CAAC;QACD,MAAM,GAAG,CAAC;IACd,CAAC;IACD,qEAAqE;IACrE,oEAAoE;IACpE,oEAAoE;IACpE,IAAI,CAAC;QACD,MAAM,KAAK,GAAG,IAAI,CAAC,SAAS,CAAC;YACzB,YAAY,EAAE,IAAI,CAAC,GAAG,EAAE;YACxB,SAAS,EAAE,OAAO,SAAS,KAAK,WAAW,CAAC,CAAC,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS;SAChF,CAAC,CAAC;QACH,IAAI,CAAC,KAAK,CAAC,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;IAChD,CAAC;IAAC,MAAM,CAAC;QACL,uBAAuB;IAC3B,CAAC;IACD,OAAO;QACH,KAAK,CAAC,OAAO;YACT,IAAI,CAAC;gBACD,IAAI,CAAC,KAAK,EAAE,CAAC;YACjB,CAAC;YAAC,MAAM,CAAC;gBACL,kCAAkC;YACtC,CAAC;QACL,CAAC;KACJ,CAAC;AACN,CAAC,CAAC;AAEF;;;GAGG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,KAAK,EAClC,IAA+B,EACJ,EAAE;IAC7B,MAAM,IAAI,GAAG,MAAM,eAAe,CAAC,IAAI,CAAC,CAAC;IACzC,MAAM,IAAI,GAAG,KAAK,EAAE,OAAe,EAA6B,EAAE;QAC9D,eAAe,CAAC,OAAO,CAAC,CAAC;QACzB,MAAM,KAAK,GAAG,SAAS,CAAC,OAAO,CAAC,CAAC;QACjC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACrB,MAAM,IAAI,KAAK,CAAC,iCAAiC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;QAChF,CAAC;QACD,MAAM,QAAQ,GAAG,KAAK,CAAC,GAAG,EAAG,CAAC;QAC9B,MAAM,GAAG,GAAG,MAAM,UAAU,CAAC,IAAI,EAAE,KAAK,EAAE,IAAI,CAAC,CAAC;QAChD,MAAM,UAAU,GAAG,MAAM,GAAG,CAAC,aAAa,CAAC,QAAQ,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;QACvE,OAAO,kBAAkB,CAAC,UAAU,CAAC,CAAC;IAC1C,CAAC,CAAC;IAEF,MAAM,IAAI,GAAG,KAAK,EAAE,OAAe,EAA8B,EAAE;QAC/D,MAAM,KAAK,GAAG,OAAO,KAAK,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,eAAe,CAAC,OAAO,CAAC,EAAE,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC;QACpF,IAAI,CAAC;YACD,MAAM,GAAG,GAAG,MAAM,UAAU,CAAC,IAAI,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC;YACjD,MAAM,GAAG,GAAa,EAAE,CAAC;YACzB,IAAI,KAAK,EAAE,MAAM,IAAI,IAAK,GAAoD,CAAC,IAAI,EAAE,EAAE,CAAC;gBACpF,uDAAuD;gBACvD,qDAAqD;gBACrD,uCAAuC;gBACvC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,IAAI,IAAI,KAAK,aAAa;oBAAE,SAAS;gBAC3D,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACnB,CAAC;YACD,OAAO,GAAG,CAAC;QACf,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACX,IAAI,eAAe,CAAC,GAAG,CAAC;gBAAE,OAAO,EAAE,CAAC;YACpC,MAAM,GAAG,CAAC;QACd,CAAC;IACL,CAAC,CAAC;IAEF,MAAM,MAAM,GAAG,KAAK,EAAE,OAAe,EAAiB,EAAE;QACpD,eAAe,CAAC,OAAO,CAAC,CAAC;QACzB,MAAM,KAAK,GAAG,SAAS,CAAC,OAAO,CAAC,CAAC;QACjC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO;QAC/B,MAAM,QAAQ,GAAG,KAAK,CAAC,GAAG,EAAG,CAAC;QAC9B,IAAI,CAAC;YACD,MAAM,GAAG,GAAG,MAAM,UAAU,CAAC,IAAI,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC;YACjD,MAAM,GAAG,CAAC,WAAW,CAAC,QAAQ,CAAC,CAAC;QACpC,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACX,IAAI,eAAe,CAAC,GAAG,CAAC;gBAAE,OAAO;YACjC,MAAM,GAAG,CAAC;QACd,CAAC;IACL,CAAC,CAAC;IAEF,MAAM,MAAM,GAAG,KAAK,EAAE,IAAY,EAAE,EAAU,EAAiB,EAAE;QAC7D,mEAAmE;QACnE,oEAAoE;QACpE,mEAAmE;QACnE,2DAA2D;QAC3D,eAAe,CAAC,IAAI,CAAC,CAAC;QACtB,eAAe,CAAC,EAAE,CAAC,CAAC;QAEpB,MAAM,UAAU,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,CAAC;QACpC,MAAM,IAAI,GAAG,MAAM,UAAU,CAAC,IAAI,EAAE,CAAC;QACrC,MAAM,IAAI,GAAG,MAAM,UAAU,CAAC,MAAM,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC;QAC9C,MAAM,UAAU,CAAC,KAAK,EAAE,CAAC;QAEzB,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,EAAE,CAAC,CAAC;QAChC,MAAM,QAAQ,CAAC,OAAO,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC;QAChC,MAAM,QAAQ,CAAC,KAAK,EAAE,CAAC;QAEvB,MAAM,MAAM,CAAC,IAAI,CAAC,CAAC;IACvB,CAAC,CAAC;IAEF,MAAM,OAAO,GAAG,KAAK,IAAmB,EAAE;QACtC,MAAM,IAAI,EAAE,OAAO,EAAE,CAAC;IAC1B,CAAC,CAAC;IAEF,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC;AACnD,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// © 2026 Adobe. MIT License. See /LICENSE for details.
|
|
2
|
+
//
|
|
3
|
+
// Test-only worker bootstrap for OPFS lock behavior. The main-thread
|
|
4
|
+
// test sends `{ cmd: "acquire", dirName }` and we try to construct an
|
|
5
|
+
// OPFS backend on `<opfs root>/<dirName>`. We post back `acquired` or
|
|
6
|
+
// `failed` (with the message). On `{ cmd: "release" }` we dispose
|
|
7
|
+
// the backend.
|
|
8
|
+
//
|
|
9
|
+
// This file lives next to the OPFS sources so Vite's test transform
|
|
10
|
+
// picks it up via `new URL(...)` from the test file.
|
|
11
|
+
/// <reference lib="webworker" />
|
|
12
|
+
import { createOpfsBackend } from "./opfs-backend.js";
|
|
13
|
+
let backend = null;
|
|
14
|
+
const post = (msg) => self.postMessage(msg);
|
|
15
|
+
self.addEventListener("message", (ev) => {
|
|
16
|
+
const msg = ev.data;
|
|
17
|
+
if (msg.cmd === "acquire") {
|
|
18
|
+
void (async () => {
|
|
19
|
+
try {
|
|
20
|
+
const opfs = await navigator.storage.getDirectory();
|
|
21
|
+
const subdir = await opfs.getDirectoryHandle(msg.dirName, { create: true });
|
|
22
|
+
backend = await createOpfsBackend(subdir);
|
|
23
|
+
post({ cmd: "acquired", id: msg.id });
|
|
24
|
+
}
|
|
25
|
+
catch (err) {
|
|
26
|
+
post({
|
|
27
|
+
cmd: "failed",
|
|
28
|
+
id: msg.id,
|
|
29
|
+
error: err instanceof Error ? err.message : String(err),
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
})();
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
if (msg.cmd === "release") {
|
|
36
|
+
void (async () => {
|
|
37
|
+
try {
|
|
38
|
+
if (backend !== null) {
|
|
39
|
+
await backend.dispose?.();
|
|
40
|
+
backend = null;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
finally {
|
|
44
|
+
post({ cmd: "released", id: msg.id });
|
|
45
|
+
}
|
|
46
|
+
})();
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
//# sourceMappingURL=opfs-lock-test-worker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"opfs-lock-test-worker.js","sourceRoot":"","sources":["../../src/browser/opfs-lock-test-worker.ts"],"names":[],"mappings":"AAAA,uDAAuD;AACvD,EAAE;AACF,qEAAqE;AACrE,sEAAsE;AACtE,sEAAsE;AACtE,kEAAkE;AAClE,eAAe;AACf,EAAE;AACF,oEAAoE;AACpE,qDAAqD;AAErD,iCAAiC;AAEjC,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AActD,IAAI,OAAO,GAA8B,IAAI,CAAC;AAE9C,MAAM,IAAI,GAAG,CAAC,GAAe,EAAQ,EAAE,CAAC,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;AAE9D,IAAI,CAAC,gBAAgB,CAAC,SAAS,EAAE,CAAC,EAA2B,EAAE,EAAE;IAC7D,MAAM,GAAG,GAAG,EAAE,CAAC,IAAI,CAAC;IACpB,IAAI,GAAG,CAAC,GAAG,KAAK,SAAS,EAAE,CAAC;QACxB,KAAK,CAAC,KAAK,IAAI,EAAE;YACb,IAAI,CAAC;gBACD,MAAM,IAAI,GAAG,MAAM,SAAS,CAAC,OAAO,CAAC,YAAY,EAAE,CAAC;gBACpD,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,kBAAkB,CAAC,GAAG,CAAC,OAAO,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;gBAC5E,OAAO,GAAG,MAAM,iBAAiB,CAAC,MAAM,CAAC,CAAC;gBAC1C,IAAI,CAAC,EAAE,GAAG,EAAE,UAAU,EAAE,EAAE,EAAE,GAAG,CAAC,EAAE,EAAE,CAAC,CAAC;YAC1C,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACX,IAAI,CAAC;oBACD,GAAG,EAAE,QAAQ;oBACb,EAAE,EAAE,GAAG,CAAC,EAAE;oBACV,KAAK,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC;iBAC1D,CAAC,CAAC;YACP,CAAC;QACL,CAAC,CAAC,EAAE,CAAC;QACL,OAAO;IACX,CAAC;IACD,IAAI,GAAG,CAAC,GAAG,KAAK,SAAS,EAAE,CAAC;QACxB,KAAK,CAAC,KAAK,IAAI,EAAE;YACb,IAAI,CAAC;gBACD,IAAI,OAAO,KAAK,IAAI,EAAE,CAAC;oBACnB,MAAM,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC;oBAC1B,OAAO,GAAG,IAAI,CAAC;gBACnB,CAAC;YACL,CAAC;oBAAS,CAAC;gBACP,IAAI,CAAC,EAAE,GAAG,EAAE,UAAU,EAAE,EAAE,EAAE,GAAG,CAAC,EAAE,EAAE,CAAC,CAAC;YAC1C,CAAC;QACL,CAAC,CAAC,EAAE,CAAC;QACL,OAAO;IACX,CAAC;AACL,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { RandomAccessFile } from "../backend/random-access-file.js";
|
|
2
|
+
/**
|
|
3
|
+
* OPFS sync-handle adapter. Only callable inside a dedicated worker
|
|
4
|
+
* because `FileSystemSyncAccessHandle` is unavailable on the main
|
|
5
|
+
* thread per the spec.
|
|
6
|
+
*
|
|
7
|
+
* The async surface in `RandomAccessFile` is satisfied by sync calls
|
|
8
|
+
* under the hood.
|
|
9
|
+
*/
|
|
10
|
+
export declare const createOpfsSyncFile: (handle: FileSystemFileHandle) => Promise<RandomAccessFile>;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// © 2026 Adobe. MIT License. See /LICENSE for details.
|
|
2
|
+
/**
|
|
3
|
+
* OPFS sync-handle adapter. Only callable inside a dedicated worker
|
|
4
|
+
* because `FileSystemSyncAccessHandle` is unavailable on the main
|
|
5
|
+
* thread per the spec.
|
|
6
|
+
*
|
|
7
|
+
* The async surface in `RandomAccessFile` is satisfied by sync calls
|
|
8
|
+
* under the hood.
|
|
9
|
+
*/
|
|
10
|
+
export const createOpfsSyncFile = async (handle) => {
|
|
11
|
+
const handleAsSync = handle;
|
|
12
|
+
if (typeof handleAsSync.createSyncAccessHandle !== "function") {
|
|
13
|
+
throw new Error("OpfsSyncFile requires FileSystemFileHandle.createSyncAccessHandle (only available in dedicated workers)");
|
|
14
|
+
}
|
|
15
|
+
const sync = await handleAsSync.createSyncAccessHandle();
|
|
16
|
+
let closed = false;
|
|
17
|
+
const ensureOpen = () => {
|
|
18
|
+
if (closed)
|
|
19
|
+
throw new Error("OpfsSyncFile is closed");
|
|
20
|
+
};
|
|
21
|
+
return {
|
|
22
|
+
async readAt(offset, length) {
|
|
23
|
+
ensureOpen();
|
|
24
|
+
const buffer = new Uint8Array(length);
|
|
25
|
+
const bytesRead = sync.read(buffer, { at: offset });
|
|
26
|
+
return buffer.subarray(0, bytesRead).slice();
|
|
27
|
+
},
|
|
28
|
+
async writeAt(offset, bytes) {
|
|
29
|
+
ensureOpen();
|
|
30
|
+
sync.write(bytes, { at: offset });
|
|
31
|
+
},
|
|
32
|
+
async appendAt(bytes) {
|
|
33
|
+
ensureOpen();
|
|
34
|
+
const start = sync.getSize();
|
|
35
|
+
sync.write(bytes, { at: start });
|
|
36
|
+
return start + bytes.byteLength;
|
|
37
|
+
},
|
|
38
|
+
async size() {
|
|
39
|
+
ensureOpen();
|
|
40
|
+
return sync.getSize();
|
|
41
|
+
},
|
|
42
|
+
async truncate(length) {
|
|
43
|
+
ensureOpen();
|
|
44
|
+
sync.truncate(length);
|
|
45
|
+
},
|
|
46
|
+
async sync() {
|
|
47
|
+
ensureOpen();
|
|
48
|
+
sync.flush();
|
|
49
|
+
},
|
|
50
|
+
async close() {
|
|
51
|
+
if (closed)
|
|
52
|
+
return;
|
|
53
|
+
closed = true;
|
|
54
|
+
sync.close();
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
};
|
|
58
|
+
//# sourceMappingURL=opfs-sync-file.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"opfs-sync-file.js","sourceRoot":"","sources":["../../src/browser/opfs-sync-file.ts"],"names":[],"mappings":"AAAA,uDAAuD;AAwBvD;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,KAAK,EACnC,MAA4B,EACH,EAAE;IAC3B,MAAM,YAAY,GAAG,MAAoD,CAAC;IAC1E,IAAI,OAAO,YAAY,CAAC,sBAAsB,KAAK,UAAU,EAAE,CAAC;QAC5D,MAAM,IAAI,KAAK,CACX,yGAAyG,CAC5G,CAAC;IACN,CAAC;IACD,MAAM,IAAI,GAAyB,MAAM,YAAY,CAAC,sBAAsB,EAAE,CAAC;IAC/E,IAAI,MAAM,GAAG,KAAK,CAAC;IAEnB,MAAM,UAAU,GAAG,GAAS,EAAE;QAC1B,IAAI,MAAM;YAAE,MAAM,IAAI,KAAK,CAAC,wBAAwB,CAAC,CAAC;IAC1D,CAAC,CAAC;IAEF,OAAO;QACH,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM;YACvB,UAAU,EAAE,CAAC;YACb,MAAM,MAAM,GAAG,IAAI,UAAU,CAAC,MAAM,CAAC,CAAC;YACtC,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC;YACpD,OAAO,MAAM,CAAC,QAAQ,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC,KAAK,EAAE,CAAC;QACjD,CAAC;QACD,KAAK,CAAC,OAAO,CAAC,MAAM,EAAE,KAAK;YACvB,UAAU,EAAE,CAAC;YACb,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,EAAE,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC;QACtC,CAAC;QACD,KAAK,CAAC,QAAQ,CAAC,KAAK;YAChB,UAAU,EAAE,CAAC;YACb,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,EAAE,CAAC;YAC7B,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,EAAE,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC;YACjC,OAAO,KAAK,GAAG,KAAK,CAAC,UAAU,CAAC;QACpC,CAAC;QACD,KAAK,CAAC,IAAI;YACN,UAAU,EAAE,CAAC;YACb,OAAO,IAAI,CAAC,OAAO,EAAE,CAAC;QAC1B,CAAC;QACD,KAAK,CAAC,QAAQ,CAAC,MAAM;YACjB,UAAU,EAAE,CAAC;YACb,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;QAC1B,CAAC;QACD,KAAK,CAAC,IAAI;YACN,UAAU,EAAE,CAAC;YACb,IAAI,CAAC,KAAK,EAAE,CAAC;QACjB,CAAC;QACD,KAAK,CAAC,KAAK;YACP,IAAI,MAAM;gBAAE,OAAO;YACnB,MAAM,GAAG,IAAI,CAAC;YACd,IAAI,CAAC,KAAK,EAAE,CAAC;QACjB,CAAC;KACJ,CAAC;AACN,CAAC,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { TypedBuffer } from "@adobe/data/typed-buffer";
|
|
2
|
+
import type { ColumnEncoder } from "./types.js";
|
|
3
|
+
/**
|
|
4
|
+
* Build a {@link ColumnEncoder} for a given TypedBuffer. The buffer's
|
|
5
|
+
* `type` (number / struct / enum / const / array) selects the strategy:
|
|
6
|
+
*
|
|
7
|
+
* number / struct / enum - fixed-stride; encoded as a copy of the
|
|
8
|
+
* relevant slice of the underlying TypedArray.
|
|
9
|
+
* boolean - bit-packed in memory; per-row JSON in the journal.
|
|
10
|
+
* const - nothing per row; manifest carries the value.
|
|
11
|
+
* array - variable-length; per-row JSON into the journal.
|
|
12
|
+
*
|
|
13
|
+
* The `componentName` is used for diagnostics only.
|
|
14
|
+
*/
|
|
15
|
+
export declare const createColumnEncoder: (componentName: string, buffer: TypedBuffer<unknown>) => ColumnEncoder;
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
// © 2026 Adobe. MIT License. See /LICENSE for details.
|
|
2
|
+
// Implementation note (V8 hidden classes / monomorphic IC):
|
|
3
|
+
//
|
|
4
|
+
// The hot path calls `encoder.encodeRows(...)` and
|
|
5
|
+
// `encoder.encodeRowValue(...)` once per touched component per
|
|
6
|
+
// changed entity per transaction. With one fresh closure-shaped
|
|
7
|
+
// encoder object per archetype × column, the IC at those call sites
|
|
8
|
+
// goes polymorphic → megamorphic almost immediately. We therefore
|
|
9
|
+
// implement encoders as classes so every "fixed" encoder shares one
|
|
10
|
+
// hidden class and every "journal" encoder shares another. See
|
|
11
|
+
// `.claude/skills/performance/SKILL.md` and
|
|
12
|
+
// `packages/data/src/typed-buffer/create-number-buffer.ts` for the
|
|
13
|
+
// pattern this mirrors.
|
|
14
|
+
//
|
|
15
|
+
// Public-API discipline: the classes are NOT exported. Consumers see
|
|
16
|
+
// only the `ColumnEncoder` interface and the `createColumnEncoder`
|
|
17
|
+
// factory; nothing in the calling code uses `instanceof`.
|
|
18
|
+
const FIXED_STORAGE = "fixed";
|
|
19
|
+
const CONST_STORAGE = "manifest";
|
|
20
|
+
const JOURNAL_STORAGE = "journal";
|
|
21
|
+
class FixedColumnEncoder {
|
|
22
|
+
constructor(buffer) {
|
|
23
|
+
this.storage = FIXED_STORAGE;
|
|
24
|
+
this.buffer = buffer;
|
|
25
|
+
this.stride = buffer.typedArrayElementSizeInBytes;
|
|
26
|
+
// Hoist the per-row element count out of the hot loop. Every
|
|
27
|
+
// typed-array view we slice goes through this multiplier; it
|
|
28
|
+
// never changes for a given column, so cache it on the
|
|
29
|
+
// instance once.
|
|
30
|
+
this.elementsPerRow = this.stride / buffer.getTypedArray().BYTES_PER_ELEMENT;
|
|
31
|
+
}
|
|
32
|
+
encodeRows(rowStart, rowCount) {
|
|
33
|
+
return this.encodeFixed(rowStart, rowCount);
|
|
34
|
+
}
|
|
35
|
+
// Fixed-stride columns DO populate journal payloads — the column
|
|
36
|
+
// slice file write is a fast-load snapshot optimization, the
|
|
37
|
+
// journal is the WAL and must carry the row bytes so crash
|
|
38
|
+
// recovery can repair partial column writes.
|
|
39
|
+
encodeRowValue(rowIndex) {
|
|
40
|
+
const out = this.encodeFixed(rowIndex, 1);
|
|
41
|
+
return new Uint8Array(out);
|
|
42
|
+
}
|
|
43
|
+
encodeFixed(rowStart, rowCount) {
|
|
44
|
+
const typedArray = this.buffer.getTypedArray();
|
|
45
|
+
const startElement = rowStart * this.elementsPerRow;
|
|
46
|
+
const endElement = startElement + rowCount * this.elementsPerRow;
|
|
47
|
+
const view = typedArray.subarray(startElement, endElement);
|
|
48
|
+
const out = new ArrayBuffer(view.byteLength);
|
|
49
|
+
new Uint8Array(out).set(new Uint8Array(view.buffer, view.byteOffset, view.byteLength));
|
|
50
|
+
return out;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
class ConstColumnEncoder {
|
|
54
|
+
constructor() {
|
|
55
|
+
this.storage = CONST_STORAGE;
|
|
56
|
+
this.stride = 0;
|
|
57
|
+
}
|
|
58
|
+
encodeRows() {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
encodeRowValue() {
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
class JournalColumnEncoder {
|
|
66
|
+
constructor(buffer) {
|
|
67
|
+
this.storage = JOURNAL_STORAGE;
|
|
68
|
+
this.stride = 0;
|
|
69
|
+
this.buffer = buffer;
|
|
70
|
+
this.textEncoder = new TextEncoder();
|
|
71
|
+
}
|
|
72
|
+
encodeRows() {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
encodeRowValue(rowIndex) {
|
|
76
|
+
const value = this.buffer.get(rowIndex);
|
|
77
|
+
return this.textEncoder.encode(JSON.stringify(value));
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Build a {@link ColumnEncoder} for a given TypedBuffer. The buffer's
|
|
82
|
+
* `type` (number / struct / enum / const / array) selects the strategy:
|
|
83
|
+
*
|
|
84
|
+
* number / struct / enum - fixed-stride; encoded as a copy of the
|
|
85
|
+
* relevant slice of the underlying TypedArray.
|
|
86
|
+
* boolean - bit-packed in memory; per-row JSON in the journal.
|
|
87
|
+
* const - nothing per row; manifest carries the value.
|
|
88
|
+
* array - variable-length; per-row JSON into the journal.
|
|
89
|
+
*
|
|
90
|
+
* The `componentName` is used for diagnostics only.
|
|
91
|
+
*/
|
|
92
|
+
export const createColumnEncoder = (componentName, buffer) => {
|
|
93
|
+
const bufferType = buffer.type;
|
|
94
|
+
switch (bufferType) {
|
|
95
|
+
case "number":
|
|
96
|
+
case "struct":
|
|
97
|
+
case "enum":
|
|
98
|
+
return new FixedColumnEncoder(buffer);
|
|
99
|
+
case "const":
|
|
100
|
+
return new ConstColumnEncoder();
|
|
101
|
+
case "boolean":
|
|
102
|
+
case "array":
|
|
103
|
+
return new JournalColumnEncoder(buffer);
|
|
104
|
+
default: {
|
|
105
|
+
const exhaustive = bufferType;
|
|
106
|
+
throw new Error(`Unsupported TypedBuffer type for encoder: ${String(exhaustive)} (component=${componentName})`);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
//# sourceMappingURL=create-column-encoder.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"create-column-encoder.js","sourceRoot":"","sources":["../../src/encoder/create-column-encoder.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Encode an array of per-row UTF-8 JSON payloads into a snapshot
|
|
3
|
+
* buffer. Pass `undefined` for rows that should be treated as absent
|
|
4
|
+
* (the decoder will skip them; the archetype default stands).
|
|
5
|
+
*
|
|
6
|
+
* Each `Uint8Array` in `rows` must already be UTF-8 encoded JSON —
|
|
7
|
+
* this mirrors the output of `JournalColumnEncoder.encodeRowValue`.
|
|
8
|
+
*/
|
|
9
|
+
export declare const encodeJournalSnapshot: (rows: ReadonlyArray<Uint8Array | undefined>) => ArrayBuffer;
|
|
10
|
+
/**
|
|
11
|
+
* Decode a snapshot buffer produced by {@link encodeJournalSnapshot}.
|
|
12
|
+
*
|
|
13
|
+
* Returns an array of length `count` where each element is either a
|
|
14
|
+
* `Uint8Array` (the raw UTF-8 JSON bytes for that row) or `undefined`
|
|
15
|
+
* (ABSENT — caller should not write the row into the buffer).
|
|
16
|
+
*
|
|
17
|
+
* Torn-tail: if any record's `len` extends past the buffer boundary,
|
|
18
|
+
* all remaining records (including that one) are returned as `undefined`
|
|
19
|
+
* and decoding stops — no error is thrown.
|
|
20
|
+
*/
|
|
21
|
+
export declare const decodeJournalSnapshot: (src: ArrayBuffer) => ReadonlyArray<Uint8Array | undefined>;
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
// © 2026 Adobe. MIT License. See /LICENSE for details.
|
|
2
|
+
//
|
|
3
|
+
// Codec for journal-storage ("array") column snapshot files.
|
|
4
|
+
//
|
|
5
|
+
// Format (all integers little-endian):
|
|
6
|
+
//
|
|
7
|
+
// <count: u32>
|
|
8
|
+
// [ <len: u32> <utf8-JSON-bytes: len bytes> ] × count
|
|
9
|
+
//
|
|
10
|
+
// Torn-tail tolerance: if a record's `len` would run past EOF, the
|
|
11
|
+
// decoder stops and returns only the records decoded so far. Rows that
|
|
12
|
+
// had no written value (sparse) are encoded as the 4-byte length 0xFFFFFFFF
|
|
13
|
+
// (ABSENT sentinel), decoded back to `undefined` so callers can skip
|
|
14
|
+
// setting them and let the archetype's default value stand.
|
|
15
|
+
//
|
|
16
|
+
// Why length-prefixed binary rather than a plain JSON array:
|
|
17
|
+
//
|
|
18
|
+
// - One JSON.parse per record keeps peak memory bounded (O(max value
|
|
19
|
+
// size)), whereas a top-level array materialises everything at once.
|
|
20
|
+
// - A torn file leaves all complete records intact; a torn JSON file
|
|
21
|
+
// is unparseable in its entirety.
|
|
22
|
+
// - Matches the journal codec's torn-tail handling, keeping the
|
|
23
|
+
// crash-safety story uniform across all persisted files.
|
|
24
|
+
const ABSENT = 0xffff_ffff;
|
|
25
|
+
/**
|
|
26
|
+
* Encode an array of per-row UTF-8 JSON payloads into a snapshot
|
|
27
|
+
* buffer. Pass `undefined` for rows that should be treated as absent
|
|
28
|
+
* (the decoder will skip them; the archetype default stands).
|
|
29
|
+
*
|
|
30
|
+
* Each `Uint8Array` in `rows` must already be UTF-8 encoded JSON —
|
|
31
|
+
* this mirrors the output of `JournalColumnEncoder.encodeRowValue`.
|
|
32
|
+
*/
|
|
33
|
+
export const encodeJournalSnapshot = (rows) => {
|
|
34
|
+
// Compute total byte size up-front so we do a single allocation.
|
|
35
|
+
let totalBytes = 4; // count u32
|
|
36
|
+
for (const row of rows) {
|
|
37
|
+
totalBytes += 4; // len u32
|
|
38
|
+
if (row !== undefined) {
|
|
39
|
+
totalBytes += row.byteLength;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
const buf = new ArrayBuffer(totalBytes);
|
|
43
|
+
const view = new DataView(buf);
|
|
44
|
+
let offset = 0;
|
|
45
|
+
view.setUint32(offset, rows.length, true);
|
|
46
|
+
offset += 4;
|
|
47
|
+
for (const row of rows) {
|
|
48
|
+
if (row === undefined || row.byteLength === 0) {
|
|
49
|
+
view.setUint32(offset, ABSENT, true);
|
|
50
|
+
offset += 4;
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
view.setUint32(offset, row.byteLength, true);
|
|
54
|
+
offset += 4;
|
|
55
|
+
new Uint8Array(buf, offset, row.byteLength).set(row);
|
|
56
|
+
offset += row.byteLength;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return buf;
|
|
60
|
+
};
|
|
61
|
+
/**
|
|
62
|
+
* Decode a snapshot buffer produced by {@link encodeJournalSnapshot}.
|
|
63
|
+
*
|
|
64
|
+
* Returns an array of length `count` where each element is either a
|
|
65
|
+
* `Uint8Array` (the raw UTF-8 JSON bytes for that row) or `undefined`
|
|
66
|
+
* (ABSENT — caller should not write the row into the buffer).
|
|
67
|
+
*
|
|
68
|
+
* Torn-tail: if any record's `len` extends past the buffer boundary,
|
|
69
|
+
* all remaining records (including that one) are returned as `undefined`
|
|
70
|
+
* and decoding stops — no error is thrown.
|
|
71
|
+
*/
|
|
72
|
+
export const decodeJournalSnapshot = (src) => {
|
|
73
|
+
const byteLength = src.byteLength;
|
|
74
|
+
if (byteLength < 4)
|
|
75
|
+
return [];
|
|
76
|
+
const view = new DataView(src);
|
|
77
|
+
let offset = 0;
|
|
78
|
+
const count = view.getUint32(offset, true);
|
|
79
|
+
offset += 4;
|
|
80
|
+
const result = new Array(count);
|
|
81
|
+
for (let i = 0; i < count; i++) {
|
|
82
|
+
if (offset + 4 > byteLength) {
|
|
83
|
+
// Torn header — fill remainder with undefined.
|
|
84
|
+
result.fill(undefined, i);
|
|
85
|
+
break;
|
|
86
|
+
}
|
|
87
|
+
const len = view.getUint32(offset, true);
|
|
88
|
+
offset += 4;
|
|
89
|
+
if (len === ABSENT) {
|
|
90
|
+
result[i] = undefined;
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
if (offset + len > byteLength) {
|
|
94
|
+
// Torn body — treat this and all remaining records as absent.
|
|
95
|
+
result.fill(undefined, i);
|
|
96
|
+
break;
|
|
97
|
+
}
|
|
98
|
+
result[i] = new Uint8Array(src, offset, len).slice();
|
|
99
|
+
offset += len;
|
|
100
|
+
}
|
|
101
|
+
return result;
|
|
102
|
+
};
|
|
103
|
+
//# sourceMappingURL=journal-snapshot-codec.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"journal-snapshot-codec.js","sourceRoot":"","sources":["../../src/encoder/journal-snapshot-codec.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|