@openclaw/fs-safe 0.6.0 → 0.7.1

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 (202) hide show
  1. package/CHANGELOG.md +79 -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 -27
  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/directory-guard.d.ts +2 -1
  57. package/dist/directory-guard.d.ts.map +1 -1
  58. package/dist/directory-guard.js +10 -0
  59. package/dist/file-lock-sync.d.ts.map +1 -1
  60. package/dist/file-lock-sync.js +81 -30
  61. package/dist/file-observation.d.ts +9 -0
  62. package/dist/file-observation.d.ts.map +1 -0
  63. package/dist/file-observation.js +22 -0
  64. package/dist/file-store-boundary.d.ts.map +1 -1
  65. package/dist/file-store-boundary.js +9 -5
  66. package/dist/file-store-limit.d.ts +2 -0
  67. package/dist/file-store-limit.d.ts.map +1 -0
  68. package/dist/file-store-limit.js +8 -0
  69. package/dist/file-store-sync-write.d.ts.map +1 -1
  70. package/dist/file-store-sync-write.js +38 -8
  71. package/dist/file-store.d.ts.map +1 -1
  72. package/dist/file-store.js +26 -26
  73. package/dist/json-durable-queue-directory.d.ts +2 -0
  74. package/dist/json-durable-queue-directory.d.ts.map +1 -0
  75. package/dist/json-durable-queue-directory.js +20 -0
  76. package/dist/json-durable-queue-ownership.d.ts +14 -0
  77. package/dist/json-durable-queue-ownership.d.ts.map +1 -0
  78. package/dist/json-durable-queue-ownership.js +168 -0
  79. package/dist/json-durable-queue-retirement.d.ts +9 -0
  80. package/dist/json-durable-queue-retirement.d.ts.map +1 -0
  81. package/dist/json-durable-queue-retirement.js +126 -0
  82. package/dist/json-durable-queue-transfer-lock.d.ts +2 -0
  83. package/dist/json-durable-queue-transfer-lock.d.ts.map +1 -0
  84. package/dist/json-durable-queue-transfer-lock.js +19 -0
  85. package/dist/json-durable-queue.d.ts +1 -0
  86. package/dist/json-durable-queue.d.ts.map +1 -1
  87. package/dist/json-durable-queue.js +90 -57
  88. package/dist/json.d.ts.map +1 -1
  89. package/dist/json.js +32 -13
  90. package/dist/local-roots.d.ts.map +1 -1
  91. package/dist/local-roots.js +4 -2
  92. package/dist/native-binding.d.ts +12 -3
  93. package/dist/native-binding.d.ts.map +1 -1
  94. package/dist/native-pinned-write-windows.js +1 -1
  95. package/dist/opened-file-failure.d.ts +7 -0
  96. package/dist/opened-file-failure.d.ts.map +1 -0
  97. package/dist/opened-file-failure.js +41 -0
  98. package/dist/opened-realpath.d.ts.map +1 -1
  99. package/dist/opened-realpath.js +8 -2
  100. package/dist/output-sibling.d.ts.map +1 -1
  101. package/dist/output-sibling.js +11 -110
  102. package/dist/output.d.ts.map +1 -1
  103. package/dist/output.js +4 -2
  104. package/dist/pinned-open.d.ts.map +1 -1
  105. package/dist/pinned-open.js +27 -48
  106. package/dist/pinned-write.d.ts.map +1 -1
  107. package/dist/pinned-write.js +10 -10
  108. package/dist/private-temp-workspace.d.ts +3 -1
  109. package/dist/private-temp-workspace.d.ts.map +1 -1
  110. package/dist/private-temp-workspace.js +81 -56
  111. package/dist/publish-file.d.ts.map +1 -1
  112. package/dist/publish-file.js +2 -4
  113. package/dist/read-opened-file.d.ts.map +1 -1
  114. package/dist/read-opened-file.js +6 -4
  115. package/dist/regular-file.d.ts.map +1 -1
  116. package/dist/regular-file.js +136 -82
  117. package/dist/replace-file-copy-fallback.d.ts +3 -1
  118. package/dist/replace-file-copy-fallback.d.ts.map +1 -1
  119. package/dist/replace-file-copy-fallback.js +21 -36
  120. package/dist/replace-file-copy-source.d.ts +21 -0
  121. package/dist/replace-file-copy-source.d.ts.map +1 -0
  122. package/dist/replace-file-copy-source.js +112 -0
  123. package/dist/replace-file-descriptor.d.ts +13 -3
  124. package/dist/replace-file-descriptor.d.ts.map +1 -1
  125. package/dist/replace-file-descriptor.js +32 -7
  126. package/dist/replace-file-rename-policy.d.ts +7 -0
  127. package/dist/replace-file-rename-policy.d.ts.map +1 -0
  128. package/dist/replace-file-rename-policy.js +30 -0
  129. package/dist/replace-file-temp-owner.d.ts +46 -0
  130. package/dist/replace-file-temp-owner.d.ts.map +1 -0
  131. package/dist/replace-file-temp-owner.js +346 -0
  132. package/dist/replace-file.d.ts +6 -1
  133. package/dist/replace-file.d.ts.map +1 -1
  134. package/dist/replace-file.js +72 -58
  135. package/dist/root-impl.d.ts.map +1 -1
  136. package/dist/root-impl.js +113 -82
  137. package/dist/secret-file.d.ts.map +1 -1
  138. package/dist/secret-file.js +6 -3
  139. package/dist/secret-read-async.d.ts.map +1 -1
  140. package/dist/secret-read-async.js +6 -5
  141. package/dist/secure-file.d.ts.map +1 -1
  142. package/dist/secure-file.js +17 -13
  143. package/dist/sibling-staged-file.d.ts +15 -0
  144. package/dist/sibling-staged-file.d.ts.map +1 -0
  145. package/dist/sibling-staged-file.js +148 -0
  146. package/dist/sibling-temp.d.ts +3 -0
  147. package/dist/sibling-temp.d.ts.map +1 -1
  148. package/dist/sibling-temp.js +30 -74
  149. package/dist/sidecar-lock-acquire.d.ts.map +1 -1
  150. package/dist/sidecar-lock-acquire.js +91 -37
  151. package/dist/sidecar-lock-handle.d.ts +6 -2
  152. package/dist/sidecar-lock-handle.d.ts.map +1 -1
  153. package/dist/sidecar-lock-handle.js +17 -3
  154. package/dist/sidecar-lock-policy.d.ts +2 -0
  155. package/dist/sidecar-lock-policy.d.ts.map +1 -1
  156. package/dist/sidecar-lock-policy.js +29 -0
  157. package/dist/sidecar-lock-reclaim.d.ts +16 -5
  158. package/dist/sidecar-lock-reclaim.d.ts.map +1 -1
  159. package/dist/sidecar-lock-reclaim.js +60 -28
  160. package/dist/sidecar-lock-root.d.ts +3 -0
  161. package/dist/sidecar-lock-root.d.ts.map +1 -0
  162. package/dist/sidecar-lock-root.js +76 -0
  163. package/dist/sidecar-lock.d.ts.map +1 -1
  164. package/dist/sidecar-lock.js +28 -16
  165. package/dist/strict-file-identity.d.ts.map +1 -1
  166. package/dist/strict-file-identity.js +4 -1
  167. package/dist/suppressed-error.d.ts +6 -0
  168. package/dist/suppressed-error.d.ts.map +1 -0
  169. package/dist/suppressed-error.js +15 -0
  170. package/dist/temp-cleanup.d.ts +2 -0
  171. package/dist/temp-cleanup.d.ts.map +1 -1
  172. package/dist/temp-cleanup.js +25 -10
  173. package/dist/temp-workspace-owner.d.ts +23 -0
  174. package/dist/temp-workspace-owner.d.ts.map +1 -0
  175. package/dist/temp-workspace-owner.js +320 -0
  176. package/dist/temp.d.ts +1 -1
  177. package/dist/temp.d.ts.map +1 -1
  178. package/dist/test-hooks.d.ts +5 -0
  179. package/dist/test-hooks.d.ts.map +1 -1
  180. package/docs/advanced.md +8 -1
  181. package/docs/archive.md +202 -26
  182. package/docs/atomic.md +11 -2
  183. package/docs/durability.md +1 -1
  184. package/docs/file-store.md +2 -0
  185. package/docs/install.md +8 -4
  186. package/docs/json.md +11 -4
  187. package/docs/native-helper.md +15 -3
  188. package/docs/native.md +33 -6
  189. package/docs/output.md +16 -3
  190. package/docs/public-api.md +6 -3
  191. package/docs/quickstart.md +1 -1
  192. package/docs/reading.md +1 -1
  193. package/docs/regular-file.md +9 -2
  194. package/docs/root.md +2 -0
  195. package/docs/secret-file.md +5 -1
  196. package/docs/secure-file.md +4 -2
  197. package/docs/security-model.md +18 -0
  198. package/docs/sidecar-lock.md +60 -4
  199. package/docs/store.md +13 -0
  200. package/docs/temp.md +128 -14
  201. package/docs/writing.md +6 -0
  202. package/package.json +10 -10
@@ -1 +1 @@
1
- {"version":3,"file":"bounded-read.d.ts","sourceRoot":"","sources":["../src/bounded-read.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAKnD,KAAK,kBAAkB,GAAG,IAAI,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;AA4DnD;;;;GAIG;AACH,wBAAsB,qBAAqB,CACzC,MAAM,EAAE,kBAAkB,EAC1B,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,MAAM,CAAC,CAIjB;AAcD,oFAAoF;AACpF,wBAAsB,yBAAyB,CAAC,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAI7F;AAED,mFAAmF;AACnF,wBAAgB,6BAA6B,CAAC,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAalF"}
1
+ {"version":3,"file":"bounded-read.d.ts","sourceRoot":"","sources":["../src/bounded-read.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAMnD,KAAK,kBAAkB,GAAG,IAAI,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;AAmDnD;;;;GAIG;AACH,wBAAsB,qBAAqB,CACzC,MAAM,EAAE,kBAAkB,EAC1B,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,MAAM,CAAC,CAIjB;AAcD,oFAAoF;AACpF,wBAAsB,yBAAyB,CAAC,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAI7F;AAED,mFAAmF;AACnF,wBAAgB,6BAA6B,CAAC,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAalF"}
@@ -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,4 +1,4 @@
1
- import type { Stats } from "node:fs";
1
+ import type { BigIntStats, Stats } from "node:fs";
2
2
  export type AsyncDirectoryGuard = {
3
3
  dir: string;
4
4
  realPath: string;
@@ -15,4 +15,5 @@ export declare function createSyncDirectoryGuard(dir: string): SyncDirectoryGuar
15
15
  export declare function assertSyncDirectoryGuard(guard: SyncDirectoryGuard): void;
16
16
  export declare function createNearestExistingDirectoryGuard(rootReal: string, targetPath: string): Promise<AsyncDirectoryGuard>;
17
17
  export declare function createNearestExistingSyncDirectoryGuard(rootReal: string, targetPath: string): SyncDirectoryGuard;
18
+ export declare function inspectDirectoryIdentity(dir: string, expected?: BigIntStats): Promise<BigIntStats>;
18
19
  //# sourceMappingURL=directory-guard.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"directory-guard.d.ts","sourceRoot":"","sources":["../src/directory-guard.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AASrC,MAAM,MAAM,mBAAmB,GAAG;IAChC,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,KAAK,CAAC;CACb,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,KAAK,CAAC;CACb,CAAC;AAEF,wBAAsB,yBAAyB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAMzF;AAED,wBAAsB,yBAAyB,CAAC,KAAK,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC,CAQzF;AAED,wBAAgB,wBAAwB,CAAC,GAAG,EAAE,MAAM,GAAG,kBAAkB,CAMxE;AAED,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,kBAAkB,GAAG,IAAI,CAQxE;AAED,wBAAsB,mCAAmC,CACvD,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,mBAAmB,CAAC,CAc9B;AAED,wBAAgB,uCAAuC,CACrD,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,GACjB,kBAAkB,CAcpB"}
1
+ {"version":3,"file":"directory-guard.d.ts","sourceRoot":"","sources":["../src/directory-guard.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAUlD,MAAM,MAAM,mBAAmB,GAAG;IAChC,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,KAAK,CAAC;CACb,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,KAAK,CAAC;CACb,CAAC;AAEF,wBAAsB,yBAAyB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAMzF;AAED,wBAAsB,yBAAyB,CAAC,KAAK,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC,CAQzF;AAED,wBAAgB,wBAAwB,CAAC,GAAG,EAAE,MAAM,GAAG,kBAAkB,CAMxE;AAED,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,kBAAkB,GAAG,IAAI,CAQxE;AAED,wBAAsB,mCAAmC,CACvD,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,mBAAmB,CAAC,CAc9B;AAED,wBAAgB,uCAAuC,CACrD,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,GACjB,kBAAkB,CAcpB;AAGD,wBAAsB,wBAAwB,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,WAAW,CAAC,CAMxG"}
@@ -3,6 +3,7 @@ import fs from "node:fs/promises";
3
3
  import path from "node:path";
4
4
  import { FsSafeError } from "./errors.js";
5
5
  import { sameFileIdentity } from "./file-identity.js";
6
+ import { inspectFileIdentity } from "./strict-file-identity.js";
6
7
  import { isNotFoundPathError } from "./path.js";
7
8
  import { directoryComponentNotDirectoryError } from "./root-errors.js";
8
9
  export async function createAsyncDirectoryGuard(dir) {
@@ -69,3 +70,12 @@ export function createNearestExistingSyncDirectoryGuard(rootReal, targetPath) {
69
70
  }
70
71
  return createSyncDirectoryGuard(root);
71
72
  }
73
+ // Recovery receipts must retain every identity bit, including on Windows.
74
+ export async function inspectDirectoryIdentity(dir, expected) {
75
+ return await inspectFileIdentity(async () => {
76
+ const stat = await fs.lstat(dir, { bigint: true });
77
+ if (stat.isSymbolicLink() || !stat.isDirectory())
78
+ throw directoryComponentNotDirectoryError();
79
+ return stat;
80
+ }, expected);
81
+ }
@@ -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,EASL,KAAK,wBAAwB,EAC9B,MAAM,2BAA2B,CAAC;AASnC,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;AAoJF,wBAAgB,mBAAmB,CAAC,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC1E,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,0BAA0B,CAAC,QAAQ,CAAC,GAC5C,kBAAkB,CAwMpB;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,8 +1,8 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
3
  import { FsSafeError } from "./errors.js";
4
- import { readSidecarLockSnapshotSync, relativeSidecarLockPath, removeSidecarLockIfUnchangedSync, serializeSidecarLockPayload, sidecarLockSnapshotMatches, } from "./sidecar-lock-reclaim.js";
5
- import { computeSidecarLockDelayMs, sidecarLockPayloadCreatedAtMs, } from "./sidecar-lock-policy.js";
4
+ import { readSidecarLockSnapshotSync, readSidecarLockRawSnapshotSync, parseSidecarLockSnapshot, relativeSidecarLockPath, removeSidecarLockIfUnchangedSync, serializeSidecarLockPayload, sidecarLockSnapshotMatches, } from "./sidecar-lock-reclaim.js";
5
+ import { computeSidecarLockDelayMs, isTransientLockFileDenial, maxTransientLockDenials, 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");
@@ -23,7 +23,8 @@ function releaseAllSyncHeldLocks() {
23
23
  held.timer = undefined;
24
24
  }
25
25
  try {
26
- fs.closeSync(held.fd);
26
+ if (held.fd !== undefined)
27
+ fs.closeSync(held.fd);
27
28
  }
28
29
  catch {
29
30
  // Best-effort process-exit cleanup.
@@ -53,16 +54,23 @@ function releaseSyncHeldLock(held) {
53
54
  const heldLocks = getSyncHeldLocks();
54
55
  if (heldLocks.get(held.normalizedTargetPath) !== held)
55
56
  return false;
56
- held.refCount -= 1;
57
- if (held.refCount > 0)
57
+ if (held.refCount > 1) {
58
+ held.refCount -= 1;
58
59
  return false;
59
- heldLocks.delete(held.normalizedTargetPath);
60
+ }
61
+ // Keep the final reference and cleanup receipt until deletion succeeds.
60
62
  if (held.timer) {
61
63
  clearInterval(held.timer);
62
64
  held.timer = undefined;
63
65
  }
64
- fs.closeSync(held.fd);
66
+ if (held.fd !== undefined) {
67
+ const fd = held.fd;
68
+ // A close error may still free the number; consume ownership before closing.
69
+ held.fd = undefined;
70
+ fs.closeSync(fd);
71
+ }
65
72
  removeSidecarLockIfUnchangedSync(held.lockPath, held.snapshot);
73
+ heldLocks.delete(held.normalizedTargetPath);
66
74
  return true;
67
75
  }
68
76
  function createSyncHeldLockHandle(held) {
@@ -70,8 +78,8 @@ function createSyncHeldLockHandle(held) {
70
78
  const release = () => {
71
79
  if (released)
72
80
  return;
73
- released = true;
74
81
  releaseSyncHeldLock(held);
82
+ released = true;
75
83
  };
76
84
  return {
77
85
  lockPath: held.lockPath,
@@ -81,11 +89,15 @@ function createSyncHeldLockHandle(held) {
81
89
  [Symbol.dispose]: release,
82
90
  };
83
91
  }
92
+ function canonicalLockParentSync(parent) {
93
+ // Match Root's async realpath, including Windows short-name expansion.
94
+ return process.platform === "win32" ? fs.realpathSync.native(parent) : fs.realpathSync(parent);
95
+ }
84
96
  function normalizeTargetPath(targetPath) {
85
97
  const resolved = path.resolve(targetPath);
86
98
  fs.mkdirSync(path.dirname(resolved), { recursive: true });
87
99
  try {
88
- return path.join(fs.realpathSync(path.dirname(resolved)), path.basename(resolved));
100
+ return path.join(canonicalLockParentSync(path.dirname(resolved)), path.basename(resolved));
89
101
  }
90
102
  catch {
91
103
  return resolved;
@@ -97,23 +109,19 @@ function boundedLockPath(lockPath, lockRoot) {
97
109
  return resolved;
98
110
  relativeSidecarLockPath(lockRoot, resolved);
99
111
  const parent = path.dirname(resolved);
100
- const parentReal = fs.realpathSync(parent);
112
+ const parentReal = canonicalLockParentSync(parent);
101
113
  const parentRelative = path.relative(lockRoot.rootReal, parentReal);
102
114
  if (parentRelative === ".." || parentRelative.startsWith(`..${path.sep}`) || path.isAbsolute(parentRelative)) {
103
115
  throw new FsSafeError("outside-workspace", "sidecar lock parent is outside lockRoot");
104
116
  }
105
117
  return path.join(parentReal, path.basename(resolved));
106
118
  }
107
- function defaultShouldReclaim(payload, lockPath, staleMs, nowMs) {
108
- const createdAtMs = sidecarLockPayloadCreatedAtMs(payload);
119
+ function defaultShouldReclaim(snapshot, staleMs, nowMs) {
120
+ const createdAtMs = sidecarLockPayloadCreatedAtMs(snapshot.payload);
109
121
  if (createdAtMs !== null)
110
122
  return nowMs - createdAtMs > staleMs;
111
- try {
112
- return nowMs - fs.statSync(lockPath).mtimeMs > staleMs;
113
- }
114
- catch {
115
- return true;
116
- }
123
+ // A cooperating holder can unlink after the snapshot; use its observed age.
124
+ return !snapshot.stat || nowMs - snapshot.stat.mtimeMs > staleMs;
117
125
  }
118
126
  function reclaimGuardExists(reclaimGuardPath) {
119
127
  try {
@@ -127,6 +135,11 @@ function reclaimGuardExists(reclaimGuardPath) {
127
135
  }
128
136
  }
129
137
  export function acquireFileLockSync(targetPath, options) {
138
+ const defaults = getFsSafeLockConfig();
139
+ const retry = options.retry ?? defaults.retry ?? {};
140
+ const timeoutMs = options.timeoutMs ?? defaults.timeoutMs;
141
+ validateSidecarLockRetryOptions(retry);
142
+ validateSidecarLockTimeoutMs(timeoutMs);
130
143
  const normalizedTargetPath = normalizeTargetPath(targetPath);
131
144
  const lockPath = boundedLockPath(options.lockPath ?? `${normalizedTargetPath}.lock`, options.lockRoot);
132
145
  const heldLocks = getSyncHeldLocks();
@@ -140,13 +153,11 @@ export function acquireFileLockSync(targetPath, options) {
140
153
  }
141
154
  // Process defaults fill the same fields here as withLockDefaults() fills for
142
155
  // the asynchronous manager, so both acquirers honor configureFsSafeLocks().
143
- const defaults = getFsSafeLockConfig();
144
156
  const staleMs = options.staleMs ?? defaults.staleMs ?? 30_000;
145
- const retry = options.retry ?? defaults.retry ?? {};
146
- const timeoutMs = options.timeoutMs ?? defaults.timeoutMs;
147
157
  const staleRecovery = options.staleRecovery ?? defaults.staleRecovery;
148
158
  const startedAt = Date.now();
149
159
  let attempt = 0;
160
+ let transientDenials = 0;
150
161
  const reclaimGuardPath = `${lockPath}.reclaim`;
151
162
  const waitForRetry = () => {
152
163
  const elapsed = Date.now() - startedAt;
@@ -158,22 +169,47 @@ export function acquireFileLockSync(targetPath, options) {
158
169
  normalizedTargetPath,
159
170
  });
160
171
  }
161
- sleepSync(computeSidecarLockDelayMs(retry, attempt));
172
+ const remaining = timeoutMs === undefined || timeoutMs === Number.POSITIVE_INFINITY
173
+ ? Number.POSITIVE_INFINITY
174
+ : Math.max(0, timeoutMs - elapsed);
175
+ sleepSync(Math.min(computeSidecarLockDelayMs(retry, attempt), remaining));
162
176
  attempt += 1;
163
177
  };
178
+ const retryLockFileDenial = (error) => {
179
+ if (!isTransientLockFileDenial(error, lockPath) ||
180
+ ++transientDenials > maxTransientLockDenials)
181
+ return false;
182
+ try {
183
+ waitForRetry();
184
+ }
185
+ catch (waitError) {
186
+ // Preserve the filesystem diagnosis when the caller's budget runs out.
187
+ if (waitError.code === "file_lock_timeout")
188
+ throw error;
189
+ throw waitError;
190
+ }
191
+ return true;
192
+ };
164
193
  while (true) {
165
194
  if (reclaimGuardExists(reclaimGuardPath)) {
166
195
  waitForRetry();
167
196
  continue;
168
197
  }
169
198
  let fd;
199
+ const payload = options.payload();
200
+ const { raw, ownershipToken } = serializeSidecarLockPayload(payload);
201
+ let lockFileCreateDenied = false;
170
202
  try {
171
- const payload = options.payload();
172
- const { raw, ownershipToken } = serializeSidecarLockPayload(payload);
173
203
  const noFollow = process.platform !== "win32" && typeof fs.constants.O_NOFOLLOW === "number"
174
204
  ? fs.constants.O_NOFOLLOW
175
205
  : 0;
176
- fd = fs.openSync(lockPath, fs.constants.O_WRONLY | fs.constants.O_CREAT | fs.constants.O_EXCL | noFollow, 0o600);
206
+ try {
207
+ fd = fs.openSync(lockPath, fs.constants.O_WRONLY | fs.constants.O_CREAT | fs.constants.O_EXCL | noFollow, 0o600);
208
+ }
209
+ catch (error) {
210
+ lockFileCreateDenied = isTransientLockFileDenial(error, lockPath);
211
+ throw error;
212
+ }
177
213
  fs.writeFileSync(fd, raw, "utf8");
178
214
  fs.fsyncSync(fd);
179
215
  const snapshot = {
@@ -221,17 +257,32 @@ export function acquireFileLockSync(targetPath, options) {
221
257
  fd = undefined;
222
258
  removeSidecarLockIfUnchangedSync(lockPath, failed);
223
259
  }
260
+ if (lockFileCreateDenied && retryLockFileDenial(error))
261
+ continue;
224
262
  if (error.code !== "EEXIST")
225
263
  throw error;
226
264
  if (heldLocks.has(normalizedTargetPath)) {
227
265
  waitForRetry();
228
266
  continue;
229
267
  }
230
- const snapshot = readSidecarLockSnapshotSync(lockPath, options.parsePayload, {
231
- rejectNonFile: true,
232
- });
233
- if (!snapshot)
268
+ let rawSnapshot;
269
+ let lockFileOpenDenied = false;
270
+ try {
271
+ rawSnapshot = readSidecarLockRawSnapshotSync(lockPath, {
272
+ rejectNonFile: true,
273
+ onOpenFailure: (error) => { lockFileOpenDenied = isTransientLockFileDenial(error, lockPath); },
274
+ });
275
+ }
276
+ catch (readError) {
277
+ if (lockFileOpenDenied && retryLockFileDenial(readError))
278
+ continue;
279
+ throw readError;
280
+ }
281
+ const snapshot = parseSidecarLockSnapshot(rawSnapshot, options.parsePayload);
282
+ if (!snapshot) {
283
+ waitForRetry();
234
284
  continue;
285
+ }
235
286
  const nowMs = Date.now();
236
287
  const reclaim = options.shouldReclaim
237
288
  ? options.shouldReclaim({
@@ -242,7 +293,7 @@ export function acquireFileLockSync(targetPath, options) {
242
293
  nowMs,
243
294
  heldByThisProcess: false,
244
295
  })
245
- : defaultShouldReclaim(snapshot.payload, lockPath, staleMs, nowMs);
296
+ : defaultShouldReclaim(snapshot, staleMs, nowMs);
246
297
  if (reclaim) {
247
298
  if (staleRecovery === "remove-if-unchanged" &&
248
299
  snapshot.raw !== undefined &&
@@ -0,0 +1,9 @@
1
+ type FailureKind = "identity" | "resolution" | `open:${string}` | `unlinked:${string}` | `changed:${string}`;
2
+ export declare function recordFileObservationFailure(error: unknown, kind: FailureKind): void;
3
+ export declare function isFileObservationFailure(error: unknown, kind: FailureKind): boolean;
4
+ export declare function fileObservation(): {
5
+ run<T>(operation: () => T): T;
6
+ has(error: unknown, kind: FailureKind): boolean;
7
+ };
8
+ export {};
9
+ //# sourceMappingURL=file-observation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-observation.d.ts","sourceRoot":"","sources":["../src/file-observation.ts"],"names":[],"mappings":"AAEA,KAAK,WAAW,GAAG,UAAU,GAAG,YAAY,GAAG,QAAQ,MAAM,EAAE,GAAG,YAAY,MAAM,EAAE,GAAG,WAAW,MAAM,EAAE,CAAC;AAG7G,wBAAgB,4BAA4B,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,WAAW,GAAG,IAAI,CAMpF;AAED,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,WAAW,GAAG,OAAO,CAEnF;AAGD,wBAAgB,eAAe;IAG3B,GAAG,CAAC,CAAC,aAAa,MAAM,CAAC,GAAG,CAAC;IAC7B,GAAG,QAAQ,OAAO,QAAQ,WAAW,GAAG,OAAO;EAElD"}
@@ -0,0 +1,22 @@
1
+ import { AsyncLocalStorage } from "node:async_hooks";
2
+ const active = new AsyncLocalStorage();
3
+ export function recordFileObservationFailure(error, kind) {
4
+ const failures = active.getStore();
5
+ if (!failures)
6
+ return;
7
+ let kinds = failures.get(error);
8
+ if (!kinds)
9
+ failures.set(error, kinds = new Set());
10
+ kinds.add(kind);
11
+ }
12
+ export function isFileObservationFailure(error, kind) {
13
+ return active.getStore()?.get(error)?.has(kind) === true;
14
+ }
15
+ // Each Root observation owns its receipts, including nested and concurrent reads.
16
+ export function fileObservation() {
17
+ const failures = new Map();
18
+ return {
19
+ run(operation) { return active.run(failures, operation); },
20
+ has(error, kind) { return failures.get(error)?.has(kind) === true; },
21
+ };
22
+ }
@@ -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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"}