@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,48 @@
1
+ // © 2026 Adobe. MIT License. See /LICENSE for details.
2
+ import { promises as fs } from "node:fs";
3
+ import { createInprocessTransport } from "../transport/inprocess-transport.js";
4
+ import { createIncrementalPersistenceService } from "../service/create-incremental-persistence-service.js";
5
+ import { createNodeFsBackend } from "./node-fs-backend.js";
6
+ import { createNodeWorkerTransport } from "./node-worker-transport.js";
7
+ /**
8
+ * Creates a Node.js persistence provider backed by `node:fs`.
9
+ *
10
+ * ```ts
11
+ * import { mount } from "@adobe/data-persistence";
12
+ * import { createNodeFsProvider } from "@adobe/data-persistence/node";
13
+ *
14
+ * const m = await mount(createNodeFsProvider("/tmp/my-db"), database);
15
+ * await m.service.load();
16
+ * // ...run app...
17
+ * await m.dispose();
18
+ * ```
19
+ *
20
+ * Pass `{ worker: true }` to move I/O off the calling thread into a
21
+ * dedicated `node:worker_threads` worker.
22
+ */
23
+ export const createNodeFsProvider = (root, options) => ({
24
+ providerName: "NodeFsProvider",
25
+ async mount(database, mountOptions) {
26
+ await fs.mkdir(root, { recursive: true });
27
+ const backend = await createNodeFsBackend(root);
28
+ const useWorker = options?.worker ?? false;
29
+ const transport = useWorker
30
+ ? createNodeWorkerTransport({ root, workerScript: options?.workerScript })
31
+ : createInprocessTransport(backend);
32
+ const service = await createIncrementalPersistenceService({
33
+ database,
34
+ backend,
35
+ transport,
36
+ autoPersist: mountOptions?.autoPersist,
37
+ checkpoint: mountOptions?.checkpoint,
38
+ });
39
+ return {
40
+ service,
41
+ async dispose() {
42
+ await service.dispose();
43
+ await transport.close();
44
+ },
45
+ };
46
+ },
47
+ });
48
+ //# sourceMappingURL=create-node-fs-provider.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"create-node-fs-provider.js","sourceRoot":"","sources":["../../src/node/create-node-fs-provider.ts"],"names":[],"mappings":"AAAA,uDAAuD;AAEvD,OAAO,EAAE,QAAQ,IAAI,EAAE,EAAE,MAAM,SAAS,CAAC;AAEzC,OAAO,EAAE,wBAAwB,EAAE,MAAM,qCAAqC,CAAC;AAC/E,OAAO,EAAE,mCAAmC,EAAE,MAAM,sDAAsD,CAAC;AAE3G,OAAO,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,EAAE,yBAAyB,EAAE,MAAM,4BAA4B,CAAC;AAmBvE;;;;;;;;;;;;;;;GAeG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAChC,IAAY,EACZ,OAA+B,EACZ,EAAE,CAAC,CAAC;IACvB,YAAY,EAAE,gBAAgB;IAE9B,KAAK,CAAC,KAAK,CACP,QAA0D,EAC1D,YAAmC;QAEnC,MAAM,EAAE,CAAC,KAAK,CAAC,IAAI,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAC1C,MAAM,OAAO,GAAG,MAAM,mBAAmB,CAAC,IAAI,CAAC,CAAC;QAEhD,MAAM,SAAS,GAAG,OAAO,EAAE,MAAM,IAAI,KAAK,CAAC;QAC3C,MAAM,SAAS,GAAG,SAAS;YACvB,CAAC,CAAC,yBAAyB,CAAC,EAAE,IAAI,EAAE,YAAY,EAAE,OAAO,EAAE,YAAY,EAAE,CAAC;YAC1E,CAAC,CAAC,wBAAwB,CAAC,OAAO,CAAC,CAAC;QAExC,MAAM,OAAO,GAAG,MAAM,mCAAmC,CAAC;YACtD,QAAQ;YACR,OAAO;YACP,SAAS;YACT,WAAW,EAAE,YAAY,EAAE,WAAW;YACtC,UAAU,EAAE,YAAY,EAAE,UAAU;SACvC,CAAC,CAAC;QAEH,OAAO;YACH,OAAO;YACP,KAAK,CAAC,OAAO;gBACT,MAAM,OAAO,CAAC,OAAO,EAAE,CAAC;gBACxB,MAAM,SAAS,CAAC,KAAK,EAAE,CAAC;YAC5B,CAAC;SACJ,CAAC;IACN,CAAC;CACJ,CAAC,CAAC"}
@@ -0,0 +1,6 @@
1
+ export { createNodeFsFile } from "./node-fs-file.js";
2
+ export { createNodeFsBackend, type NodeFsBackendOptions } from "./node-fs-backend.js";
3
+ export { createNodeWorkerTransport, type NodeWorkerTransportOptions } from "./node-worker-transport.js";
4
+ export { createNodeFsProvider, type NodeFsProviderOptions } from "./create-node-fs-provider.js";
5
+ /** @deprecated Use `mount(createNodeFsProvider(root), db, opts)` instead. */
6
+ export { mountNodeFs } from "./mount-node-fs.js";
@@ -0,0 +1,10 @@
1
+ // © 2026 Adobe. MIT License. See /LICENSE for details.
2
+ // Node-only entry point. Re-exports node:fs-based factories and the
3
+ // node:worker_threads transport.
4
+ export { createNodeFsFile } from "./node-fs-file.js";
5
+ export { createNodeFsBackend } from "./node-fs-backend.js";
6
+ export { createNodeWorkerTransport } from "./node-worker-transport.js";
7
+ export { createNodeFsProvider } from "./create-node-fs-provider.js";
8
+ /** @deprecated Use `mount(createNodeFsProvider(root), db, opts)` instead. */
9
+ export { mountNodeFs } from "./mount-node-fs.js";
10
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/node/index.ts"],"names":[],"mappings":"AAAA,uDAAuD;AAEvD,oEAAoE;AACpE,iCAAiC;AAEjC,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AACrD,OAAO,EAAE,mBAAmB,EAA6B,MAAM,sBAAsB,CAAC;AACtF,OAAO,EAAE,yBAAyB,EAAmC,MAAM,4BAA4B,CAAC;AACxG,OAAO,EAAE,oBAAoB,EAA8B,MAAM,8BAA8B,CAAC;AAEhG,6EAA6E;AAC7E,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC"}
@@ -0,0 +1,12 @@
1
+ import type { MountOptions, PersistenceMount } from "../service/worker-persistence-service.js";
2
+ /**
3
+ * @deprecated Use `mount(createNodeFsProvider(root), database, opts)` instead.
4
+ *
5
+ * ```ts
6
+ * import { mount } from "@adobe/data-persistence";
7
+ * import { createNodeFsProvider } from "@adobe/data-persistence/node";
8
+ *
9
+ * const m = await mount(createNodeFsProvider("/tmp/my-db"), database);
10
+ * ```
11
+ */
12
+ export declare const mountNodeFs: (options: MountOptions<string>) => Promise<PersistenceMount>;
@@ -0,0 +1,15 @@
1
+ // © 2026 Adobe. MIT License. See /LICENSE for details.
2
+ import { mount } from "../provider/mount.js";
3
+ import { createNodeFsProvider } from "./create-node-fs-provider.js";
4
+ /**
5
+ * @deprecated Use `mount(createNodeFsProvider(root), database, opts)` instead.
6
+ *
7
+ * ```ts
8
+ * import { mount } from "@adobe/data-persistence";
9
+ * import { createNodeFsProvider } from "@adobe/data-persistence/node";
10
+ *
11
+ * const m = await mount(createNodeFsProvider("/tmp/my-db"), database);
12
+ * ```
13
+ */
14
+ export const mountNodeFs = async (options) => mount(createNodeFsProvider(options.root, { worker: true }), options.database, { autoPersist: options.autoPersist, checkpoint: options.checkpoint });
15
+ //# sourceMappingURL=mount-node-fs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mount-node-fs.js","sourceRoot":"","sources":["../../src/node/mount-node-fs.ts"],"names":[],"mappings":"AAAA,uDAAuD;AAGvD,OAAO,EAAE,KAAK,EAAE,MAAM,sBAAsB,CAAC;AAC7C,OAAO,EAAE,oBAAoB,EAAE,MAAM,8BAA8B,CAAC;AAEpE;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,KAAK,EAC5B,OAA6B,EACJ,EAAE,CAC3B,KAAK,CACD,oBAAoB,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,EACpD,OAAO,CAAC,QAAQ,EAChB,EAAE,WAAW,EAAE,OAAO,CAAC,WAAW,EAAE,UAAU,EAAE,OAAO,CAAC,UAAU,EAAE,CACvE,CAAC"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,160 @@
1
+ // © 2026 Adobe. MIT License. See /LICENSE for details.
2
+ //
3
+ // Single-writer lock behavior for the Node fs backend. The lock file
4
+ // (`lock.json`) is written exclusively at backend creation, released
5
+ // on `dispose`, and tolerated when stale (owner pid is gone).
6
+ import { promises as fs } from "node:fs";
7
+ import { tmpdir } from "node:os";
8
+ import { join } from "node:path";
9
+ import { afterEach, beforeEach, describe, expect, it } from "vitest";
10
+ import { createNodeFsBackend } from "./node-fs-backend.js";
11
+ const LOCK_FILENAME = "lock.json";
12
+ const makeTmpDir = async () => fs.mkdtemp(join(tmpdir(), "data-persistence-lock-"));
13
+ describe("node-fs-backend single-writer lock", () => {
14
+ let dir;
15
+ beforeEach(async () => {
16
+ dir = await makeTmpDir();
17
+ });
18
+ afterEach(async () => {
19
+ await fs.rm(dir, { recursive: true, force: true });
20
+ });
21
+ it("creates the lock file on construction and removes it on dispose", async () => {
22
+ const backend = await createNodeFsBackend(dir);
23
+ const lockPath = join(dir, LOCK_FILENAME);
24
+ const exists = await fs.stat(lockPath).then(() => true).catch(() => false);
25
+ expect(exists).toBe(true);
26
+ const text = await fs.readFile(lockPath, "utf8");
27
+ const record = JSON.parse(text);
28
+ expect(record.pid).toBe(process.pid);
29
+ expect(typeof record.hostname).toBe("string");
30
+ expect(typeof record.startedAtMs).toBe("number");
31
+ await backend.dispose();
32
+ const stillExists = await fs.stat(lockPath).then(() => true).catch(() => false);
33
+ expect(stillExists).toBe(false);
34
+ });
35
+ it("hides the lock file from list('.')", async () => {
36
+ const backend = await createNodeFsBackend(dir);
37
+ try {
38
+ const f = await backend.open("data.bin");
39
+ await f.writeAt(0, new Uint8Array([1]));
40
+ await f.close();
41
+ const entries = [...(await backend.list("."))].sort();
42
+ expect(entries).toEqual(["data.bin"]);
43
+ }
44
+ finally {
45
+ await backend.dispose();
46
+ }
47
+ });
48
+ it("rejects a second backend on the same root while the first is alive", async () => {
49
+ const first = await createNodeFsBackend(dir);
50
+ try {
51
+ // The second acquire should see a fresh, live owner: us.
52
+ // We simulate a different pid by rewriting the lock file
53
+ // in-place — same hostname, different alive pid.
54
+ const lockPath = join(dir, LOCK_FILENAME);
55
+ const live = {
56
+ pid: process.pid, // alive: kill(0) succeeds
57
+ hostname: (await import("node:os")).hostname(),
58
+ startedAtMs: Date.now(),
59
+ };
60
+ await fs.writeFile(lockPath, JSON.stringify(live));
61
+ // ...but trick the lock check by changing the pid to a known-
62
+ // alive pid of ANOTHER process: 1 (init / launchd). Signal 0
63
+ // to pid 1 from a non-root process raises EPERM, which our
64
+ // implementation correctly classifies as "alive".
65
+ const live2 = { ...live, pid: 1 };
66
+ await fs.writeFile(lockPath, JSON.stringify(live2));
67
+ await expect(createNodeFsBackend(dir)).rejects.toThrow(/another process \(pid 1\) is currently using/);
68
+ }
69
+ finally {
70
+ await first.dispose();
71
+ }
72
+ });
73
+ it("steals a stale lock left by a dead pid", async () => {
74
+ // Pre-seed a stale lock referencing a pid that almost certainly
75
+ // does not exist. 2**31 - 1 is well above the OS pid_max on all
76
+ // major platforms (Linux default 32768; macOS/BSD 99999) and
77
+ // fits in Node's ints. kill(0, pid) → ESRCH → "stale".
78
+ const lockPath = join(dir, LOCK_FILENAME);
79
+ const stale = {
80
+ pid: 2 ** 31 - 1,
81
+ hostname: (await import("node:os")).hostname(),
82
+ startedAtMs: Date.now() - 86_400_000,
83
+ };
84
+ await fs.writeFile(lockPath, JSON.stringify(stale));
85
+ const backend = await createNodeFsBackend(dir);
86
+ try {
87
+ // Lock was stolen and rewritten to our pid.
88
+ const text = await fs.readFile(lockPath, "utf8");
89
+ const record = JSON.parse(text);
90
+ expect(record.pid).toBe(process.pid);
91
+ }
92
+ finally {
93
+ await backend.dispose();
94
+ }
95
+ });
96
+ it("treats an unparseable lock file as stale", async () => {
97
+ const lockPath = join(dir, LOCK_FILENAME);
98
+ await fs.writeFile(lockPath, "not json {");
99
+ const backend = await createNodeFsBackend(dir);
100
+ try {
101
+ const text = await fs.readFile(lockPath, "utf8");
102
+ // Should now be valid JSON identifying us.
103
+ const record = JSON.parse(text);
104
+ expect(record.pid).toBe(process.pid);
105
+ }
106
+ finally {
107
+ await backend.dispose();
108
+ }
109
+ });
110
+ it("refuses a lock owned by a different host (NFS safety)", async () => {
111
+ const lockPath = join(dir, LOCK_FILENAME);
112
+ const foreign = {
113
+ pid: 1,
114
+ hostname: "some-other-host-not-this-one",
115
+ startedAtMs: Date.now(),
116
+ };
117
+ await fs.writeFile(lockPath, JSON.stringify(foreign));
118
+ await expect(createNodeFsBackend(dir)).rejects.toThrow(/owned by pid 1 on host "some-other-host-not-this-one"/);
119
+ });
120
+ it("re-entry from the same pid succeeds and refreshes the timestamp", async () => {
121
+ // First acquire writes the lock.
122
+ const first = await createNodeFsBackend(dir);
123
+ try {
124
+ const lockPath = join(dir, LOCK_FILENAME);
125
+ const before = JSON.parse(await fs.readFile(lockPath, "utf8"));
126
+ // Force a measurable time gap.
127
+ await new Promise(r => setTimeout(r, 5));
128
+ // Second acquire from the same pid should succeed
129
+ // (re-entry path; common during dev-server reloads).
130
+ const second = await createNodeFsBackend(dir);
131
+ try {
132
+ const after = JSON.parse(await fs.readFile(lockPath, "utf8"));
133
+ expect(after.pid).toBe(process.pid);
134
+ expect(after.startedAtMs).toBeGreaterThanOrEqual(before.startedAtMs);
135
+ }
136
+ finally {
137
+ await second.dispose();
138
+ }
139
+ }
140
+ finally {
141
+ await first.dispose();
142
+ }
143
+ });
144
+ it("disableLock skips lock acquisition entirely", async () => {
145
+ const backend = await createNodeFsBackend(dir, { disableLock: true });
146
+ try {
147
+ const lockPath = join(dir, LOCK_FILENAME);
148
+ const exists = await fs.stat(lockPath).then(() => true).catch(() => false);
149
+ expect(exists).toBe(false);
150
+ // And nothing throws on dispose either.
151
+ await backend.dispose();
152
+ }
153
+ catch (err) {
154
+ // Make sure dispose got called once if the test body bailed.
155
+ await backend.dispose?.().catch(() => { });
156
+ throw err;
157
+ }
158
+ });
159
+ });
160
+ //# sourceMappingURL=node-fs-backend-lock.node.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"node-fs-backend-lock.node.test.js","sourceRoot":"","sources":["../../src/node/node-fs-backend-lock.node.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ import type { PersistenceBackend } from "../backend/persistence-backend.js";
2
+ /**
3
+ * Node `fs` backend rooted at an absolute directory. The root must
4
+ * exist and be a directory; create it ahead of time with `fs.mkdir`.
5
+ *
6
+ * All input paths are validated and resolved against the root; any
7
+ * attempt to escape the root is rejected (defense in depth — the
8
+ * `validateRelPath` rejection is the primary line of defense).
9
+ */
10
+ /**
11
+ * Options for {@link createNodeFsBackend}.
12
+ */
13
+ export interface NodeFsBackendOptions {
14
+ /**
15
+ * Disable the single-writer lock file. Use only when an external
16
+ * mechanism guarantees exclusive access (containerized single-
17
+ * process services, ephemeral test directories, etc.).
18
+ */
19
+ readonly disableLock?: boolean;
20
+ }
21
+ export declare const createNodeFsBackend: (rootPath: string, options?: NodeFsBackendOptions) => Promise<PersistenceBackend>;
@@ -0,0 +1,186 @@
1
+ // © 2026 Adobe. MIT License. See /LICENSE for details.
2
+ import { promises as fs } from "node:fs";
3
+ import { hostname } from "node:os";
4
+ import { dirname, join, resolve, sep } from "node:path";
5
+ import { validateRelPath } from "../backend/validate-rel-path.js";
6
+ import { createNodeFsFile } from "./node-fs-file.js";
7
+ /**
8
+ * `NodeJS.ErrnoException` is a structural type with optional `code` —
9
+ * Node throws plain `Error` instances at runtime, so `instanceof` is
10
+ * unhelpful here. Duck-type the `code` field instead, then narrow.
11
+ */
12
+ const isErrnoCode = (err, code) => typeof err === "object" &&
13
+ err !== null &&
14
+ err.code === code;
15
+ const LOCK_FILENAME = "lock.json";
16
+ /**
17
+ * Acquire a single-writer lock on `root`. Writes a JSON record
18
+ * containing the current process's pid and start time to `lock.json`.
19
+ *
20
+ * Behavior:
21
+ * - File does not exist → create it (exclusive `wx` flag).
22
+ * - File exists, owner pid is alive on this host → throw.
23
+ * - File exists, owner pid is gone (or different host) → treat as
24
+ * stale and overwrite. We DO NOT steal locks owned by a different
25
+ * host because that almost certainly means the data lives on a
26
+ * shared filesystem (NFS) and the other host is still writing.
27
+ *
28
+ * Returns the absolute lock path so the caller can release it.
29
+ *
30
+ * Race-condition tolerance: two processes may both observe a stale
31
+ * lock and both try to overwrite. We use `flag: 'wx'` for the initial
32
+ * acquire; if it fails with EEXIST after we've already concluded the
33
+ * lock is stale, we use `flag: 'w'` to overwrite. The window where
34
+ * both racers can write is small (microseconds) and the loser will
35
+ * be detected at the NEXT call (writes happen sequentially through
36
+ * the worker so cross-process interleaving is the only concern). For
37
+ * stronger guarantees a flock(2)-based lock would be needed, but
38
+ * Node's `fs` module does not surface flock without a native addon.
39
+ */
40
+ const acquireNodeLock = async (root) => {
41
+ const lockPath = join(root, LOCK_FILENAME);
42
+ const myRecord = {
43
+ pid: process.pid,
44
+ hostname: hostname(),
45
+ startedAtMs: Date.now(),
46
+ };
47
+ const recordBytes = Buffer.from(JSON.stringify(myRecord), "utf8");
48
+ // Fast path: exclusive create. Wins the race in the common case
49
+ // where no prior session left a lock behind.
50
+ try {
51
+ await fs.writeFile(lockPath, recordBytes, { flag: "wx" });
52
+ return lockPath;
53
+ }
54
+ catch (err) {
55
+ if (!isErrnoCode(err, "EEXIST"))
56
+ throw err;
57
+ }
58
+ // A lock file already exists. Read it and decide whether to steal.
59
+ let existing;
60
+ try {
61
+ const text = await fs.readFile(lockPath, "utf8");
62
+ existing = JSON.parse(text);
63
+ }
64
+ catch {
65
+ // Unparseable lock file from an older / corrupt run; treat as stale.
66
+ await fs.writeFile(lockPath, recordBytes, { flag: "w" });
67
+ return lockPath;
68
+ }
69
+ if (existing.hostname !== myRecord.hostname) {
70
+ throw new Error(`NodeFsBackend: refusing to acquire lock at ${lockPath}: ` +
71
+ `owned by pid ${existing.pid} on host ${JSON.stringify(existing.hostname)} ` +
72
+ `(this host is ${JSON.stringify(myRecord.hostname)}). ` +
73
+ `If the data is on a shared filesystem and the other host crashed, ` +
74
+ `delete ${LOCK_FILENAME} manually.`);
75
+ }
76
+ if (existing.pid === process.pid) {
77
+ // Re-entry from the same process. Refresh the timestamp and
78
+ // continue. This handles dev-server reloads that re-import
79
+ // without forking, and tests that recreate the backend.
80
+ await fs.writeFile(lockPath, recordBytes, { flag: "w" });
81
+ return lockPath;
82
+ }
83
+ // Different pid, same host. Probe with signal 0 (no-op signal that
84
+ // verifies the process exists and we have permission to signal
85
+ // it). If the process is gone, the lock is stale.
86
+ let alive = false;
87
+ try {
88
+ process.kill(existing.pid, 0);
89
+ alive = true;
90
+ }
91
+ catch (err) {
92
+ // ESRCH = process does not exist. EPERM = exists but not ours,
93
+ // which on most systems still means the process is alive.
94
+ if (isErrnoCode(err, "EPERM"))
95
+ alive = true;
96
+ }
97
+ if (alive) {
98
+ throw new Error(`NodeFsBackend: another process (pid ${existing.pid}) is currently ` +
99
+ `using ${root}. If you are sure that process has exited, delete ${LOCK_FILENAME}.`);
100
+ }
101
+ // Stale lock; steal it.
102
+ await fs.writeFile(lockPath, recordBytes, { flag: "w" });
103
+ return lockPath;
104
+ };
105
+ export const createNodeFsBackend = async (rootPath, options = {}) => {
106
+ const root = resolve(rootPath);
107
+ // Ensure the root exists and is a directory. Failing fast here
108
+ // surfaces configuration errors at startup rather than mid-write.
109
+ const stat = await fs.stat(root);
110
+ if (!stat.isDirectory()) {
111
+ throw new Error(`NodeFsBackend root is not a directory: ${root}`);
112
+ }
113
+ // Acquire the single-writer lock. If two processes try to mount
114
+ // the same root, one of them must lose here — otherwise their
115
+ // appendJournal() calls race on the file size and corrupt the WAL.
116
+ const lockPath = options.disableLock
117
+ ? null
118
+ : await acquireNodeLock(root);
119
+ const safeJoin = (relPath) => {
120
+ validateRelPath(relPath);
121
+ const joined = resolve(join(root, relPath));
122
+ // Final check: the resolved path must be inside the root. This is
123
+ // belt-and-suspenders since validateRelPath already rejects '..'
124
+ // segments and absolute inputs.
125
+ if (joined !== root && !joined.startsWith(root + sep)) {
126
+ throw new Error(`Resolved path escapes root: ${JSON.stringify(relPath)}`);
127
+ }
128
+ return joined;
129
+ };
130
+ return {
131
+ async open(relPath) {
132
+ const abs = safeJoin(relPath);
133
+ await fs.mkdir(dirname(abs), { recursive: true });
134
+ return createNodeFsFile(abs);
135
+ },
136
+ async list(relPath) {
137
+ const target = relPath === "." ? root : safeJoin(relPath);
138
+ try {
139
+ const all = await fs.readdir(target);
140
+ // Hide the lock file from listings so user code sees a
141
+ // consistent view independent of whether locking is on.
142
+ if (relPath === ".")
143
+ return all.filter(name => name !== LOCK_FILENAME);
144
+ return all;
145
+ }
146
+ catch (err) {
147
+ if (isErrnoCode(err, "ENOENT"))
148
+ return [];
149
+ throw err;
150
+ }
151
+ },
152
+ async remove(relPath) {
153
+ const abs = safeJoin(relPath);
154
+ try {
155
+ await fs.unlink(abs);
156
+ }
157
+ catch (err) {
158
+ if (isErrnoCode(err, "ENOENT"))
159
+ return;
160
+ throw err;
161
+ }
162
+ },
163
+ async rename(from, to) {
164
+ const fromAbs = safeJoin(from);
165
+ const toAbs = safeJoin(to);
166
+ await fs.mkdir(dirname(toAbs), { recursive: true });
167
+ await fs.rename(fromAbs, toAbs);
168
+ },
169
+ async dispose() {
170
+ if (lockPath === null)
171
+ return;
172
+ try {
173
+ await fs.unlink(lockPath);
174
+ }
175
+ catch (err) {
176
+ // Already gone — fine. Anything else, log and move on:
177
+ // teardown should never throw, the OS will reap us.
178
+ if (!isErrnoCode(err, "ENOENT")) {
179
+ // eslint-disable-next-line no-console
180
+ console.warn(`NodeFsBackend: failed to release lock at ${lockPath}:`, err);
181
+ }
182
+ }
183
+ },
184
+ };
185
+ };
186
+ //# sourceMappingURL=node-fs-backend.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"node-fs-backend.js","sourceRoot":"","sources":["../../src/node/node-fs-backend.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,17 @@
1
+ // © 2026 Adobe. MIT License. See /LICENSE for details.
2
+ import { promises as fs } from "node:fs";
3
+ import { tmpdir } from "node:os";
4
+ import { join } from "node:path";
5
+ import { runPersistenceBackendConformance } from "../backend/persistence-backend.conformance.js";
6
+ import { createNodeFsBackend } from "./node-fs-backend.js";
7
+ runPersistenceBackendConformance("node-fs", async () => {
8
+ const dir = await fs.mkdtemp(join(tmpdir(), "data-persistence-be-"));
9
+ const backend = await createNodeFsBackend(dir);
10
+ return {
11
+ backend,
12
+ cleanup: async () => {
13
+ await fs.rm(dir, { recursive: true, force: true });
14
+ },
15
+ };
16
+ });
17
+ //# sourceMappingURL=node-fs-backend.node.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"node-fs-backend.node.test.js","sourceRoot":"","sources":["../../src/node/node-fs-backend.node.test.ts"],"names":[],"mappings":"AAAA,uDAAuD;AACvD,OAAO,EAAE,QAAQ,IAAI,EAAE,EAAE,MAAM,SAAS,CAAC;AACzC,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACjC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,gCAAgC,EAAE,MAAM,+CAA+C,CAAC;AACjG,OAAO,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAE3D,gCAAgC,CAAC,SAAS,EAAE,KAAK,IAAI,EAAE;IACnD,MAAM,GAAG,GAAG,MAAM,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,sBAAsB,CAAC,CAAC,CAAC;IACrE,MAAM,OAAO,GAAG,MAAM,mBAAmB,CAAC,GAAG,CAAC,CAAC;IAC/C,OAAO;QACH,OAAO;QACP,OAAO,EAAE,KAAK,IAAI,EAAE;YAChB,MAAM,EAAE,CAAC,EAAE,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;QACvD,CAAC;KACJ,CAAC;AACN,CAAC,CAAC,CAAC"}
@@ -0,0 +1,6 @@
1
+ import { RandomAccessFile } from "../backend/random-access-file.js";
2
+ /**
3
+ * Node-fs implementation of RandomAccessFile. The given path must
4
+ * already be validated by the surrounding {@link NodeFsBackend}.
5
+ */
6
+ export declare const createNodeFsFile: (absPath: string) => Promise<RandomAccessFile>;
@@ -0,0 +1,57 @@
1
+ // © 2026 Adobe. MIT License. See /LICENSE for details.
2
+ import { promises as fs, constants as fsConstants } from "node:fs";
3
+ /**
4
+ * Node-fs implementation of RandomAccessFile. The given path must
5
+ * already be validated by the surrounding {@link NodeFsBackend}.
6
+ */
7
+ export const createNodeFsFile = async (absPath) => {
8
+ // O_RDWR | O_CREAT: read+write, create-if-missing, never truncate.
9
+ // "a+" is wrong here — append mode ignores the position argument on writes,
10
+ // which breaks writeAt semantics (every write would land at EOF instead).
11
+ const handle = await fs.open(absPath, fsConstants.O_RDWR | fsConstants.O_CREAT, 0o666);
12
+ let closed = false;
13
+ const ensureOpen = () => {
14
+ if (closed)
15
+ throw new Error(`NodeFsFile is closed: ${absPath}`);
16
+ };
17
+ return {
18
+ async readAt(offset, length) {
19
+ ensureOpen();
20
+ const buffer = Buffer.allocUnsafe(length);
21
+ const { bytesRead } = await handle.read(buffer, 0, length, offset);
22
+ // Slice to actual bytes read; copy detached from internal buffer.
23
+ return new Uint8Array(buffer.subarray(0, bytesRead));
24
+ },
25
+ async writeAt(offset, bytes) {
26
+ ensureOpen();
27
+ await handle.write(bytes, 0, bytes.byteLength, offset);
28
+ },
29
+ async appendAt(bytes) {
30
+ ensureOpen();
31
+ const stat = await handle.stat();
32
+ const start = stat.size;
33
+ await handle.write(bytes, 0, bytes.byteLength, start);
34
+ return start + bytes.byteLength;
35
+ },
36
+ async size() {
37
+ ensureOpen();
38
+ const stat = await handle.stat();
39
+ return stat.size;
40
+ },
41
+ async truncate(length) {
42
+ ensureOpen();
43
+ await handle.truncate(length);
44
+ },
45
+ async sync() {
46
+ ensureOpen();
47
+ await handle.sync();
48
+ },
49
+ async close() {
50
+ if (closed)
51
+ return;
52
+ closed = true;
53
+ await handle.close();
54
+ },
55
+ };
56
+ };
57
+ //# sourceMappingURL=node-fs-file.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"node-fs-file.js","sourceRoot":"","sources":["../../src/node/node-fs-file.ts"],"names":[],"mappings":"AAAA,uDAAuD;AAEvD,OAAO,EAAE,QAAQ,IAAI,EAAE,EAAE,SAAS,IAAI,WAAW,EAAE,MAAM,SAAS,CAAC;AAInE;;;GAGG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,KAAK,EAAE,OAAe,EAA6B,EAAE;IACjF,mEAAmE;IACnE,4EAA4E;IAC5E,0EAA0E;IAC1E,MAAM,MAAM,GAAe,MAAM,EAAE,CAAC,IAAI,CAAC,OAAO,EAAE,WAAW,CAAC,MAAM,GAAG,WAAW,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;IACnG,IAAI,MAAM,GAAG,KAAK,CAAC;IAEnB,MAAM,UAAU,GAAG,GAAS,EAAE;QAC1B,IAAI,MAAM;YAAE,MAAM,IAAI,KAAK,CAAC,yBAAyB,OAAO,EAAE,CAAC,CAAC;IACpE,CAAC,CAAC;IAEF,OAAO;QACH,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM;YACvB,UAAU,EAAE,CAAC;YACb,MAAM,MAAM,GAAG,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC;YAC1C,MAAM,EAAE,SAAS,EAAE,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;YACnE,kEAAkE;YAClE,OAAO,IAAI,UAAU,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC,CAAC;QACzD,CAAC;QACD,KAAK,CAAC,OAAO,CAAC,MAAM,EAAE,KAAK;YACvB,UAAU,EAAE,CAAC;YACb,MAAM,MAAM,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC,EAAE,KAAK,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;QAC3D,CAAC;QACD,KAAK,CAAC,QAAQ,CAAC,KAAK;YAChB,UAAU,EAAE,CAAC;YACb,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,IAAI,EAAE,CAAC;YACjC,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,CAAC;YACxB,MAAM,MAAM,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC,EAAE,KAAK,CAAC,UAAU,EAAE,KAAK,CAAC,CAAC;YACtD,OAAO,KAAK,GAAG,KAAK,CAAC,UAAU,CAAC;QACpC,CAAC;QACD,KAAK,CAAC,IAAI;YACN,UAAU,EAAE,CAAC;YACb,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,IAAI,EAAE,CAAC;YACjC,OAAO,IAAI,CAAC,IAAI,CAAC;QACrB,CAAC;QACD,KAAK,CAAC,QAAQ,CAAC,MAAM;YACjB,UAAU,EAAE,CAAC;YACb,MAAM,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;QAClC,CAAC;QACD,KAAK,CAAC,IAAI;YACN,UAAU,EAAE,CAAC;YACb,MAAM,MAAM,CAAC,IAAI,EAAE,CAAC;QACxB,CAAC;QACD,KAAK,CAAC,KAAK;YACP,IAAI,MAAM;gBAAE,OAAO;YACnB,MAAM,GAAG,IAAI,CAAC;YACd,MAAM,MAAM,CAAC,KAAK,EAAE,CAAC;QACzB,CAAC;KACJ,CAAC;AACN,CAAC,CAAC"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,13 @@
1
+ // © 2026 Adobe. MIT License. See /LICENSE for details.
2
+ import { promises as fs } from "node:fs";
3
+ import { tmpdir } from "node:os";
4
+ import { join } from "node:path";
5
+ import { runRandomAccessFileConformance } from "../backend/random-access-file.conformance.js";
6
+ import { createNodeFsFile } from "./node-fs-file.js";
7
+ let counter = 0;
8
+ runRandomAccessFileConformance("node-fs", async () => {
9
+ const dir = await fs.mkdtemp(join(tmpdir(), "data-persistence-raf-"));
10
+ const path = join(dir, `file-${counter++}.bin`);
11
+ return createNodeFsFile(path);
12
+ });
13
+ //# sourceMappingURL=node-fs-file.node.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"node-fs-file.node.test.js","sourceRoot":"","sources":["../../src/node/node-fs-file.node.test.ts"],"names":[],"mappings":"AAAA,uDAAuD;AACvD,OAAO,EAAE,QAAQ,IAAI,EAAE,EAAE,MAAM,SAAS,CAAC;AACzC,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACjC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,8BAA8B,EAAE,MAAM,8CAA8C,CAAC;AAC9F,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAErD,IAAI,OAAO,GAAG,CAAC,CAAC;AAChB,8BAA8B,CAAC,SAAS,EAAE,KAAK,IAAI,EAAE;IACjD,MAAM,GAAG,GAAG,MAAM,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,uBAAuB,CAAC,CAAC,CAAC;IACtE,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,EAAE,QAAQ,OAAO,EAAE,MAAM,CAAC,CAAC;IAChD,OAAO,gBAAgB,CAAC,IAAI,CAAC,CAAC;AAClC,CAAC,CAAC,CAAC"}
@@ -0,0 +1 @@
1
+ export {};