@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.
Files changed (198) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +258 -0
  3. package/dist/backend/memory-backend.d.ts +6 -0
  4. package/dist/backend/memory-backend.js +92 -0
  5. package/dist/backend/memory-backend.js.map +1 -0
  6. package/dist/backend/memory-backend.test.d.ts +1 -0
  7. package/dist/backend/memory-backend.test.js +7 -0
  8. package/dist/backend/memory-backend.test.js.map +1 -0
  9. package/dist/backend/memory-file.d.ts +7 -0
  10. package/dist/backend/memory-file.js +90 -0
  11. package/dist/backend/memory-file.js.map +1 -0
  12. package/dist/backend/memory-file.test.d.ts +1 -0
  13. package/dist/backend/memory-file.test.js +5 -0
  14. package/dist/backend/memory-file.test.js.map +1 -0
  15. package/dist/backend/persistence-backend.conformance.d.ts +10 -0
  16. package/dist/backend/persistence-backend.conformance.js +104 -0
  17. package/dist/backend/persistence-backend.conformance.js.map +1 -0
  18. package/dist/backend/persistence-backend.d.ts +54 -0
  19. package/dist/backend/persistence-backend.js +3 -0
  20. package/dist/backend/persistence-backend.js.map +1 -0
  21. package/dist/backend/random-access-file.conformance.d.ts +20 -0
  22. package/dist/backend/random-access-file.conformance.js +156 -0
  23. package/dist/backend/random-access-file.conformance.js.map +1 -0
  24. package/dist/backend/random-access-file.d.ts +39 -0
  25. package/dist/backend/random-access-file.js +3 -0
  26. package/dist/backend/random-access-file.js.map +1 -0
  27. package/dist/backend/validate-rel-path.d.ts +14 -0
  28. package/dist/backend/validate-rel-path.js +44 -0
  29. package/dist/backend/validate-rel-path.js.map +1 -0
  30. package/dist/backend/validate-rel-path.test.d.ts +1 -0
  31. package/dist/backend/validate-rel-path.test.js +37 -0
  32. package/dist/backend/validate-rel-path.test.js.map +1 -0
  33. package/dist/browser/browser-worker-bootstrap.d.ts +1 -0
  34. package/dist/browser/browser-worker-bootstrap.js +97 -0
  35. package/dist/browser/browser-worker-bootstrap.js.map +1 -0
  36. package/dist/browser/browser-worker-transport.browser.test.d.ts +1 -0
  37. package/dist/browser/browser-worker-transport.browser.test.js +156 -0
  38. package/dist/browser/browser-worker-transport.browser.test.js.map +1 -0
  39. package/dist/browser/browser-worker-transport.d.ts +33 -0
  40. package/dist/browser/browser-worker-transport.js +120 -0
  41. package/dist/browser/browser-worker-transport.js.map +1 -0
  42. package/dist/browser/create-opfs-provider.d.ts +23 -0
  43. package/dist/browser/create-opfs-provider.js +49 -0
  44. package/dist/browser/create-opfs-provider.js.map +1 -0
  45. package/dist/browser/index.d.ts +6 -0
  46. package/dist/browser/index.js +12 -0
  47. package/dist/browser/index.js.map +1 -0
  48. package/dist/browser/mount-opfs.d.ts +12 -0
  49. package/dist/browser/mount-opfs.js +15 -0
  50. package/dist/browser/mount-opfs.js.map +1 -0
  51. package/dist/browser/opfs-backend-lock.browser.test.d.ts +1 -0
  52. package/dist/browser/opfs-backend-lock.browser.test.js +127 -0
  53. package/dist/browser/opfs-backend-lock.browser.test.js.map +1 -0
  54. package/dist/browser/opfs-backend.d.ts +6 -0
  55. package/dist/browser/opfs-backend.js +162 -0
  56. package/dist/browser/opfs-backend.js.map +1 -0
  57. package/dist/browser/opfs-lock-test-worker.d.ts +1 -0
  58. package/dist/browser/opfs-lock-test-worker.js +50 -0
  59. package/dist/browser/opfs-lock-test-worker.js.map +1 -0
  60. package/dist/browser/opfs-sync-file.d.ts +10 -0
  61. package/dist/browser/opfs-sync-file.js +58 -0
  62. package/dist/browser/opfs-sync-file.js.map +1 -0
  63. package/dist/encoder/create-column-encoder.d.ts +15 -0
  64. package/dist/encoder/create-column-encoder.js +110 -0
  65. package/dist/encoder/create-column-encoder.js.map +1 -0
  66. package/dist/encoder/journal-snapshot-codec.d.ts +21 -0
  67. package/dist/encoder/journal-snapshot-codec.js +103 -0
  68. package/dist/encoder/journal-snapshot-codec.js.map +1 -0
  69. package/dist/encoder/journal-snapshot-codec.test.d.ts +1 -0
  70. package/dist/encoder/journal-snapshot-codec.test.js +96 -0
  71. package/dist/encoder/journal-snapshot-codec.test.js.map +1 -0
  72. package/dist/encoder/types.d.ts +72 -0
  73. package/dist/encoder/types.js +3 -0
  74. package/dist/encoder/types.js.map +1 -0
  75. package/dist/index.d.ts +24 -0
  76. package/dist/index.js +15 -0
  77. package/dist/index.js.map +1 -0
  78. package/dist/journal/journal-codec.d.ts +28 -0
  79. package/dist/journal/journal-codec.js +82 -0
  80. package/dist/journal/journal-codec.js.map +1 -0
  81. package/dist/journal/journal-codec.test.d.ts +1 -0
  82. package/dist/journal/journal-codec.test.js +106 -0
  83. package/dist/journal/journal-codec.test.js.map +1 -0
  84. package/dist/journal/journal-entry.d.ts +63 -0
  85. package/dist/journal/journal-entry.js +28 -0
  86. package/dist/journal/journal-entry.js.map +1 -0
  87. package/dist/node/create-node-fs-provider.d.ts +34 -0
  88. package/dist/node/create-node-fs-provider.js +48 -0
  89. package/dist/node/create-node-fs-provider.js.map +1 -0
  90. package/dist/node/index.d.ts +6 -0
  91. package/dist/node/index.js +10 -0
  92. package/dist/node/index.js.map +1 -0
  93. package/dist/node/mount-node-fs.d.ts +12 -0
  94. package/dist/node/mount-node-fs.js +15 -0
  95. package/dist/node/mount-node-fs.js.map +1 -0
  96. package/dist/node/node-fs-backend-lock.node.test.d.ts +1 -0
  97. package/dist/node/node-fs-backend-lock.node.test.js +160 -0
  98. package/dist/node/node-fs-backend-lock.node.test.js.map +1 -0
  99. package/dist/node/node-fs-backend.d.ts +21 -0
  100. package/dist/node/node-fs-backend.js +186 -0
  101. package/dist/node/node-fs-backend.js.map +1 -0
  102. package/dist/node/node-fs-backend.node.test.d.ts +1 -0
  103. package/dist/node/node-fs-backend.node.test.js +17 -0
  104. package/dist/node/node-fs-backend.node.test.js.map +1 -0
  105. package/dist/node/node-fs-file.d.ts +6 -0
  106. package/dist/node/node-fs-file.js +57 -0
  107. package/dist/node/node-fs-file.js.map +1 -0
  108. package/dist/node/node-fs-file.node.test.d.ts +1 -0
  109. package/dist/node/node-fs-file.node.test.js +13 -0
  110. package/dist/node/node-fs-file.node.test.js.map +1 -0
  111. package/dist/node/node-fs-provider.node.test.d.ts +1 -0
  112. package/dist/node/node-fs-provider.node.test.js +119 -0
  113. package/dist/node/node-fs-provider.node.test.js.map +1 -0
  114. package/dist/node/node-worker-bootstrap.d.ts +1 -0
  115. package/dist/node/node-worker-bootstrap.js +104 -0
  116. package/dist/node/node-worker-bootstrap.js.map +1 -0
  117. package/dist/node/node-worker-transport.d.ts +27 -0
  118. package/dist/node/node-worker-transport.js +152 -0
  119. package/dist/node/node-worker-transport.js.map +1 -0
  120. package/dist/node/node-worker-transport.node.test.d.ts +1 -0
  121. package/dist/node/node-worker-transport.node.test.js +147 -0
  122. package/dist/node/node-worker-transport.node.test.js.map +1 -0
  123. package/dist/perftest/perf-harness.d.ts +74 -0
  124. package/dist/perftest/perf-harness.js +147 -0
  125. package/dist/perftest/perf-harness.js.map +1 -0
  126. package/dist/perftest/persistence-perf.d.ts +2 -0
  127. package/dist/perftest/persistence-perf.js +290 -0
  128. package/dist/perftest/persistence-perf.js.map +1 -0
  129. package/dist/perftest/run.d.ts +1 -0
  130. package/dist/perftest/run.js +28 -0
  131. package/dist/perftest/run.js.map +1 -0
  132. package/dist/provider/create-memory-provider.d.ts +16 -0
  133. package/dist/provider/create-memory-provider.js +44 -0
  134. package/dist/provider/create-memory-provider.js.map +1 -0
  135. package/dist/provider/mount.d.ts +20 -0
  136. package/dist/provider/mount.js +20 -0
  137. package/dist/provider/mount.js.map +1 -0
  138. package/dist/provider/persistence-provider.d.ts +63 -0
  139. package/dist/provider/persistence-provider.js +3 -0
  140. package/dist/provider/persistence-provider.js.map +1 -0
  141. package/dist/provider/persistence-provider.test.d.ts +9 -0
  142. package/dist/provider/persistence-provider.test.js +139 -0
  143. package/dist/provider/persistence-provider.test.js.map +1 -0
  144. package/dist/service/component-nonpersistent-durability.test.d.ts +1 -0
  145. package/dist/service/component-nonpersistent-durability.test.js +57 -0
  146. package/dist/service/component-nonpersistent-durability.test.js.map +1 -0
  147. package/dist/service/create-incremental-persistence-service.d.ts +7 -0
  148. package/dist/service/create-incremental-persistence-service.js +960 -0
  149. package/dist/service/create-incremental-persistence-service.js.map +1 -0
  150. package/dist/service/create-worker-persistence-service.d.ts +1 -0
  151. package/dist/service/create-worker-persistence-service.js +7 -0
  152. package/dist/service/create-worker-persistence-service.js.map +1 -0
  153. package/dist/service/create-worker-persistence-service.test.d.ts +1 -0
  154. package/dist/service/create-worker-persistence-service.test.js +291 -0
  155. package/dist/service/create-worker-persistence-service.test.js.map +1 -0
  156. package/dist/service/entity-location-cache.d.ts +15 -0
  157. package/dist/service/entity-location-cache.js +75 -0
  158. package/dist/service/entity-location-cache.js.map +1 -0
  159. package/dist/service/entity-location-density.test.d.ts +1 -0
  160. package/dist/service/entity-location-density.test.js +42 -0
  161. package/dist/service/entity-location-density.test.js.map +1 -0
  162. package/dist/service/incremental-persistence-service.d.ts +57 -0
  163. package/dist/service/incremental-persistence-service.js +3 -0
  164. package/dist/service/incremental-persistence-service.js.map +1 -0
  165. package/dist/service/intermediate-relocation-durability.test.d.ts +1 -0
  166. package/dist/service/intermediate-relocation-durability.test.js +89 -0
  167. package/dist/service/intermediate-relocation-durability.test.js.map +1 -0
  168. package/dist/service/internal-access.d.ts +34 -0
  169. package/dist/service/internal-access.js +43 -0
  170. package/dist/service/internal-access.js.map +1 -0
  171. package/dist/service/journal-replay.test.d.ts +1 -0
  172. package/dist/service/journal-replay.test.js +424 -0
  173. package/dist/service/journal-replay.test.js.map +1 -0
  174. package/dist/service/journal-storage-durability.test.d.ts +1 -0
  175. package/dist/service/journal-storage-durability.test.js +171 -0
  176. package/dist/service/journal-storage-durability.test.js.map +1 -0
  177. package/dist/service/load-roundtrip.test.d.ts +1 -0
  178. package/dist/service/load-roundtrip.test.js +224 -0
  179. package/dist/service/load-roundtrip.test.js.map +1 -0
  180. package/dist/service/persistence-complexity.test.d.ts +1 -0
  181. package/dist/service/persistence-complexity.test.js +275 -0
  182. package/dist/service/persistence-complexity.test.js.map +1 -0
  183. package/dist/service/worker-persistence-service.d.ts +24 -0
  184. package/dist/service/worker-persistence-service.js +8 -0
  185. package/dist/service/worker-persistence-service.js.map +1 -0
  186. package/dist/transport/inprocess-transport.d.ts +15 -0
  187. package/dist/transport/inprocess-transport.js +64 -0
  188. package/dist/transport/inprocess-transport.js.map +1 -0
  189. package/dist/transport/inprocess-transport.test.d.ts +1 -0
  190. package/dist/transport/inprocess-transport.test.js +100 -0
  191. package/dist/transport/inprocess-transport.test.js.map +1 -0
  192. package/dist/transport/router.d.ts +12 -0
  193. package/dist/transport/router.js +146 -0
  194. package/dist/transport/router.js.map +1 -0
  195. package/dist/transport/transport.d.ts +105 -0
  196. package/dist/transport/transport.js +3 -0
  197. package/dist/transport/transport.js.map +1 -0
  198. 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":"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"}
@@ -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":"AAAA,uDAAuD;AAKvD,4DAA4D;AAC5D,EAAE;AACF,mDAAmD;AACnD,+DAA+D;AAC/D,gEAAgE;AAChE,oEAAoE;AACpE,kEAAkE;AAClE,oEAAoE;AACpE,+DAA+D;AAC/D,4CAA4C;AAC5C,mEAAmE;AACnE,wBAAwB;AACxB,EAAE;AACF,qEAAqE;AACrE,mEAAmE;AACnE,0DAA0D;AAE1D,MAAM,aAAa,GAA8B,OAAO,CAAC;AACzD,MAAM,aAAa,GAA8B,UAAU,CAAC;AAC5D,MAAM,eAAe,GAA8B,SAAS,CAAC;AAE7D,MAAM,kBAAkB;IAMpB,YAAY,MAA4B;QALxB,YAAO,GAA8B,aAAa,CAAC;QAM/D,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC,4BAA4B,CAAC;QAClD,6DAA6D;QAC7D,6DAA6D;QAC7D,uDAAuD;QACvD,iBAAiB;QACjB,IAAI,CAAC,cAAc,GAAG,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC,aAAa,EAAE,CAAC,iBAAiB,CAAC;IACjF,CAAC;IAED,UAAU,CAAC,QAAgB,EAAE,QAAgB;QACzC,OAAO,IAAI,CAAC,WAAW,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;IAChD,CAAC;IAED,iEAAiE;IACjE,6DAA6D;IAC7D,2DAA2D;IAC3D,6CAA6C;IAC7C,cAAc,CAAC,QAAgB;QAC3B,MAAM,GAAG,GAAG,IAAI,CAAC,WAAW,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC;QAC1C,OAAO,IAAI,UAAU,CAAC,GAAG,CAAC,CAAC;IAC/B,CAAC;IAEO,WAAW,CAAC,QAAgB,EAAE,QAAgB;QAClD,MAAM,UAAU,GAAG,IAAI,CAAC,MAAM,CAAC,aAAa,EAAE,CAAC;QAC/C,MAAM,YAAY,GAAG,QAAQ,GAAG,IAAI,CAAC,cAAc,CAAC;QACpD,MAAM,UAAU,GAAG,YAAY,GAAG,QAAQ,GAAG,IAAI,CAAC,cAAc,CAAC;QACjE,MAAM,IAAI,GAAG,UAAU,CAAC,QAAQ,CAAC,YAAY,EAAE,UAAU,CAAC,CAAC;QAC3D,MAAM,GAAG,GAAG,IAAI,WAAW,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QAC7C,IAAI,UAAU,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,IAAI,UAAU,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,UAAU,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC;QACvF,OAAO,GAAG,CAAC;IACf,CAAC;CACJ;AAED,MAAM,kBAAkB;IAAxB;QACoB,YAAO,GAA8B,aAAa,CAAC;QACnD,WAAM,GAAW,CAAC,CAAC;IASvC,CAAC;IAPG,UAAU;QACN,OAAO,IAAI,CAAC;IAChB,CAAC;IAED,cAAc;QACV,OAAO,IAAI,CAAC;IAChB,CAAC;CACJ;AAED,MAAM,oBAAoB;IAUtB,YAAY,MAA4B;QATxB,YAAO,GAA8B,eAAe,CAAC;QACrD,WAAM,GAAW,CAAC,CAAC;QAS/B,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,CAAC,WAAW,GAAG,IAAI,WAAW,EAAE,CAAC;IACzC,CAAC;IAED,UAAU;QACN,OAAO,IAAI,CAAC;IAChB,CAAC;IAED,cAAc,CAAC,QAAgB;QAC3B,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QACxC,OAAO,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC;IAC1D,CAAC;CACJ;AAED;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAC/B,aAAqB,EACrB,MAA4B,EACf,EAAE;IACf,MAAM,UAAU,GAAoB,MAAM,CAAC,IAAI,CAAC;IAChD,QAAQ,UAAU,EAAE,CAAC;QACjB,KAAK,QAAQ,CAAC;QACd,KAAK,QAAQ,CAAC;QACd,KAAK,MAAM;YACP,OAAO,IAAI,kBAAkB,CAAC,MAAM,CAAC,CAAC;QAC1C,KAAK,OAAO;YACR,OAAO,IAAI,kBAAkB,EAAE,CAAC;QACpC,KAAK,SAAS,CAAC;QACf,KAAK,OAAO;YACR,OAAO,IAAI,oBAAoB,CAAC,MAAM,CAAC,CAAC;QAC5C,OAAO,CAAC,CAAC,CAAC;YACN,MAAM,UAAU,GAAU,UAAU,CAAC;YACrC,MAAM,IAAI,KAAK,CAAC,6CAA6C,MAAM,CAAC,UAAU,CAAC,eAAe,aAAa,GAAG,CAAC,CAAC;QACpH,CAAC;IACL,CAAC;AACL,CAAC,CAAC"}
@@ -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 {};