@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,54 @@
1
+ import type { RandomAccessFile } from "./random-access-file.js";
2
+ /**
3
+ * Capability flags for a `PersistenceBackend`. Declared optionally by
4
+ * backends so that higher-level code can adapt its strategy.
5
+ */
6
+ export interface PersistenceBackendCapabilities {
7
+ /**
8
+ * Whether the backend supports partial byte-range writes via
9
+ * `RandomAccessFile.writeAt`. When `false` the service should prefer
10
+ * whole-file snapshot writes (e.g. for cloud blob stores). Currently
11
+ * informational — the existing journal/checkpoint strategy is used
12
+ * regardless.
13
+ */
14
+ readonly byteRangeWrites: boolean;
15
+ }
16
+ /**
17
+ * A directory of RandomAccessFile handles, rooted at some host-defined
18
+ * location (an OPFS directory, a Node fs path, or an in-memory map).
19
+ *
20
+ * All paths are relative to the root and validated by {@link validateRelPath}
21
+ * to prevent path traversal.
22
+ */
23
+ export interface PersistenceBackend {
24
+ /**
25
+ * Optional capability advertisement. Omit (or omit individual fields) to
26
+ * indicate the backend has the same capabilities as the default
27
+ * (`byteRangeWrites: true`).
28
+ */
29
+ readonly capabilities?: Partial<PersistenceBackendCapabilities>;
30
+ /**
31
+ * Open (and create if missing) a file at `relPath`.
32
+ */
33
+ open(relPath: string): Promise<RandomAccessFile>;
34
+ /**
35
+ * List entries directly under `relPath`. Returns names only, not full paths.
36
+ */
37
+ list(relPath: string): Promise<readonly string[]>;
38
+ /**
39
+ * Remove a file. No-op if the file does not exist.
40
+ */
41
+ remove(relPath: string): Promise<void>;
42
+ /**
43
+ * Atomic rename within the root.
44
+ */
45
+ rename(from: string, to: string): Promise<void>;
46
+ /**
47
+ * Optional teardown hook. Backends that hold process-wide resources
48
+ * — a single-writer lock file, a sync access handle, an open
49
+ * directory descriptor — release them here. The router calls this
50
+ * during `close()`. Backends without persistent resources can omit
51
+ * it.
52
+ */
53
+ dispose?(): Promise<void>;
54
+ }
@@ -0,0 +1,3 @@
1
+ // © 2026 Adobe. MIT License. See /LICENSE for details.
2
+ export {};
3
+ //# sourceMappingURL=persistence-backend.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"persistence-backend.js","sourceRoot":"","sources":["../../src/backend/persistence-backend.ts"],"names":[],"mappings":"AAAA,uDAAuD"}
@@ -0,0 +1,20 @@
1
+ import { RandomAccessFile } from "./random-access-file.js";
2
+ /**
3
+ * Conformance suite for RandomAccessFile implementations. Invoke from a
4
+ * thin per-runtime test file with the appropriate factory:
5
+ *
6
+ * // memory-file.test.ts
7
+ * runRandomAccessFileConformance("memory", () => createMemoryFile());
8
+ *
9
+ * // node-fs-file.node.test.ts
10
+ * runRandomAccessFileConformance("node-fs", async () => {
11
+ * const tmp = await fs.mkdtemp(...);
12
+ * return createNodeFsFile(path.join(tmp, "f.bin"));
13
+ * });
14
+ *
15
+ * // opfs-sync-file.browser.test.ts
16
+ * runRandomAccessFileConformance("opfs", async () => createOpfsSyncFile(...));
17
+ *
18
+ * All implementations must satisfy identical semantics.
19
+ */
20
+ export declare const runRandomAccessFileConformance: (name: string, createFile: () => RandomAccessFile | Promise<RandomAccessFile>) => void;
@@ -0,0 +1,156 @@
1
+ // © 2026 Adobe. MIT License. See /LICENSE for details.
2
+ import { describe, expect, test } from "vitest";
3
+ /**
4
+ * Conformance suite for RandomAccessFile implementations. Invoke from a
5
+ * thin per-runtime test file with the appropriate factory:
6
+ *
7
+ * // memory-file.test.ts
8
+ * runRandomAccessFileConformance("memory", () => createMemoryFile());
9
+ *
10
+ * // node-fs-file.node.test.ts
11
+ * runRandomAccessFileConformance("node-fs", async () => {
12
+ * const tmp = await fs.mkdtemp(...);
13
+ * return createNodeFsFile(path.join(tmp, "f.bin"));
14
+ * });
15
+ *
16
+ * // opfs-sync-file.browser.test.ts
17
+ * runRandomAccessFileConformance("opfs", async () => createOpfsSyncFile(...));
18
+ *
19
+ * All implementations must satisfy identical semantics.
20
+ */
21
+ export const runRandomAccessFileConformance = (name, createFile) => {
22
+ describe(`RandomAccessFile conformance: ${name}`, () => {
23
+ test("size() of a new file is 0", async () => {
24
+ const file = await createFile();
25
+ try {
26
+ expect(await file.size()).toBe(0);
27
+ }
28
+ finally {
29
+ await file.close();
30
+ }
31
+ });
32
+ test("appendAt grows the file and returns the new size", async () => {
33
+ const file = await createFile();
34
+ try {
35
+ const newSize = await file.appendAt(new Uint8Array([1, 2, 3]));
36
+ expect(newSize).toBe(3);
37
+ expect(await file.size()).toBe(3);
38
+ }
39
+ finally {
40
+ await file.close();
41
+ }
42
+ });
43
+ test("writeAt then readAt round-trips at non-zero offset", async () => {
44
+ const file = await createFile();
45
+ try {
46
+ const payload = new Uint8Array([10, 20, 30, 40]);
47
+ await file.writeAt(8, payload);
48
+ expect(await file.size()).toBe(12);
49
+ const read = await file.readAt(8, 4);
50
+ expect(Array.from(read)).toEqual([10, 20, 30, 40]);
51
+ }
52
+ finally {
53
+ await file.close();
54
+ }
55
+ });
56
+ test("writeAt grows the file and zero-fills the gap", async () => {
57
+ const file = await createFile();
58
+ try {
59
+ await file.writeAt(4, new Uint8Array([99]));
60
+ expect(await file.size()).toBe(5);
61
+ const read = await file.readAt(0, 5);
62
+ expect(Array.from(read)).toEqual([0, 0, 0, 0, 99]);
63
+ }
64
+ finally {
65
+ await file.close();
66
+ }
67
+ });
68
+ test("readAt past EOF returns a short buffer", async () => {
69
+ const file = await createFile();
70
+ try {
71
+ await file.appendAt(new Uint8Array([1, 2, 3]));
72
+ const read = await file.readAt(2, 10);
73
+ expect(read.byteLength).toBe(1);
74
+ expect(read[0]).toBe(3);
75
+ }
76
+ finally {
77
+ await file.close();
78
+ }
79
+ });
80
+ test("readAt entirely past EOF returns an empty buffer", async () => {
81
+ const file = await createFile();
82
+ try {
83
+ const read = await file.readAt(10, 5);
84
+ expect(read.byteLength).toBe(0);
85
+ }
86
+ finally {
87
+ await file.close();
88
+ }
89
+ });
90
+ test("truncate shrinks reported size and zeroes the freed range when grown back", async () => {
91
+ const file = await createFile();
92
+ try {
93
+ await file.writeAt(0, new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]));
94
+ await file.truncate(4);
95
+ expect(await file.size()).toBe(4);
96
+ await file.truncate(8);
97
+ const read = await file.readAt(0, 8);
98
+ expect(Array.from(read)).toEqual([1, 2, 3, 4, 0, 0, 0, 0]);
99
+ }
100
+ finally {
101
+ await file.close();
102
+ }
103
+ });
104
+ test("multiple writeAt to disjoint ranges produce expected layout", async () => {
105
+ const file = await createFile();
106
+ try {
107
+ await Promise.all([
108
+ file.writeAt(0, new Uint8Array([1, 2])),
109
+ file.writeAt(8, new Uint8Array([3, 4])),
110
+ file.writeAt(16, new Uint8Array([5, 6])),
111
+ ]);
112
+ expect(await file.size()).toBe(18);
113
+ const read = await file.readAt(0, 18);
114
+ expect(Array.from(read)).toEqual([
115
+ 1, 2, 0, 0, 0, 0, 0, 0,
116
+ 3, 4, 0, 0, 0, 0, 0, 0,
117
+ 5, 6,
118
+ ]);
119
+ }
120
+ finally {
121
+ await file.close();
122
+ }
123
+ });
124
+ test("operations after close() reject", async () => {
125
+ const file = await createFile();
126
+ await file.close();
127
+ await expect(file.size()).rejects.toThrow();
128
+ });
129
+ test("readAt returns a copy, not a reference into the buffer", async () => {
130
+ const file = await createFile();
131
+ try {
132
+ await file.writeAt(0, new Uint8Array([1, 2, 3]));
133
+ const a = await file.readAt(0, 3);
134
+ a[0] = 99;
135
+ const b = await file.readAt(0, 3);
136
+ expect(b[0]).toBe(1);
137
+ }
138
+ finally {
139
+ await file.close();
140
+ }
141
+ });
142
+ test("sync() does not throw and is idempotent", async () => {
143
+ const file = await createFile();
144
+ try {
145
+ await file.sync();
146
+ await file.appendAt(new Uint8Array([1]));
147
+ await file.sync();
148
+ await file.sync();
149
+ }
150
+ finally {
151
+ await file.close();
152
+ }
153
+ });
154
+ });
155
+ };
156
+ //# sourceMappingURL=random-access-file.conformance.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"random-access-file.conformance.js","sourceRoot":"","sources":["../../src/backend/random-access-file.conformance.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Lowest-level file abstraction. The sole runtime seam between the
3
+ * persistence package and the host environment.
4
+ *
5
+ * Implementations:
6
+ *
7
+ * MemoryFile - ArrayBuffer-backed; tests and the in-process
8
+ * Node server option.
9
+ * NodeFsFile - wraps node:fs.promises.FileHandle.
10
+ * OpfsSyncFile - wraps FileSystemSyncAccessHandle (browser
11
+ * dedicated worker only).
12
+ *
13
+ * The interface is async to keep the contract uniform across runtimes;
14
+ * sync-handle implementations (OPFS) satisfy it with sync calls under
15
+ * the hood.
16
+ */
17
+ export interface RandomAccessFile {
18
+ /**
19
+ * Read `length` bytes starting at `offset`. May return fewer bytes
20
+ * than requested if the read goes past EOF.
21
+ */
22
+ readAt(offset: number, length: number): Promise<Uint8Array>;
23
+ /**
24
+ * Write `bytes` starting at `offset`. Grows the file if necessary.
25
+ */
26
+ writeAt(offset: number, bytes: Uint8Array): Promise<void>;
27
+ /**
28
+ * Append `bytes` at the end of the file. Returns the new file size.
29
+ */
30
+ appendAt(bytes: Uint8Array): Promise<number>;
31
+ /** Current file size in bytes. */
32
+ size(): Promise<number>;
33
+ /** Truncate the file to `length` bytes (zero-fills if growing). */
34
+ truncate(length: number): Promise<void>;
35
+ /** Flush any buffered writes to durable storage. */
36
+ sync(): Promise<void>;
37
+ /** Close the file handle. Subsequent operations reject. */
38
+ close(): Promise<void>;
39
+ }
@@ -0,0 +1,3 @@
1
+ // © 2026 Adobe. MIT License. See /LICENSE for details.
2
+ export {};
3
+ //# sourceMappingURL=random-access-file.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"random-access-file.js","sourceRoot":"","sources":["../../src/backend/random-access-file.ts"],"names":[],"mappings":"AAAA,uDAAuD"}
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Validate that a relative path is safe to use within a backend root.
3
+ * Rejects:
4
+ *
5
+ * - empty paths
6
+ * - absolute paths (leading '/' or drive letter)
7
+ * - paths containing '..' segments (path traversal)
8
+ * - null bytes
9
+ * - backslashes (we normalize on '/')
10
+ *
11
+ * Defense-in-depth against the path-traversal class of vulnerabilities.
12
+ * See .cursor/rules/security-global/security-global-pathtraversal-prevention.mdc.
13
+ */
14
+ export declare const validateRelPath: (relPath: string) => void;
@@ -0,0 +1,44 @@
1
+ // © 2026 Adobe. MIT License. See /LICENSE for details.
2
+ /**
3
+ * Validate that a relative path is safe to use within a backend root.
4
+ * Rejects:
5
+ *
6
+ * - empty paths
7
+ * - absolute paths (leading '/' or drive letter)
8
+ * - paths containing '..' segments (path traversal)
9
+ * - null bytes
10
+ * - backslashes (we normalize on '/')
11
+ *
12
+ * Defense-in-depth against the path-traversal class of vulnerabilities.
13
+ * See .cursor/rules/security-global/security-global-pathtraversal-prevention.mdc.
14
+ */
15
+ export const validateRelPath = (relPath) => {
16
+ if (typeof relPath !== "string" || relPath.length === 0) {
17
+ throw new Error("Path must be a non-empty string");
18
+ }
19
+ if (relPath.includes("\0")) {
20
+ throw new Error(`Path contains a null byte: ${JSON.stringify(relPath)}`);
21
+ }
22
+ if (relPath.includes("\\")) {
23
+ throw new Error(`Path contains a backslash; use '/' as the separator: ${JSON.stringify(relPath)}`);
24
+ }
25
+ if (relPath.startsWith("/")) {
26
+ throw new Error(`Path must be relative (no leading '/'): ${JSON.stringify(relPath)}`);
27
+ }
28
+ // Reject Windows drive letters explicitly even though we never use
29
+ // them — consistent rejection regardless of host OS.
30
+ if (/^[A-Za-z]:/.test(relPath)) {
31
+ throw new Error(`Path looks like an absolute Windows path: ${JSON.stringify(relPath)}`);
32
+ }
33
+ const segments = relPath.split("/");
34
+ for (const segment of segments) {
35
+ if (segment === "" || segment === ".") {
36
+ // Tolerate trailing slash and self-references; they reduce to nothing.
37
+ continue;
38
+ }
39
+ if (segment === "..") {
40
+ throw new Error(`Path contains '..' segment (traversal): ${JSON.stringify(relPath)}`);
41
+ }
42
+ }
43
+ };
44
+ //# sourceMappingURL=validate-rel-path.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validate-rel-path.js","sourceRoot":"","sources":["../../src/backend/validate-rel-path.ts"],"names":[],"mappings":"AAAA,uDAAuD;AAEvD;;;;;;;;;;;;GAYG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,OAAe,EAAQ,EAAE;IACrD,IAAI,OAAO,OAAO,KAAK,QAAQ,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACtD,MAAM,IAAI,KAAK,CAAC,iCAAiC,CAAC,CAAC;IACvD,CAAC;IACD,IAAI,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;QACzB,MAAM,IAAI,KAAK,CAAC,8BAA8B,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;IAC7E,CAAC;IACD,IAAI,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;QACzB,MAAM,IAAI,KAAK,CAAC,wDAAwD,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;IACvG,CAAC;IACD,IAAI,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QAC1B,MAAM,IAAI,KAAK,CAAC,2CAA2C,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;IAC1F,CAAC;IACD,mEAAmE;IACnE,qDAAqD;IACrD,IAAI,YAAY,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;QAC7B,MAAM,IAAI,KAAK,CAAC,6CAA6C,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;IAC5F,CAAC;IACD,MAAM,QAAQ,GAAG,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACpC,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;QAC7B,IAAI,OAAO,KAAK,EAAE,IAAI,OAAO,KAAK,GAAG,EAAE,CAAC;YACpC,uEAAuE;YACvE,SAAS;QACb,CAAC;QACD,IAAI,OAAO,KAAK,IAAI,EAAE,CAAC;YACnB,MAAM,IAAI,KAAK,CAAC,2CAA2C,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;QAC1F,CAAC;IACL,CAAC;AACL,CAAC,CAAC"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,37 @@
1
+ // © 2026 Adobe. MIT License. See /LICENSE for details.
2
+ import { describe, expect, it } from "vitest";
3
+ import { validateRelPath } from "./validate-rel-path.js";
4
+ describe("validateRelPath", () => {
5
+ it("accepts simple paths", () => {
6
+ expect(() => validateRelPath("file.bin")).not.toThrow();
7
+ expect(() => validateRelPath("dir/file.bin")).not.toThrow();
8
+ expect(() => validateRelPath("a/b/c/d.bin")).not.toThrow();
9
+ });
10
+ it("accepts trailing slash and self-references", () => {
11
+ expect(() => validateRelPath("dir/")).not.toThrow();
12
+ expect(() => validateRelPath("./file.bin")).not.toThrow();
13
+ expect(() => validateRelPath("dir/./file.bin")).not.toThrow();
14
+ });
15
+ it("rejects empty path", () => {
16
+ expect(() => validateRelPath("")).toThrow(/non-empty/);
17
+ });
18
+ it("rejects absolute paths", () => {
19
+ expect(() => validateRelPath("/etc/passwd")).toThrow(/relative/);
20
+ });
21
+ it("rejects '..' segments", () => {
22
+ expect(() => validateRelPath("..")).toThrow(/traversal/);
23
+ expect(() => validateRelPath("../etc")).toThrow(/traversal/);
24
+ expect(() => validateRelPath("a/../b")).toThrow(/traversal/);
25
+ expect(() => validateRelPath("a/b/..")).toThrow(/traversal/);
26
+ });
27
+ it("rejects null bytes", () => {
28
+ expect(() => validateRelPath("a\0b")).toThrow(/null byte/);
29
+ });
30
+ it("rejects backslashes", () => {
31
+ expect(() => validateRelPath("a\\b")).toThrow(/backslash/);
32
+ });
33
+ it("rejects Windows drive letters", () => {
34
+ expect(() => validateRelPath("C:/foo")).toThrow(/Windows/);
35
+ });
36
+ });
37
+ //# sourceMappingURL=validate-rel-path.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validate-rel-path.test.js","sourceRoot":"","sources":["../../src/backend/validate-rel-path.test.ts"],"names":[],"mappings":"AAAA,uDAAuD;AACvD,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAEzD,QAAQ,CAAC,iBAAiB,EAAE,GAAG,EAAE;IAC7B,EAAE,CAAC,sBAAsB,EAAE,GAAG,EAAE;QAC5B,MAAM,CAAC,GAAG,EAAE,CAAC,eAAe,CAAC,UAAU,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC;QACxD,MAAM,CAAC,GAAG,EAAE,CAAC,eAAe,CAAC,cAAc,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC;QAC5D,MAAM,CAAC,GAAG,EAAE,CAAC,eAAe,CAAC,aAAa,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC;IAC/D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,4CAA4C,EAAE,GAAG,EAAE;QAClD,MAAM,CAAC,GAAG,EAAE,CAAC,eAAe,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC;QACpD,MAAM,CAAC,GAAG,EAAE,CAAC,eAAe,CAAC,YAAY,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC;QAC1D,MAAM,CAAC,GAAG,EAAE,CAAC,eAAe,CAAC,gBAAgB,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC;IAClE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,oBAAoB,EAAE,GAAG,EAAE;QAC1B,MAAM,CAAC,GAAG,EAAE,CAAC,eAAe,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC;IAC3D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,wBAAwB,EAAE,GAAG,EAAE;QAC9B,MAAM,CAAC,GAAG,EAAE,CAAC,eAAe,CAAC,aAAa,CAAC,CAAC,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;IACrE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,uBAAuB,EAAE,GAAG,EAAE;QAC7B,MAAM,CAAC,GAAG,EAAE,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC;QACzD,MAAM,CAAC,GAAG,EAAE,CAAC,eAAe,CAAC,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC;QAC7D,MAAM,CAAC,GAAG,EAAE,CAAC,eAAe,CAAC,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC;QAC7D,MAAM,CAAC,GAAG,EAAE,CAAC,eAAe,CAAC,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC;IACjE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,oBAAoB,EAAE,GAAG,EAAE;QAC1B,MAAM,CAAC,GAAG,EAAE,CAAC,eAAe,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC;IAC/D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,qBAAqB,EAAE,GAAG,EAAE;QAC3B,MAAM,CAAC,GAAG,EAAE,CAAC,eAAe,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC;IAC/D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,+BAA+B,EAAE,GAAG,EAAE;QACrC,MAAM,CAAC,GAAG,EAAE,CAAC,eAAe,CAAC,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAC/D,CAAC,CAAC,CAAC;AACP,CAAC,CAAC,CAAC"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,97 @@
1
+ // © 2026 Adobe. MIT License. See /LICENSE for details.
2
+ //
3
+ // Worker bootstrap for the browser persistence runtime. Spawned by
4
+ // the application via `new Worker(new URL(...), { type: "module" })`
5
+ // and wrapped by {@link createBrowserWorkerTransport}.
6
+ //
7
+ // The script:
8
+ //
9
+ // 1. Acquires the OPFS root via `navigator.storage.getDirectory()`.
10
+ // 2. Constructs an {@link createOpfsBackend} over that root.
11
+ // 3. Constructs a {@link createPersistRouter}.
12
+ // 4. Listens for `PersistOp` messages on `self`, dispatches each
13
+ // through the router, and posts back an `AckMessage`.
14
+ //
15
+ // The bootstrap runs entirely inside the worker and never touches the
16
+ // DOM. Sync access handles (used by OpfsSyncFile) are only available
17
+ // in dedicated workers — this module fails fast if loaded elsewhere.
18
+ /// <reference lib="webworker" />
19
+ import { createOpfsBackend } from "./opfs-backend.js";
20
+ import { createPersistRouter } from "../transport/router.js";
21
+ const getOpfsRoot = async () => {
22
+ // `navigator.storage` is universally available in dedicated
23
+ // workers in modern browsers, but we guard so a clear error
24
+ // surfaces if the bootstrap is loaded in a context without it.
25
+ const storage = navigator.storage;
26
+ if (storage === undefined || typeof storage.getDirectory !== "function") {
27
+ throw new Error("browser-worker-bootstrap: navigator.storage.getDirectory is unavailable in this worker");
28
+ }
29
+ return storage.getDirectory();
30
+ };
31
+ // Lazy-init: defer OPFS acquisition until the first message so the
32
+ // bootstrap can't deadlock at module load (and so the parent gets a
33
+ // crisp error if OPFS is unavailable, rather than a silent hang).
34
+ let routerPromise = null;
35
+ const getRouter = () => {
36
+ if (routerPromise === null) {
37
+ routerPromise = (async () => {
38
+ const root = await getOpfsRoot();
39
+ const backend = await createOpfsBackend(root);
40
+ return createPersistRouter(backend);
41
+ })();
42
+ }
43
+ return routerPromise;
44
+ };
45
+ const sendAck = (id, value, error) => {
46
+ const ack = error === undefined
47
+ ? { kind: "ack", id, value }
48
+ : { kind: "ack", id, error };
49
+ self.postMessage(ack);
50
+ };
51
+ // Serialize op handling so the parent's "send a sentinel and wait for
52
+ // its ack" flush strategy is correct. Without this, async dispatch
53
+ // interleaves router.handle() promises and the sentinel ack can race
54
+ // ahead of an earlier op still doing I/O.
55
+ let opChain = Promise.resolve();
56
+ const handleSerially = (op) => {
57
+ const next = opChain.then(async () => {
58
+ try {
59
+ const router = await getRouter();
60
+ const value = await router.handle(op);
61
+ sendAck(op.id, value);
62
+ }
63
+ catch (err) {
64
+ sendAck(op.id, undefined, err instanceof Error ? err.message : String(err));
65
+ }
66
+ });
67
+ opChain = next;
68
+ return next;
69
+ };
70
+ self.addEventListener("message", (ev) => {
71
+ const msg = ev.data;
72
+ if (msg.kind === "shutdown") {
73
+ // Graceful: drain the in-flight op chain, close the router
74
+ // (releasing all OPFS access handles), ack the shutdown, then
75
+ // close the worker. The main thread waits for the ack before
76
+ // calling worker.terminate() so it can rely on file releases
77
+ // having happened.
78
+ const { id } = msg;
79
+ const finish = async () => {
80
+ try {
81
+ await opChain;
82
+ if (routerPromise !== null) {
83
+ const r = await routerPromise;
84
+ await r.close();
85
+ }
86
+ }
87
+ finally {
88
+ sendAck(id, undefined);
89
+ self.close();
90
+ }
91
+ };
92
+ void finish();
93
+ return;
94
+ }
95
+ void handleSerially(msg);
96
+ });
97
+ //# sourceMappingURL=browser-worker-bootstrap.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"browser-worker-bootstrap.js","sourceRoot":"","sources":["../../src/browser/browser-worker-bootstrap.ts"],"names":[],"mappings":"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"}