@openclaw/fs-safe 0.6.0 → 0.7.0

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 (183) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/README.md +7 -0
  3. package/dist/archive-deadline.d.ts +3 -0
  4. package/dist/archive-deadline.d.ts.map +1 -1
  5. package/dist/archive-deadline.js +44 -8
  6. package/dist/archive-entry.d.ts.map +1 -1
  7. package/dist/archive-entry.js +1 -0
  8. package/dist/archive-errors.d.ts +1 -0
  9. package/dist/archive-errors.d.ts.map +1 -1
  10. package/dist/archive-errors.js +3 -0
  11. package/dist/archive-input.d.ts.map +1 -1
  12. package/dist/archive-input.js +26 -16
  13. package/dist/archive-limits.d.ts +11 -3
  14. package/dist/archive-limits.d.ts.map +1 -1
  15. package/dist/archive-limits.js +24 -0
  16. package/dist/archive-native.d.ts +3 -2
  17. package/dist/archive-native.d.ts.map +1 -1
  18. package/dist/archive-native.js +24 -9
  19. package/dist/archive-policy.d.ts +2 -0
  20. package/dist/archive-policy.d.ts.map +1 -1
  21. package/dist/archive-policy.js +9 -1
  22. package/dist/archive-read.d.ts.map +1 -1
  23. package/dist/archive-read.js +69 -41
  24. package/dist/archive-staging.d.ts +3 -0
  25. package/dist/archive-staging.d.ts.map +1 -1
  26. package/dist/archive-staging.js +91 -43
  27. package/dist/archive-tar-admission.d.ts +7 -0
  28. package/dist/archive-tar-admission.d.ts.map +1 -0
  29. package/dist/archive-tar-admission.js +43 -0
  30. package/dist/archive-tar-gnu.d.ts +2 -0
  31. package/dist/archive-tar-gnu.d.ts.map +1 -0
  32. package/dist/archive-tar-gnu.js +20 -0
  33. package/dist/archive-tar-header.d.ts +8 -0
  34. package/dist/archive-tar-header.d.ts.map +1 -0
  35. package/dist/archive-tar-header.js +47 -0
  36. package/dist/archive-tar-meta.d.ts +29 -1
  37. package/dist/archive-tar-meta.d.ts.map +1 -1
  38. package/dist/archive-tar-meta.js +104 -35
  39. package/dist/archive-tar-runtime.d.ts +3 -0
  40. package/dist/archive-tar-runtime.d.ts.map +1 -1
  41. package/dist/archive-tar-runtime.js +3 -0
  42. package/dist/archive-tar.d.ts.map +1 -1
  43. package/dist/archive-tar.js +8 -2
  44. package/dist/archive-zip-names.js +2 -2
  45. package/dist/archive.d.ts.map +1 -1
  46. package/dist/archive.js +38 -26
  47. package/dist/atomic.d.ts +1 -1
  48. package/dist/atomic.d.ts.map +1 -1
  49. package/dist/bounded-read-stream.d.ts.map +1 -1
  50. package/dist/bounded-read-stream.js +2 -4
  51. package/dist/bounded-read.d.ts.map +1 -1
  52. package/dist/bounded-read.js +3 -10
  53. package/dist/byte-budget.d.ts +5 -0
  54. package/dist/byte-budget.d.ts.map +1 -0
  55. package/dist/byte-budget.js +9 -0
  56. package/dist/file-lock-sync.d.ts.map +1 -1
  57. package/dist/file-lock-sync.js +10 -5
  58. package/dist/file-store-boundary.d.ts.map +1 -1
  59. package/dist/file-store-boundary.js +9 -5
  60. package/dist/file-store-limit.d.ts +2 -0
  61. package/dist/file-store-limit.d.ts.map +1 -0
  62. package/dist/file-store-limit.js +8 -0
  63. package/dist/file-store-sync-write.d.ts.map +1 -1
  64. package/dist/file-store-sync-write.js +38 -8
  65. package/dist/file-store.d.ts.map +1 -1
  66. package/dist/file-store.js +26 -26
  67. package/dist/json-durable-queue-directory.d.ts +2 -0
  68. package/dist/json-durable-queue-directory.d.ts.map +1 -0
  69. package/dist/json-durable-queue-directory.js +20 -0
  70. package/dist/json-durable-queue-ownership.d.ts +14 -0
  71. package/dist/json-durable-queue-ownership.d.ts.map +1 -0
  72. package/dist/json-durable-queue-ownership.js +168 -0
  73. package/dist/json-durable-queue-retirement.d.ts +9 -0
  74. package/dist/json-durable-queue-retirement.d.ts.map +1 -0
  75. package/dist/json-durable-queue-retirement.js +126 -0
  76. package/dist/json-durable-queue-transfer-lock.d.ts +2 -0
  77. package/dist/json-durable-queue-transfer-lock.d.ts.map +1 -0
  78. package/dist/json-durable-queue-transfer-lock.js +19 -0
  79. package/dist/json-durable-queue.d.ts +1 -0
  80. package/dist/json-durable-queue.d.ts.map +1 -1
  81. package/dist/json-durable-queue.js +90 -57
  82. package/dist/json.d.ts.map +1 -1
  83. package/dist/json.js +27 -8
  84. package/dist/local-roots.d.ts.map +1 -1
  85. package/dist/local-roots.js +4 -2
  86. package/dist/native-binding.d.ts +12 -3
  87. package/dist/native-binding.d.ts.map +1 -1
  88. package/dist/output-sibling.d.ts.map +1 -1
  89. package/dist/output-sibling.js +11 -110
  90. package/dist/output.d.ts.map +1 -1
  91. package/dist/output.js +4 -2
  92. package/dist/pinned-open.d.ts.map +1 -1
  93. package/dist/pinned-open.js +27 -48
  94. package/dist/pinned-write.d.ts.map +1 -1
  95. package/dist/pinned-write.js +8 -9
  96. package/dist/private-temp-workspace.d.ts +3 -1
  97. package/dist/private-temp-workspace.d.ts.map +1 -1
  98. package/dist/private-temp-workspace.js +81 -56
  99. package/dist/publish-file.d.ts.map +1 -1
  100. package/dist/publish-file.js +2 -4
  101. package/dist/read-opened-file.d.ts.map +1 -1
  102. package/dist/read-opened-file.js +6 -4
  103. package/dist/regular-file.d.ts.map +1 -1
  104. package/dist/regular-file.js +136 -82
  105. package/dist/replace-file-copy-fallback.d.ts +3 -1
  106. package/dist/replace-file-copy-fallback.d.ts.map +1 -1
  107. package/dist/replace-file-copy-fallback.js +21 -36
  108. package/dist/replace-file-copy-source.d.ts +21 -0
  109. package/dist/replace-file-copy-source.d.ts.map +1 -0
  110. package/dist/replace-file-copy-source.js +112 -0
  111. package/dist/replace-file-descriptor.d.ts +13 -3
  112. package/dist/replace-file-descriptor.d.ts.map +1 -1
  113. package/dist/replace-file-descriptor.js +32 -7
  114. package/dist/replace-file-rename-policy.d.ts +7 -0
  115. package/dist/replace-file-rename-policy.d.ts.map +1 -0
  116. package/dist/replace-file-rename-policy.js +30 -0
  117. package/dist/replace-file-temp-owner.d.ts +46 -0
  118. package/dist/replace-file-temp-owner.d.ts.map +1 -0
  119. package/dist/replace-file-temp-owner.js +346 -0
  120. package/dist/replace-file.d.ts +6 -1
  121. package/dist/replace-file.d.ts.map +1 -1
  122. package/dist/replace-file.js +72 -58
  123. package/dist/root-impl.d.ts.map +1 -1
  124. package/dist/root-impl.js +40 -26
  125. package/dist/secret-file.d.ts.map +1 -1
  126. package/dist/secret-file.js +6 -3
  127. package/dist/secret-read-async.d.ts.map +1 -1
  128. package/dist/secret-read-async.js +6 -5
  129. package/dist/secure-file.d.ts.map +1 -1
  130. package/dist/secure-file.js +17 -13
  131. package/dist/sibling-staged-file.d.ts +15 -0
  132. package/dist/sibling-staged-file.d.ts.map +1 -0
  133. package/dist/sibling-staged-file.js +148 -0
  134. package/dist/sibling-temp.d.ts +3 -0
  135. package/dist/sibling-temp.d.ts.map +1 -1
  136. package/dist/sibling-temp.js +30 -74
  137. package/dist/sidecar-lock-acquire.d.ts.map +1 -1
  138. package/dist/sidecar-lock-acquire.js +48 -27
  139. package/dist/sidecar-lock-handle.d.ts +6 -2
  140. package/dist/sidecar-lock-handle.d.ts.map +1 -1
  141. package/dist/sidecar-lock-handle.js +17 -3
  142. package/dist/sidecar-lock-policy.d.ts +2 -0
  143. package/dist/sidecar-lock-policy.d.ts.map +1 -1
  144. package/dist/sidecar-lock-policy.js +29 -0
  145. package/dist/sidecar-lock-reclaim.d.ts.map +1 -1
  146. package/dist/sidecar-lock-reclaim.js +29 -7
  147. package/dist/sidecar-lock.d.ts.map +1 -1
  148. package/dist/sidecar-lock.js +28 -16
  149. package/dist/suppressed-error.d.ts +6 -0
  150. package/dist/suppressed-error.d.ts.map +1 -0
  151. package/dist/suppressed-error.js +15 -0
  152. package/dist/temp-cleanup.d.ts +2 -0
  153. package/dist/temp-cleanup.d.ts.map +1 -1
  154. package/dist/temp-cleanup.js +25 -10
  155. package/dist/temp-workspace-owner.d.ts +23 -0
  156. package/dist/temp-workspace-owner.d.ts.map +1 -0
  157. package/dist/temp-workspace-owner.js +320 -0
  158. package/dist/temp.d.ts +1 -1
  159. package/dist/temp.d.ts.map +1 -1
  160. package/dist/test-hooks.d.ts +5 -0
  161. package/dist/test-hooks.d.ts.map +1 -1
  162. package/docs/advanced.md +8 -1
  163. package/docs/archive.md +194 -26
  164. package/docs/atomic.md +11 -2
  165. package/docs/durability.md +1 -1
  166. package/docs/file-store.md +2 -0
  167. package/docs/install.md +8 -4
  168. package/docs/json.md +8 -4
  169. package/docs/native-helper.md +15 -3
  170. package/docs/native.md +33 -6
  171. package/docs/output.md +10 -0
  172. package/docs/public-api.md +6 -3
  173. package/docs/quickstart.md +1 -1
  174. package/docs/reading.md +1 -1
  175. package/docs/regular-file.md +9 -2
  176. package/docs/root.md +2 -0
  177. package/docs/secret-file.md +5 -1
  178. package/docs/secure-file.md +4 -2
  179. package/docs/security-model.md +18 -0
  180. package/docs/sidecar-lock.md +4 -0
  181. package/docs/store.md +13 -0
  182. package/docs/temp.md +128 -14
  183. package/package.json +10 -10
@@ -1,14 +1,7 @@
1
1
  import fs from "node:fs";
2
+ import { normalizeMaxBytes } from "./byte-budget.js";
2
3
  import { FsSafeError } from "./errors.js";
3
4
  const READ_CHUNK_BYTES = 64 * 1024;
4
- function assertMaxBytes(maxBytes) {
5
- if (maxBytes === Number.POSITIVE_INFINITY) {
6
- return;
7
- }
8
- if (!Number.isSafeInteger(maxBytes) || maxBytes < 0) {
9
- throw new RangeError("maxBytes must be a non-negative safe integer or Infinity");
10
- }
11
- }
12
5
  function createScratchBuffer(maxBytes) {
13
6
  const initialReadBytes = Number.isFinite(maxBytes)
14
7
  ? Math.min(READ_CHUNK_BYTES, maxBytes + 1)
@@ -29,7 +22,7 @@ function appendChunk(params) {
29
22
  return total;
30
23
  }
31
24
  async function readBoundedAsync(maxBytes, readChunk) {
32
- assertMaxBytes(maxBytes);
25
+ normalizeMaxBytes(maxBytes);
33
26
  const chunks = [];
34
27
  const scratch = createScratchBuffer(maxBytes);
35
28
  let total = 0;
@@ -71,7 +64,7 @@ export async function readFileDescriptorBounded(fd, maxBytes) {
71
64
  }
72
65
  /** Sync bounded read from a numeric descriptor. The caller owns the descriptor. */
73
66
  export function readFileDescriptorBoundedSync(fd, maxBytes) {
74
- assertMaxBytes(maxBytes);
67
+ normalizeMaxBytes(maxBytes);
75
68
  const chunks = [];
76
69
  const scratch = createScratchBuffer(maxBytes);
77
70
  let total = 0;
@@ -0,0 +1,5 @@
1
+ export declare function normalizeMaxBytes(value: number | undefined, options?: {
2
+ defaultValue?: number;
3
+ label?: string;
4
+ }): number | undefined;
5
+ //# sourceMappingURL=byte-budget.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"byte-budget.d.ts","sourceRoot":"","sources":["../src/byte-budget.ts"],"names":[],"mappings":"AAAA,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,MAAM,GAAG,SAAS,EACzB,OAAO,GAAE;IAAE,YAAY,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAO,GACtD,MAAM,GAAG,SAAS,CASpB"}
@@ -0,0 +1,9 @@
1
+ export function normalizeMaxBytes(value, options = {}) {
2
+ const selected = value === undefined ? options.defaultValue : value;
3
+ if (selected === undefined || selected === Number.POSITIVE_INFINITY)
4
+ return selected;
5
+ if (!Number.isSafeInteger(selected) || selected < 0) {
6
+ throw new RangeError(`${options.label ?? "maxBytes"} must be a non-negative safe integer or Infinity`);
7
+ }
8
+ return selected;
9
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"file-lock-sync.d.ts","sourceRoot":"","sources":["../src/file-lock-sync.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EAOL,KAAK,wBAAwB,EAC9B,MAAM,2BAA2B,CAAC;AAKnC,OAAO,KAAK,EACV,0BAA0B,EAC1B,uBAAuB,EACvB,wBAAwB,EACzB,MAAM,yBAAyB,CAAC;AAIjC,MAAM,MAAM,0BAA0B,CAAC,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,IAAI;IACjF,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,uBAAuB,CAAC;IAChC,aAAa,CAAC,EAAE,wBAAwB,CAAC;IACzC,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,OAAO,EAAE,MAAM,QAAQ,CAAC;IACxB,aAAa,CAAC,EAAE,CAAC,MAAM,EAAE;QACvB,QAAQ,EAAE,MAAM,CAAC;QACjB,oBAAoB,EAAE,MAAM,CAAC;QAC7B,OAAO,EAAE,OAAO,CAAC;QACjB,OAAO,EAAE,MAAM,CAAC;QAChB,KAAK,EAAE,MAAM,CAAC;QACd,iBAAiB,EAAE,KAAK,CAAC;KAC1B,KAAK,OAAO,CAAC;IACd,qBAAqB,CAAC,EAAE,CAAC,QAAQ,EAAE,wBAAwB,KAAK,OAAO,CAAC;IACxE,YAAY,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC;IACxC,QAAQ,CAAC,EAAE,IAAI,CAAC;IAChB,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE,0BAA0B,KAAK,IAAI,CAAC;IAC3D,yBAAyB,CAAC,EAAE,MAAM,CAAC;CACpC,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,QAAQ,EAAE,MAAM,CAAC;IACjB,oBAAoB,EAAE,MAAM,CAAC;IAC7B,eAAe,IAAI,OAAO,CAAC;IAC3B,OAAO,IAAI,IAAI,CAAC;IAChB,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,IAAI,CAAC;CAC1B,CAAC;AA0IF,wBAAgB,mBAAmB,CAAC,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC1E,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,0BAA0B,CAAC,QAAQ,CAAC,GAC5C,kBAAkB,CAkKpB;AAED,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC1E,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,0BAA0B,CAAC,QAAQ,CAAC,EAC7C,EAAE,EAAE,MAAM,CAAC,GACV,CAAC,CAOH"}
1
+ {"version":3,"file":"file-lock-sync.d.ts","sourceRoot":"","sources":["../src/file-lock-sync.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EAOL,KAAK,wBAAwB,EAC9B,MAAM,2BAA2B,CAAC;AAOnC,OAAO,KAAK,EACV,0BAA0B,EAC1B,uBAAuB,EACvB,wBAAwB,EACzB,MAAM,yBAAyB,CAAC;AAIjC,MAAM,MAAM,0BAA0B,CAAC,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,IAAI;IACjF,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,uBAAuB,CAAC;IAChC,aAAa,CAAC,EAAE,wBAAwB,CAAC;IACzC,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,OAAO,EAAE,MAAM,QAAQ,CAAC;IACxB,aAAa,CAAC,EAAE,CAAC,MAAM,EAAE;QACvB,QAAQ,EAAE,MAAM,CAAC;QACjB,oBAAoB,EAAE,MAAM,CAAC;QAC7B,OAAO,EAAE,OAAO,CAAC;QACjB,OAAO,EAAE,MAAM,CAAC;QAChB,KAAK,EAAE,MAAM,CAAC;QACd,iBAAiB,EAAE,KAAK,CAAC;KAC1B,KAAK,OAAO,CAAC;IACd,qBAAqB,CAAC,EAAE,CAAC,QAAQ,EAAE,wBAAwB,KAAK,OAAO,CAAC;IACxE,YAAY,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC;IACxC,QAAQ,CAAC,EAAE,IAAI,CAAC;IAChB,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE,0BAA0B,KAAK,IAAI,CAAC;IAC3D,yBAAyB,CAAC,EAAE,MAAM,CAAC;CACpC,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,QAAQ,EAAE,MAAM,CAAC;IACjB,oBAAoB,EAAE,MAAM,CAAC;IAC7B,eAAe,IAAI,OAAO,CAAC;IAC3B,OAAO,IAAI,IAAI,CAAC;IAChB,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,IAAI,CAAC;CAC1B,CAAC;AA0IF,wBAAgB,mBAAmB,CAAC,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC1E,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,0BAA0B,CAAC,QAAQ,CAAC,GAC5C,kBAAkB,CAwKpB;AAED,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC1E,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,0BAA0B,CAAC,QAAQ,CAAC,EAC7C,EAAE,EAAE,MAAM,CAAC,GACV,CAAC,CAOH"}
@@ -2,7 +2,7 @@ import fs from "node:fs";
2
2
  import path from "node:path";
3
3
  import { FsSafeError } from "./errors.js";
4
4
  import { readSidecarLockSnapshotSync, relativeSidecarLockPath, removeSidecarLockIfUnchangedSync, serializeSidecarLockPayload, sidecarLockSnapshotMatches, } from "./sidecar-lock-reclaim.js";
5
- import { computeSidecarLockDelayMs, sidecarLockPayloadCreatedAtMs, } from "./sidecar-lock-policy.js";
5
+ import { computeSidecarLockDelayMs, sidecarLockPayloadCreatedAtMs, validateSidecarLockRetryOptions, validateSidecarLockTimeoutMs, } from "./sidecar-lock-policy.js";
6
6
  import { getFsSafeLockConfig } from "./lock-config.js";
7
7
  import { sleepSync } from "./timing.js";
8
8
  const SYNC_HELD_LOCKS_KEY = Symbol.for("fsSafe.syncSidecarLocks");
@@ -127,6 +127,11 @@ function reclaimGuardExists(reclaimGuardPath) {
127
127
  }
128
128
  }
129
129
  export function acquireFileLockSync(targetPath, options) {
130
+ const defaults = getFsSafeLockConfig();
131
+ const retry = options.retry ?? defaults.retry ?? {};
132
+ const timeoutMs = options.timeoutMs ?? defaults.timeoutMs;
133
+ validateSidecarLockRetryOptions(retry);
134
+ validateSidecarLockTimeoutMs(timeoutMs);
130
135
  const normalizedTargetPath = normalizeTargetPath(targetPath);
131
136
  const lockPath = boundedLockPath(options.lockPath ?? `${normalizedTargetPath}.lock`, options.lockRoot);
132
137
  const heldLocks = getSyncHeldLocks();
@@ -140,10 +145,7 @@ export function acquireFileLockSync(targetPath, options) {
140
145
  }
141
146
  // Process defaults fill the same fields here as withLockDefaults() fills for
142
147
  // the asynchronous manager, so both acquirers honor configureFsSafeLocks().
143
- const defaults = getFsSafeLockConfig();
144
148
  const staleMs = options.staleMs ?? defaults.staleMs ?? 30_000;
145
- const retry = options.retry ?? defaults.retry ?? {};
146
- const timeoutMs = options.timeoutMs ?? defaults.timeoutMs;
147
149
  const staleRecovery = options.staleRecovery ?? defaults.staleRecovery;
148
150
  const startedAt = Date.now();
149
151
  let attempt = 0;
@@ -158,7 +160,10 @@ export function acquireFileLockSync(targetPath, options) {
158
160
  normalizedTargetPath,
159
161
  });
160
162
  }
161
- sleepSync(computeSidecarLockDelayMs(retry, attempt));
163
+ const remaining = timeoutMs === undefined || timeoutMs === Number.POSITIVE_INFINITY
164
+ ? Number.POSITIVE_INFINITY
165
+ : Math.max(0, timeoutMs - elapsed);
166
+ sleepSync(Math.min(computeSidecarLockDelayMs(retry, attempt), remaining));
162
167
  attempt += 1;
163
168
  };
164
169
  while (true) {
@@ -1 +1 @@
1
- {"version":3,"file":"file-store-boundary.d.ts","sourceRoot":"","sources":["../src/file-store-boundary.ts"],"names":[],"mappings":"AAGA,OAAO,EAAa,KAAK,QAAQ,EAAE,MAAM,aAAa,CAAC;AAEvD,OAAO,EAGL,KAAK,kBAAkB,EACxB,MAAM,sBAAsB,CAAC;AAI9B,OAAO,EAA4C,KAAK,IAAI,EAAE,MAAM,WAAW,CAAC;AAGhF,MAAM,MAAM,eAAe,GAAG,kBAAkB,CAAC;AAOjD,wBAAsB,kBAAkB,CACtC,UAAU,EAAE,IAAI,EAChB,YAAY,EAAE,MAAM,EACpB,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,IAAI,CAAC,CAOf;AAED,wBAAsB,qBAAqB,CAAC,MAAM,EAAE;IAClD,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,GAAG,OAAO,CAAC,IAAI,CAAC,CAIhB;AA+CD,wBAAsB,uBAAuB,CAAC,MAAM,EAAE;IACpD,MAAM,EAAE,QAAQ,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;CACd,GAAG,OAAO,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;CAAE,CAAC,CAuC1D;AAED,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,eAAe,GAAG,IAAI,CAWrE;AAUD,wBAAgB,gBAAgB,CAAC,MAAM,EAAE;IACvC,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;CACd,GAAG,eAAe,CAOlB;AAED,wBAAgB,wBAAwB,CAAC,MAAM,EAAE;IAC/C,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,aAAa,EAAE,eAAe,GAAG,OAAO,CAAC;CAC1C,GAAG,eAAe,CAyDlB"}
1
+ {"version":3,"file":"file-store-boundary.d.ts","sourceRoot":"","sources":["../src/file-store-boundary.ts"],"names":[],"mappings":"AAGA,OAAO,EAAa,KAAK,QAAQ,EAAE,MAAM,aAAa,CAAC;AAGvD,OAAO,EAGL,KAAK,kBAAkB,EACxB,MAAM,sBAAsB,CAAC;AAI9B,OAAO,EAA4C,KAAK,IAAI,EAAE,MAAM,WAAW,CAAC;AAGhF,MAAM,MAAM,eAAe,GAAG,kBAAkB,CAAC;AAOjD,wBAAsB,kBAAkB,CACtC,UAAU,EAAE,IAAI,EAChB,YAAY,EAAE,MAAM,EACpB,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,IAAI,CAAC,CAOf;AAED,wBAAsB,qBAAqB,CAAC,MAAM,EAAE;IAClD,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,GAAG,OAAO,CAAC,IAAI,CAAC,CAKhB;AAgDD,wBAAsB,uBAAuB,CAAC,MAAM,EAAE;IACpD,MAAM,EAAE,QAAQ,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;CACd,GAAG,OAAO,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;CAAE,CAAC,CAwC1D;AAED,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,eAAe,GAAG,IAAI,CAWrE;AAUD,wBAAgB,gBAAgB,CAAC,MAAM,EAAE;IACvC,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;CACd,GAAG,eAAe,CAOlB;AAED,wBAAgB,wBAAwB,CAAC,MAAM,EAAE;IAC/C,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,aAAa,EAAE,eAAe,GAAG,OAAO,CAAC;CAC1C,GAAG,eAAe,CAyDlB"}
@@ -2,6 +2,7 @@ import syncFs from "node:fs";
2
2
  import fs from "node:fs/promises";
3
3
  import path from "node:path";
4
4
  import { Transform } from "node:stream";
5
+ import { normalizeMaxBytes } from "./byte-budget.js";
5
6
  import { pipeline } from "node:stream/promises";
6
7
  import { assertSyncDirectoryGuard as assertDirectoryGuardSync, createSyncDirectoryGuard, } from "./directory-guard.js";
7
8
  import { FsSafeError } from "./errors.js";
@@ -22,9 +23,10 @@ export async function ensureParentInRoot(scopedRoot, relativePath, mode) {
22
23
  await chmodDirectoryInRootBestEffort(scopedRoot, parent, mode).catch(() => undefined);
23
24
  }
24
25
  export async function openWritableStoreRoot(params) {
26
+ const maxBytes = normalizeMaxBytes(params.maxBytes);
25
27
  await fs.mkdir(params.rootDir, { recursive: true, mode: params.dirMode });
26
28
  await fs.chmod(params.rootDir, params.dirMode).catch(() => undefined);
27
- return await root(params.rootDir, { hardlinks: "reject", maxBytes: params.maxBytes });
29
+ return await root(params.rootDir, { hardlinks: "reject", maxBytes });
28
30
  }
29
31
  async function chmodDirectoryInRootBestEffort(scopedRoot, relativePath, mode) {
30
32
  const dirPath = await scopedRoot.resolve(relativePath);
@@ -49,7 +51,8 @@ async function chmodDirectoryInRootBestEffort(scopedRoot, relativePath, mode) {
49
51
  }
50
52
  }
51
53
  function createMaxBytesTransform(maxBytes) {
52
- if (maxBytes === undefined) {
54
+ const limit = normalizeMaxBytes(maxBytes);
55
+ if (limit === undefined) {
53
56
  return undefined;
54
57
  }
55
58
  let total = 0;
@@ -57,8 +60,8 @@ function createMaxBytesTransform(maxBytes) {
57
60
  transform(chunk, _encoding, callback) {
58
61
  const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
59
62
  total += buffer.byteLength;
60
- if (total > maxBytes) {
61
- callback(new FsSafeError("too-large", `file exceeds maximum size of ${maxBytes} bytes`));
63
+ if (total > limit) {
64
+ callback(new FsSafeError("too-large", `file exceeds maximum size of ${limit} bytes`));
62
65
  return;
63
66
  }
64
67
  callback(null, buffer);
@@ -66,6 +69,7 @@ function createMaxBytesTransform(maxBytes) {
66
69
  });
67
70
  }
68
71
  export async function writeStreamToTempSource(params) {
72
+ const maxBytes = normalizeMaxBytes(params.maxBytes);
69
73
  const tempRoot = resolveSecureTempRoot({
70
74
  fallbackPrefix: "fs-safe-file-store",
71
75
  unsafeFallbackLabel: "file store temp dir",
@@ -81,7 +85,7 @@ export async function writeStreamToTempSource(params) {
81
85
  writable.once("close", () => {
82
86
  handleClosedByStream = true;
83
87
  });
84
- const limiter = createMaxBytesTransform(params.maxBytes);
88
+ const limiter = createMaxBytesTransform(maxBytes);
85
89
  if (limiter) {
86
90
  await pipeline(params.stream, limiter, writable);
87
91
  }
@@ -0,0 +1,2 @@
1
+ export declare function assertFileStoreMaxBytes(size: number, maxBytes?: number): void;
2
+ //# sourceMappingURL=file-store-limit.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-store-limit.d.ts","sourceRoot":"","sources":["../src/file-store-limit.ts"],"names":[],"mappings":"AAGA,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAK7E"}
@@ -0,0 +1,8 @@
1
+ import { normalizeMaxBytes } from "./byte-budget.js";
2
+ import { FsSafeError } from "./errors.js";
3
+ export function assertFileStoreMaxBytes(size, maxBytes) {
4
+ const limit = normalizeMaxBytes(maxBytes);
5
+ if (limit !== undefined && size > limit) {
6
+ throw new FsSafeError("too-large", `file exceeds maximum size of ${limit} bytes`);
7
+ }
8
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"file-store-sync-write.d.ts","sourceRoot":"","sources":["../src/file-store-sync-write.ts"],"names":[],"mappings":"AAuBA,wBAAgB,mBAAmB,CAAC,MAAM,EAAE;IAC1C,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,GAAG,UAAU,CAAC;IAC7B,WAAW,EAAE,OAAO,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;CACd,GAAG,MAAM,CA+ET"}
1
+ {"version":3,"file":"file-store-sync-write.d.ts","sourceRoot":"","sources":["../src/file-store-sync-write.ts"],"names":[],"mappings":"AAwBA,wBAAgB,mBAAmB,CAAC,MAAM,EAAE;IAC1C,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,GAAG,UAAU,CAAC;IAC7B,WAAW,EAAE,OAAO,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;CACd,GAAG,MAAM,CA6GT"}
@@ -1,6 +1,7 @@
1
1
  import { randomUUID } from "node:crypto";
2
2
  import fs from "node:fs";
3
3
  import path from "node:path";
4
+ import { syncDirectorySync } from "./directory-durability.js";
4
5
  import { FsSafeError } from "./errors.js";
5
6
  import { sameFileIdentity } from "./file-identity.js";
6
7
  import { assertSyncDirectoryGuard, ensureParentSync, ensureStoreDirectorySync, } from "./file-store-boundary.js";
@@ -48,15 +49,35 @@ export function writeFileSyncAtomic(params) {
48
49
  if (parentGuard) {
49
50
  assertSyncDirectoryGuard(parentGuard);
50
51
  }
51
- fs.writeFileSync(tempPath, params.content, { flag: "wx", mode: params.mode });
52
- tempExists = true;
53
- try {
54
- fs.chmodSync(tempPath, params.mode);
55
- }
56
- catch {
57
- // Best-effort on platforms that do not enforce POSIX modes.
52
+ const tempStat = (() => {
53
+ const descriptor = fs.openSync(tempPath, "wx", params.mode);
54
+ tempExists = true;
55
+ try {
56
+ fs.writeFileSync(descriptor, params.content);
57
+ try {
58
+ fs.fchmodSync(descriptor, params.mode);
59
+ }
60
+ catch {
61
+ // Best-effort on platforms that do not enforce POSIX modes.
62
+ }
63
+ const opened = fs.fstatSync(descriptor);
64
+ if (!opened.isFile() || opened.nlink > 1) {
65
+ throw new FsSafeError("path-mismatch", "store temp is not an owned regular file");
66
+ }
67
+ fs.fsyncSync(descriptor);
68
+ return opened;
69
+ }
70
+ finally {
71
+ fs.closeSync(descriptor);
72
+ }
73
+ })();
74
+ const tempPathStat = fs.lstatSync(tempPath);
75
+ if (tempPathStat.isSymbolicLink() ||
76
+ !tempPathStat.isFile() ||
77
+ tempPathStat.nlink > 1 ||
78
+ !sameFileIdentity(tempPathStat, tempStat)) {
79
+ throw new FsSafeError("path-mismatch", "store temp changed before publication");
58
80
  }
59
- const tempStat = fs.lstatSync(tempPath);
60
81
  if (parentGuard) {
61
82
  assertSyncDirectoryGuard(parentGuard);
62
83
  }
@@ -82,6 +103,15 @@ export function writeFileSyncAtomic(params) {
82
103
  cause: error instanceof Error ? error : undefined,
83
104
  });
84
105
  }
106
+ if (parentGuard) {
107
+ assertSyncDirectoryGuard(parentGuard);
108
+ syncDirectorySync({
109
+ path: parentGuard.dir,
110
+ realPath: parentGuard.realPath,
111
+ identity: parentGuard.stat,
112
+ }, { label: "store parent" });
113
+ assertSyncDirectoryGuard(parentGuard);
114
+ }
85
115
  return filePath;
86
116
  }
87
117
  finally {
@@ -1 +1 @@
1
- {"version":3,"file":"file-store.d.ts","sourceRoot":"","sources":["../src/file-store.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"file-store.d.ts","sourceRoot":"","sources":["../src/file-store.ts"],"names":[],"mappings":"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"}
@@ -1,8 +1,10 @@
1
1
  import syncFs from "node:fs";
2
2
  import fs from "node:fs/promises";
3
3
  import path from "node:path";
4
+ import { normalizeMaxBytes } from "./byte-budget.js";
4
5
  import { readFileDescriptorBoundedSync } from "./bounded-read.js";
5
6
  import { FsSafeError } from "./errors.js";
7
+ import { assertFileStoreMaxBytes } from "./file-store-limit.js";
6
8
  import { pruneExpiredStoreEntries } from "./file-store-prune.js";
7
9
  import { ensureParentInRoot, openWritableStoreRoot, writeStreamToTempSource, } from "./file-store-boundary.js";
8
10
  import { writeFileSyncAtomic } from "./file-store-sync-write.js";
@@ -38,15 +40,8 @@ function assertRelativePath(relativePath) {
38
40
  function resolveStorePath(rootDir, relativePath) {
39
41
  return resolveSafeRelativePath(rootDir, assertRelativePath(relativePath));
40
42
  }
41
- function assertMaxBytes(size, maxBytes) {
42
- if (maxBytes !== undefined && size > maxBytes) {
43
- throw new FsSafeError("too-large", `file exceeds maximum size of ${maxBytes} bytes`);
44
- }
45
- }
46
43
  function isNotFound(error) {
47
- return error instanceof FsSafeError
48
- ? error.code === "not-found"
49
- : isNotFoundPathError(error);
44
+ return error instanceof FsSafeError ? error.code === "not-found" : isNotFoundPathError(error);
50
45
  }
51
46
  function handleSyncStoreReadOpenFailure(opened) {
52
47
  return matchRootFileOpenFailure(opened, {
@@ -81,7 +76,7 @@ async function readFileStoreCopySource(params) {
81
76
  if (sourceStat.isSymbolicLink() || !sourceStat.isFile()) {
82
77
  throw new FsSafeError("not-file", "source path is not a file");
83
78
  }
84
- assertMaxBytes(sourceStat.size, params.maxBytes);
79
+ assertFileStoreMaxBytes(sourceStat.size, params.maxBytes);
85
80
  try {
86
81
  return (await readRegularFile({ filePath: params.sourcePath, maxBytes: params.maxBytes }))
87
82
  .buffer;
@@ -108,7 +103,7 @@ async function copyIntoRoot(params) {
108
103
  if (sourceStat.isSymbolicLink() || !sourceStat.isFile()) {
109
104
  throw new FsSafeError("not-file", "source path is not a file");
110
105
  }
111
- assertMaxBytes(sourceStat.size, params.maxBytes);
106
+ assertFileStoreMaxBytes(sourceStat.size, params.maxBytes);
112
107
  const dirMode = params.dirMode ?? 0o700;
113
108
  const scopedRoot = await openWritableStoreRoot({
114
109
  rootDir: params.rootDir,
@@ -128,7 +123,7 @@ export function fileStore(options) {
128
123
  const privateMode = options.private ?? false;
129
124
  const dirMode = options.dirMode ?? 0o700;
130
125
  const mode = options.mode ?? 0o600;
131
- const maxBytes = options.maxBytes;
126
+ const maxBytes = normalizeMaxBytes(options.maxBytes);
132
127
  async function openRoot() {
133
128
  return await root(rootDir, { hardlinks: "reject", maxBytes });
134
129
  }
@@ -136,7 +131,8 @@ export function fileStore(options) {
136
131
  const safeRelativePath = assertRelativePath(relativePath);
137
132
  const destination = resolveStorePath(rootDir, safeRelativePath);
138
133
  const content = Buffer.isBuffer(data) ? data : Buffer.from(data);
139
- assertMaxBytes(content.byteLength, writeOptions?.maxBytes ?? maxBytes);
134
+ const writeMaxBytes = normalizeMaxBytes(writeOptions?.maxBytes, { defaultValue: maxBytes });
135
+ assertFileStoreMaxBytes(content.byteLength, writeMaxBytes);
140
136
  if (privateMode) {
141
137
  await writeSecretFileAtomic({
142
138
  rootDir,
@@ -151,7 +147,7 @@ export function fileStore(options) {
151
147
  const scopedRoot = await openWritableStoreRoot({
152
148
  rootDir,
153
149
  dirMode: writeDirMode,
154
- maxBytes: writeOptions?.maxBytes ?? maxBytes,
150
+ maxBytes: writeMaxBytes,
155
151
  });
156
152
  await ensureParentInRoot(scopedRoot, safeRelativePath, writeDirMode);
157
153
  await scopedRoot.write(safeRelativePath, content, {
@@ -168,14 +164,15 @@ export function fileStore(options) {
168
164
  writeStream: async (relativePath, stream, writeOptions) => {
169
165
  const safeRelativePath = assertRelativePath(relativePath);
170
166
  const destination = resolveStorePath(rootDir, safeRelativePath);
171
- const limit = writeOptions?.maxBytes ?? maxBytes ?? (privateMode ? DEFAULT_ROOT_MAX_BYTES : undefined);
167
+ const configuredLimit = normalizeMaxBytes(writeOptions?.maxBytes, { defaultValue: maxBytes });
168
+ const limit = configuredLimit ?? (privateMode ? DEFAULT_ROOT_MAX_BYTES : undefined);
172
169
  if (privateMode) {
173
170
  const chunks = [];
174
171
  let total = 0;
175
172
  for await (const chunk of stream) {
176
173
  const buffer = typeof chunk === "string" ? Buffer.from(chunk) : Buffer.from(chunk);
177
174
  total += buffer.byteLength;
178
- assertMaxBytes(total, limit);
175
+ assertFileStoreMaxBytes(total, limit);
179
176
  chunks.push(buffer);
180
177
  }
181
178
  await writeSecretFileAtomic({
@@ -208,23 +205,25 @@ export function fileStore(options) {
208
205
  }
209
206
  return destination;
210
207
  },
211
- copyIn: async (relativePath, sourcePath, writeOptions) => privateMode
212
- ? await (async () => {
208
+ copyIn: async (relativePath, sourcePath, writeOptions) => {
209
+ const configuredLimit = normalizeMaxBytes(writeOptions?.maxBytes, { defaultValue: maxBytes });
210
+ if (privateMode) {
213
211
  const buffer = await readFileStoreCopySource({
214
212
  sourcePath,
215
- maxBytes: writeOptions?.maxBytes ?? maxBytes ?? DEFAULT_ROOT_MAX_BYTES,
213
+ maxBytes: configuredLimit ?? DEFAULT_ROOT_MAX_BYTES,
216
214
  });
217
215
  return await write(relativePath, buffer, writeOptions);
218
- })()
219
- : await copyIntoRoot({
216
+ }
217
+ return await copyIntoRoot({
220
218
  rootDir,
221
219
  relativePath,
222
220
  sourcePath,
223
221
  dirMode: writeOptions?.dirMode ?? dirMode,
224
- maxBytes: writeOptions?.maxBytes ?? maxBytes,
222
+ maxBytes: configuredLimit,
225
223
  mode: writeOptions?.mode ?? mode,
226
224
  tempPrefix: writeOptions?.tempPrefix,
227
- }),
225
+ });
226
+ },
228
227
  open: async (relativePath, readOptions) => await (await openRoot()).open(assertRelativePath(relativePath), readOptions),
229
228
  read: async (relativePath, readOptions) => await (await openRoot()).read(assertRelativePath(relativePath), readOptions),
230
229
  readBytes: async (relativePath, readOptions) => await (await openRoot()).readBytes(assertRelativePath(relativePath), readOptions),
@@ -301,11 +300,12 @@ export function fileStoreSync(options) {
301
300
  const privateMode = options.private ?? false;
302
301
  const dirMode = options.dirMode ?? 0o700;
303
302
  const mode = options.mode ?? 0o600;
304
- const maxBytes = options.maxBytes;
303
+ const maxBytes = normalizeMaxBytes(options.maxBytes);
305
304
  function write(relativePath, data, writeOptions) {
306
305
  const destination = resolveStorePath(rootDir, relativePath);
307
306
  const content = Buffer.isBuffer(data) ? data : Buffer.from(data);
308
- assertMaxBytes(content.byteLength, writeOptions?.maxBytes ?? maxBytes);
307
+ const writeMaxBytes = normalizeMaxBytes(writeOptions?.maxBytes, { defaultValue: maxBytes });
308
+ assertFileStoreMaxBytes(content.byteLength, writeMaxBytes);
309
309
  return writeFileSyncAtomic({
310
310
  rootDir,
311
311
  filePath: destination,
@@ -319,6 +319,7 @@ export function fileStoreSync(options) {
319
319
  rootDir,
320
320
  path: (relativePath) => resolveStorePath(rootDir, relativePath),
321
321
  readTextIfExists: (relativePath, readOptions) => {
322
+ const limit = normalizeMaxBytes(readOptions?.maxBytes, { defaultValue: maxBytes });
322
323
  const targetPath = resolveStorePath(rootDir, relativePath);
323
324
  const opened = openRootFileSync({
324
325
  absolutePath: targetPath,
@@ -330,8 +331,7 @@ export function fileStoreSync(options) {
330
331
  return handleSyncStoreReadOpenFailure(opened);
331
332
  }
332
333
  try {
333
- assertMaxBytes(opened.stat.size, readOptions?.maxBytes ?? maxBytes);
334
- const limit = readOptions?.maxBytes ?? maxBytes;
334
+ assertFileStoreMaxBytes(opened.stat.size, limit);
335
335
  try {
336
336
  return limit === undefined
337
337
  ? syncFs.readFileSync(opened.fd, "utf8")
@@ -0,0 +1,2 @@
1
+ export declare function syncQueueDirectoryCreation(dir: string, validationBase: string): Promise<void>;
2
+ //# sourceMappingURL=json-durable-queue-directory.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"json-durable-queue-directory.d.ts","sourceRoot":"","sources":["../src/json-durable-queue-directory.ts"],"names":[],"mappings":"AAKA,wBAAsB,0BAA0B,CAC9C,GAAG,EAAE,MAAM,EACX,cAAc,EAAE,MAAM,GACrB,OAAO,CAAC,IAAI,CAAC,CAiBf"}
@@ -0,0 +1,20 @@
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { syncDirectory } from "./directory-durability.js";
4
+ import { isPathRelativeEscape } from "./path.js";
5
+ export async function syncQueueDirectoryCreation(dir, validationBase) {
6
+ const baseReal = await fs.realpath(validationBase);
7
+ const targetReal = await fs.realpath(dir);
8
+ const relative = path.relative(baseReal, targetReal);
9
+ if (isPathRelativeEscape(relative)) {
10
+ throw new Error(`durable queue directory escapes validation base: ${dir}`);
11
+ }
12
+ const directories = [baseReal];
13
+ for (const segment of relative.split(path.sep).filter(Boolean)) {
14
+ directories.push(path.join(directories.at(-1), segment));
15
+ }
16
+ await syncDirectory(targetReal, { label: "durable queue directory" });
17
+ for (const parent of directories.slice(0, -1).toReversed()) {
18
+ await syncDirectory(parent, { label: "durable queue parent" });
19
+ }
20
+ }
@@ -0,0 +1,14 @@
1
+ export type DurableQueueEntryPathsLike = {
2
+ jsonPath: string;
3
+ deliveredPath: string;
4
+ processingPath?: string;
5
+ };
6
+ export declare function durableQueueProcessingPath(paths: DurableQueueEntryPathsLike): string;
7
+ export declare function claimDurableQueueEntry(paths: DurableQueueEntryPathsLike): Promise<string | null>;
8
+ export declare function completeDeliveredQueueEntry(paths: DurableQueueEntryPathsLike): Promise<boolean>;
9
+ export declare function acknowledgeDurableQueueEntry(paths: DurableQueueEntryPathsLike): Promise<void>;
10
+ export declare function moveDurableQueueEntryToFailed(params: {
11
+ paths: DurableQueueEntryPathsLike;
12
+ failedPath: string;
13
+ }): Promise<void>;
14
+ //# sourceMappingURL=json-durable-queue-ownership.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"json-durable-queue-ownership.d.ts","sourceRoot":"","sources":["../src/json-durable-queue-ownership.ts"],"names":[],"mappings":"AAaA,MAAM,MAAM,0BAA0B,GAAG;IACvC,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB,CAAC;AAQF,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,0BAA0B,GAAG,MAAM,CAKpF;AAqED,wBAAsB,sBAAsB,CAC1C,KAAK,EAAE,0BAA0B,GAChC,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAGxB;AAED,wBAAsB,2BAA2B,CAC/C,KAAK,EAAE,0BAA0B,GAChC,OAAO,CAAC,OAAO,CAAC,CAalB;AAED,wBAAsB,4BAA4B,CAChD,KAAK,EAAE,0BAA0B,GAChC,OAAO,CAAC,IAAI,CAAC,CAyBf;AAED,wBAAsB,6BAA6B,CAAC,MAAM,EAAE;IAC1D,KAAK,EAAE,0BAA0B,CAAC;IAClC,UAAU,EAAE,MAAM,CAAC;CACpB,GAAG,OAAO,CAAC,IAAI,CAAC,CAiDhB"}