@openclaw/fs-safe 0.10.0 → 0.12.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 (302) hide show
  1. package/CHANGELOG.md +114 -0
  2. package/LICENSE +1 -0
  3. package/README.md +39 -6
  4. package/dist/absolute-path.d.ts.map +1 -1
  5. package/dist/absolute-path.js +3 -2
  6. package/dist/advanced.d.ts +5 -1
  7. package/dist/advanced.d.ts.map +1 -1
  8. package/dist/advanced.js +5 -1
  9. package/dist/archive-durability.d.ts +6 -6
  10. package/dist/archive-durability.d.ts.map +1 -1
  11. package/dist/archive-durability.js +1 -1
  12. package/dist/archive-entry.d.ts.map +1 -1
  13. package/dist/archive-entry.js +8 -8
  14. package/dist/archive-gzip-tail.d.ts +1 -0
  15. package/dist/archive-gzip-tail.d.ts.map +1 -1
  16. package/dist/archive-gzip-tail.js +16 -9
  17. package/dist/archive-input.d.ts.map +1 -1
  18. package/dist/archive-input.js +4 -2
  19. package/dist/archive-merge.d.ts +5 -1
  20. package/dist/archive-merge.d.ts.map +1 -1
  21. package/dist/archive-merge.js +15 -12
  22. package/dist/archive-native.js +4 -4
  23. package/dist/archive-parser.wasm +0 -0
  24. package/dist/archive-read.d.ts.map +1 -1
  25. package/dist/archive-read.js +54 -42
  26. package/dist/archive-staging.d.ts +6 -3
  27. package/dist/archive-staging.d.ts.map +1 -1
  28. package/dist/archive-staging.js +42 -22
  29. package/dist/archive-tar-stream.d.ts.map +1 -1
  30. package/dist/archive-tar-stream.js +7 -6
  31. package/dist/archive-tar-wasm.d.ts.map +1 -1
  32. package/dist/archive-tar-wasm.js +16 -13
  33. package/dist/archive-zip-admission.d.ts +1 -1
  34. package/dist/archive-zip-admission.d.ts.map +1 -1
  35. package/dist/archive-zip-admission.js +48 -12
  36. package/dist/archive-zip-loader.d.ts +6 -0
  37. package/dist/archive-zip-loader.d.ts.map +1 -0
  38. package/dist/archive-zip-loader.js +38 -0
  39. package/dist/archive-zip-names.d.ts.map +1 -1
  40. package/dist/archive-zip-names.js +26 -9
  41. package/dist/archive-zip-preflight.d.ts +2 -3
  42. package/dist/archive-zip-preflight.d.ts.map +1 -1
  43. package/dist/archive-zip-preflight.js +2 -34
  44. package/dist/archive.d.ts.map +1 -1
  45. package/dist/archive.js +12 -10
  46. package/dist/bounded-read.d.ts +5 -0
  47. package/dist/bounded-read.d.ts.map +1 -1
  48. package/dist/bounded-read.js +18 -11
  49. package/dist/copy-file-input.d.ts +0 -1
  50. package/dist/copy-file-input.d.ts.map +1 -1
  51. package/dist/copy-file-input.js +4 -26
  52. package/dist/copy-tree-portable.d.ts.map +1 -1
  53. package/dist/copy-tree-portable.js +57 -26
  54. package/dist/copy.d.ts +1 -1
  55. package/dist/copy.d.ts.map +1 -1
  56. package/dist/copy.js +3 -1
  57. package/dist/device-path.d.ts.map +1 -1
  58. package/dist/device-path.js +5 -3
  59. package/dist/directory-durability.d.ts.map +1 -1
  60. package/dist/directory-durability.js +5 -4
  61. package/dist/directory-guard.d.ts +11 -1
  62. package/dist/directory-guard.d.ts.map +1 -1
  63. package/dist/directory-guard.js +53 -11
  64. package/dist/durability.d.ts +1 -1
  65. package/dist/durability.d.ts.map +1 -1
  66. package/dist/durability.js +1 -1
  67. package/dist/file-handle-transfer.d.ts +14 -0
  68. package/dist/file-handle-transfer.d.ts.map +1 -0
  69. package/dist/file-handle-transfer.js +64 -0
  70. package/dist/file-hash.d.ts +3 -0
  71. package/dist/file-hash.d.ts.map +1 -1
  72. package/dist/file-hash.js +99 -32
  73. package/dist/file-lock-sync.d.ts.map +1 -1
  74. package/dist/file-lock-sync.js +8 -4
  75. package/dist/file-store-boundary.d.ts.map +1 -1
  76. package/dist/file-store-boundary.js +7 -5
  77. package/dist/file-store-path.d.ts +3 -0
  78. package/dist/file-store-path.d.ts.map +1 -0
  79. package/dist/file-store-path.js +27 -0
  80. package/dist/file-store-prune.d.ts.map +1 -1
  81. package/dist/file-store-prune.js +15 -5
  82. package/dist/file-store-sync-write.d.ts.map +1 -1
  83. package/dist/file-store-sync-write.js +56 -44
  84. package/dist/file-store.d.ts.map +1 -1
  85. package/dist/file-store.js +2 -18
  86. package/dist/filename.d.ts +1 -0
  87. package/dist/filename.d.ts.map +1 -1
  88. package/dist/filename.js +32 -14
  89. package/dist/guarded-mkdir.d.ts +2 -0
  90. package/dist/guarded-mkdir.d.ts.map +1 -1
  91. package/dist/guarded-mkdir.js +52 -16
  92. package/dist/guest-dispatch-python.d.ts +2 -0
  93. package/dist/guest-dispatch-python.d.ts.map +1 -0
  94. package/dist/guest-dispatch-python.js +117 -0
  95. package/dist/guest-native-python.d.ts +4 -0
  96. package/dist/guest-native-python.d.ts.map +1 -0
  97. package/dist/guest-native-python.js +135 -0
  98. package/dist/guest.d.ts +9 -0
  99. package/dist/guest.d.ts.map +1 -0
  100. package/dist/guest.js +421 -0
  101. package/dist/index.d.ts +1 -1
  102. package/dist/index.d.ts.map +1 -1
  103. package/dist/install-path.d.ts +6 -0
  104. package/dist/install-path.d.ts.map +1 -1
  105. package/dist/install-path.js +16 -2
  106. package/dist/json-durable-queue-directory.js +3 -3
  107. package/dist/json-durable-queue-ownership.d.ts +2 -0
  108. package/dist/json-durable-queue-ownership.d.ts.map +1 -1
  109. package/dist/json-durable-queue-ownership.js +47 -3
  110. package/dist/json-durable-queue-read.d.ts +6 -0
  111. package/dist/json-durable-queue-read.d.ts.map +1 -0
  112. package/dist/json-durable-queue-read.js +59 -0
  113. package/dist/json-durable-queue.d.ts +1 -1
  114. package/dist/json-durable-queue.d.ts.map +1 -1
  115. package/dist/json-durable-queue.js +43 -84
  116. package/dist/json.d.ts.map +1 -1
  117. package/dist/json.js +2 -1
  118. package/dist/local-roots.d.ts.map +1 -1
  119. package/dist/local-roots.js +2 -1
  120. package/dist/move-path-stage.d.ts.map +1 -1
  121. package/dist/move-path-stage.js +2 -1
  122. package/dist/move-path.d.ts.map +1 -1
  123. package/dist/move-path.js +4 -3
  124. package/dist/mutation-authority.d.ts +1 -0
  125. package/dist/mutation-authority.d.ts.map +1 -1
  126. package/dist/mutation-authority.js +4 -4
  127. package/dist/native-binding.d.ts +14 -2
  128. package/dist/native-binding.d.ts.map +1 -1
  129. package/dist/native-pinned-write-windows.d.ts.map +1 -1
  130. package/dist/native-pinned-write-windows.js +3 -2
  131. package/dist/native-pinned-write.d.ts.map +1 -1
  132. package/dist/native-pinned-write.js +2 -1
  133. package/dist/opened-realpath.d.ts.map +1 -1
  134. package/dist/opened-realpath.js +5 -4
  135. package/dist/output.d.ts +2 -0
  136. package/dist/output.d.ts.map +1 -1
  137. package/dist/output.js +2 -0
  138. package/dist/overwrite-file-handle.d.ts +8 -0
  139. package/dist/overwrite-file-handle.d.ts.map +1 -0
  140. package/dist/overwrite-file-handle.js +42 -0
  141. package/dist/path-case.d.ts +7 -0
  142. package/dist/path-case.d.ts.map +1 -0
  143. package/dist/path-case.js +136 -0
  144. package/dist/path-scope-lexical.d.ts +14 -0
  145. package/dist/path-scope-lexical.d.ts.map +1 -0
  146. package/dist/path-scope-lexical.js +27 -0
  147. package/dist/path.d.ts.map +1 -1
  148. package/dist/path.js +22 -3
  149. package/dist/permissions-windows.d.ts +1 -1
  150. package/dist/permissions-windows.d.ts.map +1 -1
  151. package/dist/permissions-windows.js +48 -6
  152. package/dist/pinned-open.d.ts.map +1 -1
  153. package/dist/pinned-open.js +3 -1
  154. package/dist/pinned-write.d.ts +2 -2
  155. package/dist/pinned-write.d.ts.map +1 -1
  156. package/dist/pinned-write.js +3 -1
  157. package/dist/private-temp-workspace.d.ts.map +1 -1
  158. package/dist/private-temp-workspace.js +6 -4
  159. package/dist/realpath.d.ts +4 -0
  160. package/dist/realpath.d.ts.map +1 -0
  161. package/dist/realpath.js +43 -0
  162. package/dist/recursive-mkdir-path.d.ts +3 -0
  163. package/dist/recursive-mkdir-path.d.ts.map +1 -0
  164. package/dist/recursive-mkdir-path.js +8 -0
  165. package/dist/replace-directory.d.ts.map +1 -1
  166. package/dist/replace-directory.js +2 -1
  167. package/dist/replace-file-copy-fallback.d.ts.map +1 -1
  168. package/dist/replace-file-copy-fallback.js +23 -31
  169. package/dist/replace-file-copy-source.d.ts.map +1 -1
  170. package/dist/replace-file-copy-source.js +7 -12
  171. package/dist/replace-file-mode.d.ts +3 -0
  172. package/dist/replace-file-mode.d.ts.map +1 -0
  173. package/dist/replace-file-mode.js +10 -0
  174. package/dist/replace-file.d.ts +1 -0
  175. package/dist/replace-file.d.ts.map +1 -1
  176. package/dist/replace-file.js +14 -8
  177. package/dist/root-boundary.d.ts +25 -0
  178. package/dist/root-boundary.d.ts.map +1 -0
  179. package/dist/root-boundary.js +177 -0
  180. package/dist/root-context.d.ts.map +1 -1
  181. package/dist/root-context.js +40 -13
  182. package/dist/root-create-input.d.ts +10 -0
  183. package/dist/root-create-input.d.ts.map +1 -0
  184. package/dist/root-create-input.js +80 -0
  185. package/dist/root-directory-list.d.ts +3 -1
  186. package/dist/root-directory-list.d.ts.map +1 -1
  187. package/dist/root-directory-list.js +31 -5
  188. package/dist/root-entries.d.ts +11 -0
  189. package/dist/root-entries.d.ts.map +1 -0
  190. package/dist/root-entries.js +61 -0
  191. package/dist/root-errors.d.ts +5 -5
  192. package/dist/root-errors.d.ts.map +1 -1
  193. package/dist/root-errors.js +13 -12
  194. package/dist/root-impl.d.ts +13 -3
  195. package/dist/root-impl.d.ts.map +1 -1
  196. package/dist/root-impl.js +93 -70
  197. package/dist/root-move-preflight.d.ts +8 -0
  198. package/dist/root-move-preflight.d.ts.map +1 -0
  199. package/dist/root-move-preflight.js +16 -0
  200. package/dist/root-options.d.ts +12 -1
  201. package/dist/root-options.d.ts.map +1 -1
  202. package/dist/root-path-existing.d.ts +2 -0
  203. package/dist/root-path-existing.d.ts.map +1 -1
  204. package/dist/root-path-existing.js +14 -5
  205. package/dist/root-path-symlink.d.ts.map +1 -1
  206. package/dist/root-path-symlink.js +3 -2
  207. package/dist/root-path.d.ts +2 -0
  208. package/dist/root-path.d.ts.map +1 -1
  209. package/dist/root-path.js +54 -26
  210. package/dist/root-paths.d.ts +2 -6
  211. package/dist/root-paths.d.ts.map +1 -1
  212. package/dist/root-paths.js +24 -32
  213. package/dist/root-remove.d.ts +5 -0
  214. package/dist/root-remove.d.ts.map +1 -0
  215. package/dist/root-remove.js +286 -0
  216. package/dist/root-symlink-policy.d.ts +2 -1
  217. package/dist/root-symlink-policy.d.ts.map +1 -1
  218. package/dist/root-symlink-policy.js +2 -2
  219. package/dist/root-walk.d.ts.map +1 -1
  220. package/dist/root-walk.js +2 -1
  221. package/dist/root-write-mode.d.ts +2 -0
  222. package/dist/root-write-mode.d.ts.map +1 -1
  223. package/dist/root-write-mode.js +21 -7
  224. package/dist/root-write-verification.d.ts.map +1 -1
  225. package/dist/root-write-verification.js +12 -3
  226. package/dist/root.d.ts +2 -1
  227. package/dist/root.d.ts.map +1 -1
  228. package/dist/safe-path-segment.d.ts.map +1 -1
  229. package/dist/safe-path-segment.js +3 -1
  230. package/dist/secret-file.d.ts.map +1 -1
  231. package/dist/secret-file.js +2 -1
  232. package/dist/secret-read-async.d.ts.map +1 -1
  233. package/dist/secret-read-async.js +2 -1
  234. package/dist/secure-file-windows.d.ts +10 -0
  235. package/dist/secure-file-windows.d.ts.map +1 -0
  236. package/dist/secure-file-windows.js +186 -0
  237. package/dist/secure-file.d.ts.map +1 -1
  238. package/dist/secure-file.js +27 -7
  239. package/dist/secure-temp-dir.d.ts.map +1 -1
  240. package/dist/secure-temp-dir.js +2 -1
  241. package/dist/sibling-staged-file.d.ts +2 -0
  242. package/dist/sibling-staged-file.d.ts.map +1 -1
  243. package/dist/sibling-staged-file.js +49 -8
  244. package/dist/sibling-temp.d.ts +2 -0
  245. package/dist/sibling-temp.d.ts.map +1 -1
  246. package/dist/sibling-temp.js +8 -5
  247. package/dist/sidecar-lock-acquire.d.ts.map +1 -1
  248. package/dist/sidecar-lock-acquire.js +16 -5
  249. package/dist/sidecar-lock-policy.d.ts +2 -0
  250. package/dist/sidecar-lock-policy.d.ts.map +1 -1
  251. package/dist/sidecar-lock-policy.js +17 -0
  252. package/dist/sidecar-lock.d.ts.map +1 -1
  253. package/dist/sidecar-lock.js +2 -3
  254. package/dist/staged-directory.d.ts.map +1 -1
  255. package/dist/staged-directory.js +4 -3
  256. package/dist/temp-target.d.ts +14 -12
  257. package/dist/temp-target.d.ts.map +1 -1
  258. package/dist/temp-target.js +15 -7
  259. package/dist/trash.d.ts.map +1 -1
  260. package/dist/trash.js +7 -5
  261. package/dist/unicode-path.d.ts +3 -0
  262. package/dist/unicode-path.d.ts.map +1 -0
  263. package/dist/unicode-path.js +13 -0
  264. package/dist/walk.d.ts.map +1 -1
  265. package/dist/walk.js +14 -12
  266. package/dist/write-file-handle.d.ts +1 -0
  267. package/dist/write-file-handle.d.ts.map +1 -1
  268. package/dist/write-file-handle.js +3 -2
  269. package/docs/advanced.md +7 -1
  270. package/docs/archive.md +31 -5
  271. package/docs/atomic.md +17 -1
  272. package/docs/config.md +1 -0
  273. package/docs/contributing.md +33 -2
  274. package/docs/copy.md +75 -6
  275. package/docs/directory-identity.md +85 -0
  276. package/docs/durability.md +40 -3
  277. package/docs/entries.md +109 -0
  278. package/docs/errors.md +3 -3
  279. package/docs/file-store.md +21 -0
  280. package/docs/filename.md +9 -2
  281. package/docs/guest.md +146 -0
  282. package/docs/in-place-write.md +81 -0
  283. package/docs/index.md +2 -0
  284. package/docs/install-path.md +59 -13
  285. package/docs/install.md +34 -0
  286. package/docs/native-helper.md +14 -5
  287. package/docs/native.md +18 -2
  288. package/docs/output.md +32 -6
  289. package/docs/path-case.md +64 -0
  290. package/docs/path-scope.md +1 -1
  291. package/docs/path.md +1 -1
  292. package/docs/permissions.md +37 -3
  293. package/docs/public-api.md +36 -2
  294. package/docs/root.md +33 -3
  295. package/docs/secure-file.md +17 -14
  296. package/docs/security-model.md +1 -1
  297. package/docs/sidecar-lock.md +12 -3
  298. package/docs/store.md +22 -1
  299. package/docs/temp.md +39 -6
  300. package/docs/types.md +1 -1
  301. package/docs/writing.md +153 -3
  302. package/package.json +14 -8
@@ -1,10 +1,10 @@
1
- import fsSync from "node:fs";
2
1
  import path from "node:path";
3
2
  import { syncDirectory } from "./directory-durability.js";
4
3
  import { isPathRelativeEscape } from "./path.js";
4
+ import { realpathSync } from "./realpath.js";
5
5
  export async function syncQueueDirectoryCreation(dir, validationBase) {
6
- const baseReal = fsSync.realpathSync.native(validationBase);
7
- const targetReal = fsSync.realpathSync.native(dir);
6
+ const baseReal = realpathSync.native(validationBase);
7
+ const targetReal = realpathSync.native(dir);
8
8
  const relative = path.relative(baseReal, targetReal);
9
9
  if (isPathRelativeEscape(relative)) {
10
10
  throw new Error(`durable queue directory escapes validation base: ${dir}`);
@@ -1,3 +1,4 @@
1
+ import { type BigIntStats } from "node:fs";
1
2
  export type DurableQueueEntryPathsLike = {
2
3
  jsonPath: string;
3
4
  deliveredPath: string;
@@ -7,6 +8,7 @@ export declare function durableQueueProcessingPath(paths: DurableQueueEntryPaths
7
8
  export declare function claimDurableQueueEntry(paths: DurableQueueEntryPathsLike, options?: {
8
9
  skipUnowned?: boolean;
9
10
  }): Promise<string | null>;
11
+ export declare function migrateDurableQueueEntry<T>(paths: DurableQueueEntryPathsLike, expected: BigIntStats, releaseReadPin: () => Promise<void>, run: (filePath: string, beforePublish: () => Promise<void>) => Promise<T>): Promise<T>;
10
12
  export declare function completeDeliveredQueueEntry(paths: DurableQueueEntryPathsLike): Promise<boolean>;
11
13
  export declare function acknowledgeDurableQueueEntry(paths: DurableQueueEntryPathsLike): Promise<void>;
12
14
  export declare function moveDurableQueueEntryToFailed(params: {
@@ -1 +1 @@
1
- {"version":3,"file":"json-durable-queue-ownership.d.ts","sourceRoot":"","sources":["../src/json-durable-queue-ownership.ts"],"names":[],"mappings":"AAUA,MAAM,MAAM,0BAA0B,GAAG;IACvC,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB,CAAC;AAEF,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,0BAA0B,GAAG,MAAM,CAKpF;AA+DD,wBAAsB,sBAAsB,CAC1C,KAAK,EAAE,0BAA0B,EACjC,OAAO,GAAE;IAAE,WAAW,CAAC,EAAE,OAAO,CAAA;CAAO,GACtC,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;AAKD,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAI1D"}
1
+ {"version":3,"file":"json-durable-queue-ownership.d.ts","sourceRoot":"","sources":["../src/json-durable-queue-ownership.ts"],"names":[],"mappings":"AAEA,OAAe,EAAE,KAAK,WAAW,EAAE,MAAM,SAAS,CAAC;AASnD,MAAM,MAAM,0BAA0B,GAAG;IACvC,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB,CAAC;AAEF,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,0BAA0B,GAAG,MAAM,CAKpF;AAwED,wBAAsB,sBAAsB,CAC1C,KAAK,EAAE,0BAA0B,EACjC,OAAO,GAAE;IAAE,WAAW,CAAC,EAAE,OAAO,CAAA;CAAO,GACtC,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAGxB;AAGD,wBAAsB,wBAAwB,CAAC,CAAC,EAC9C,KAAK,EAAE,0BAA0B,EACjC,QAAQ,EAAE,WAAW,EACrB,cAAc,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,EACnC,GAAG,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,GACxE,OAAO,CAAC,CAAC,CAAC,CAkCZ;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;AAKD,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAI1D"}
@@ -6,6 +6,7 @@ import path from "node:path";
6
6
  import { syncDirectory } from "./directory-durability.js";
7
7
  import { FsSafeError } from "./errors.js";
8
8
  import { sameFileIdentityForCleanup, sha256Hex } from "./file-identity.js";
9
+ import { inspectFileIdentity } from "./strict-file-identity.js";
9
10
  import { serializePathWrite } from "./write-queue.js";
10
11
  export function durableQueueProcessingPath(paths) {
11
12
  if (paths.processingPath)
@@ -32,10 +33,17 @@ async function claimDurableQueueEntryUnlocked(paths, options = {}) {
32
33
  const existingProcessing = await regularQueueFileIdentity(processingPath);
33
34
  if (existingProcessing) {
34
35
  const pending = await lstatOrNull(paths.jsonPath);
35
- if (pending &&
36
+ const retiresPending = pending !== null &&
36
37
  !pending.isSymbolicLink() &&
37
38
  pending.isFile() &&
38
- sameFileIdentityForCleanup(pending, existingProcessing)) {
39
+ sameFileIdentityForCleanup(pending, existingProcessing);
40
+ if (!retiresPending ||
41
+ path.resolve(path.dirname(processingPath)) !== path.resolve(path.dirname(paths.jsonPath))) {
42
+ // An existing claim can contain a migration whose publication sync failed.
43
+ // Retirement already syncs this edge when both names share a parent.
44
+ await syncDirectory(path.dirname(processingPath));
45
+ }
46
+ if (retiresPending) {
39
47
  await retireDurableQueueSource({ jsonPath: paths.jsonPath, processingPath });
40
48
  }
41
49
  return processingPath;
@@ -54,7 +62,10 @@ async function claimDurableQueueEntryUnlocked(paths, options = {}) {
54
62
  }
55
63
  catch (error) {
56
64
  if (getErrorCode(error) === "EEXIST") {
57
- return (await regularQueueFileIdentity(processingPath)) ? processingPath : null;
65
+ if (!(await regularQueueFileIdentity(processingPath)))
66
+ return null;
67
+ await syncDirectory(path.dirname(processingPath));
68
+ return processingPath;
58
69
  }
59
70
  if (getErrorCode(error) === "ENOENT")
60
71
  return null;
@@ -73,6 +84,39 @@ export async function claimDurableQueueEntry(paths, options = {}) {
73
84
  return null;
74
85
  return await withQueueEntryLock(paths, async () => await claimDurableQueueEntryUnlocked(paths, options));
75
86
  }
87
+ // The caller retains its read pin until this owner releases it or migration settles.
88
+ export async function migrateDurableQueueEntry(paths, expected, releaseReadPin, run) {
89
+ return await withQueueEntryLock(paths, async () => {
90
+ const processingPath = durableQueueProcessingPath(paths);
91
+ const assertCurrent = async () => {
92
+ try {
93
+ await inspectFileIdentity(() => {
94
+ const current = fsSync.lstatSync(processingPath, { bigint: true });
95
+ if (current.isSymbolicLink() || !current.isFile() || current.nlink !== 1n) {
96
+ throw new FsSafeError("path-mismatch", "queue migration requires its original processing claim");
97
+ }
98
+ return current;
99
+ }, expected);
100
+ }
101
+ catch (error) {
102
+ if (getErrorCode(error) === "ENOENT") {
103
+ throw new FsSafeError("path-mismatch", "queue processing claim disappeared before migration", { cause: error });
104
+ }
105
+ throw error;
106
+ }
107
+ };
108
+ await assertCurrent();
109
+ return await run(processingPath, async () => {
110
+ await assertCurrent();
111
+ if (process.platform === "win32") {
112
+ // Windows cannot replace our still-open target. Keep the transfer lock
113
+ // across release and reject changes made while the async close settles.
114
+ await releaseReadPin();
115
+ await assertCurrent();
116
+ }
117
+ });
118
+ });
119
+ }
76
120
  export async function completeDeliveredQueueEntry(paths) {
77
121
  if (!(await lstatOrNull(path.dirname(paths.deliveredPath))))
78
122
  return false;
@@ -0,0 +1,6 @@
1
+ import { type BigIntStats } from "node:fs";
2
+ export declare const DEFAULT_JSON_DURABLE_QUEUE_ENTRY_MAX_BYTES: number;
3
+ export declare function withJsonDurableQueueEntry<T, R>(filePath: string, options: {
4
+ maxBytes?: number;
5
+ }, run: (entry: T, identity: BigIntStats, releaseReadPin: () => Promise<void>) => Promise<R>): Promise<R>;
6
+ //# sourceMappingURL=json-durable-queue-read.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"json-durable-queue-read.d.ts","sourceRoot":"","sources":["../src/json-durable-queue-read.ts"],"names":[],"mappings":"AAAA,OAAW,EAAE,KAAK,WAAW,EAAE,MAAM,SAAS,CAAC;AAM/C,eAAO,MAAM,0CAA0C,QAAmB,CAAC;AA8B3E,wBAAsB,yBAAyB,CAAC,CAAC,EAAE,CAAC,EAClD,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,EAC9B,GAAG,EAAE,CAAC,KAAK,EAAE,CAAC,EAAE,QAAQ,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,GACxF,OAAO,CAAC,CAAC,CAAC,CA8BZ"}
@@ -0,0 +1,59 @@
1
+ import fs, {} from "node:fs";
2
+ import { normalizeMaxBytes } from "./byte-budget.js";
3
+ import { readBoundedAsync } from "./bounded-read.js";
4
+ import { resolveReadOpenFlags } from "./read-open-flags.js";
5
+ import { inspectFileIdentity } from "./strict-file-identity.js";
6
+ export const DEFAULT_JSON_DURABLE_QUEUE_ENTRY_MAX_BYTES = 16 * 1024 * 1024;
7
+ async function inspectQueueEntry(inspect, maxBytes, expected) {
8
+ let inspectionFailed = false;
9
+ try {
10
+ return await inspectFileIdentity(async () => {
11
+ try {
12
+ const stat = inspect();
13
+ if (stat.isSymbolicLink() || !stat.isFile()) {
14
+ throw new Error("queue entry is not a regular file");
15
+ }
16
+ if (stat.nlink > 1n)
17
+ throw new Error("queue entry hardlinks are not allowed");
18
+ if (stat.size > maxBytes)
19
+ throw new Error(`queue entry exceeds ${maxBytes} bytes`);
20
+ return stat;
21
+ }
22
+ catch (error) {
23
+ inspectionFailed = true;
24
+ throw error;
25
+ }
26
+ }, expected);
27
+ }
28
+ catch (error) {
29
+ // Translate only the identity helper's rejection, never inspection errors.
30
+ if (inspectionFailed)
31
+ throw error;
32
+ throw new Error("queue entry changed during read", { cause: error });
33
+ }
34
+ }
35
+ export async function withJsonDurableQueueEntry(filePath, options, run) {
36
+ const maxBytes = normalizeMaxBytes(options.maxBytes, {
37
+ defaultValue: DEFAULT_JSON_DURABLE_QUEUE_ENTRY_MAX_BYTES,
38
+ });
39
+ const inspectPath = () => fs.lstatSync(filePath, { bigint: true });
40
+ const initialStat = await inspectQueueEntry(inspectPath, maxBytes);
41
+ const handle = await fs.promises.open(filePath, resolveReadOpenFlags());
42
+ let closePromise;
43
+ const releaseReadPin = () => closePromise ??= (async () => { await handle.close(); })();
44
+ try {
45
+ const openedStat = await inspectQueueEntry(() => fs.fstatSync(handle.fd, { bigint: true }), maxBytes, initialStat);
46
+ await inspectQueueEntry(inspectPath, maxBytes, openedStat);
47
+ const bytes = await readBoundedAsync(maxBytes, async (buffer, length) => (await handle.read(buffer, 0, length, null)).bytesRead, {
48
+ initialSize: openedStat.size >= 0n && Number.isSafeInteger(Number(openedStat.size))
49
+ ? Number(openedStat.size) : undefined,
50
+ createLimitError: () => new Error(`queue entry exceeds ${maxBytes} bytes`),
51
+ });
52
+ // The migration owner may release this pin only at the verified publication
53
+ // boundary; until then an unlinked inode cannot be recycled into a new claim.
54
+ return await run(JSON.parse(bytes.toString("utf8")), openedStat, releaseReadPin);
55
+ }
56
+ finally {
57
+ await releaseReadPin();
58
+ }
59
+ }
@@ -1,3 +1,4 @@
1
+ export { DEFAULT_JSON_DURABLE_QUEUE_ENTRY_MAX_BYTES } from "./json-durable-queue-read.js";
1
2
  export type JsonDurableQueueEntryPaths = {
2
3
  jsonPath: string;
3
4
  deliveredPath: string;
@@ -14,7 +15,6 @@ export type JsonDurableQueueLoadOptions<T> = {
14
15
  cleanupTmpMaxAgeMs?: number;
15
16
  maxBytes?: number;
16
17
  };
17
- export declare const DEFAULT_JSON_DURABLE_QUEUE_ENTRY_MAX_BYTES: number;
18
18
  export declare function unlinkBestEffort(filePath: string): Promise<void>;
19
19
  export declare function jsonDurableQueueEntryExists(filePath: string): Promise<boolean>;
20
20
  export declare function resolveJsonDurableQueueEntryPaths(queueDir: string, id: string): JsonDurableQueueEntryPaths;
@@ -1 +1 @@
1
- {"version":3,"file":"json-durable-queue.d.ts","sourceRoot":"","sources":["../src/json-durable-queue.ts"],"names":[],"mappings":"AAoBA,MAAM,MAAM,0BAA0B,GAAG;IACvC,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB,CAAC;AAEF,MAAM,MAAM,0BAA0B,CAAC,CAAC,IAAI;IAC1C,KAAK,EAAE,CAAC,CAAC;IACT,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,2BAA2B,CAAC,CAAC,IAAI;IAC3C,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC,EAAE,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,0BAA0B,CAAC,CAAC,CAAC,CAAC,CAAC;IAC9E,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,CAAC;AAOF,eAAO,MAAM,0CAA0C,QAAmB,CAAC;AAM3E,wBAAsB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAEtE;AAED,wBAAsB,2BAA2B,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAUpF;AAmBD,wBAAgB,iCAAiC,CAC/C,QAAQ,EAAE,MAAM,EAChB,EAAE,EAAE,MAAM,GACT,0BAA0B,CAO5B;AAED,wBAAsB,0BAA0B,CAAC,MAAM,EAAE;IACvD,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;CACnB,GAAG,OAAO,CAAC,IAAI,CAAC,CAIhB;AAiLD,wBAAsB,0BAA0B,CAAC,MAAM,EAAE;IACvD,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,OAAO,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;CACpB,GAAG,OAAO,CAAC,IAAI,CAAC,CAQhB;AAyDD,wBAAsB,yBAAyB,CAAC,CAAC,EAC/C,QAAQ,EAAE,MAAM,EAChB,OAAO,GAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAO,GAClC,OAAO,CAAC,CAAC,CAAC,CASZ;AAED,wBAAsB,wBAAwB,CAAC,KAAK,EAAE,0BAA0B,GAAG,OAAO,CAAC,IAAI,CAAC,CAE/F;AAED,wBAAsB,yBAAyB,CAAC,CAAC,EAAE,MAAM,EAAE;IACzD,KAAK,EAAE,0BAA0B,CAAC;IAClC,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC,EAAE,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,0BAA0B,CAAC,CAAC,CAAC,CAAC,CAAC;IAC9E,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,GAAG,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC,CAsBpB;AAED,wBAAsB,kCAAkC,CAAC,CAAC,EACxD,OAAO,EAAE,2BAA2B,CAAC,CAAC,CAAC,GACtC,OAAO,CAAC,CAAC,EAAE,CAAC,CA+Dd;AAED,wBAAsB,iCAAiC,CAAC,MAAM,EAAE;IAC9D,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,EAAE,EAAE,MAAM,CAAC;CACZ,GAAG,OAAO,CAAC,IAAI,CAAC,CAShB"}
1
+ {"version":3,"file":"json-durable-queue.d.ts","sourceRoot":"","sources":["../src/json-durable-queue.ts"],"names":[],"mappings":"AAoBA,OAAO,EAAE,0CAA0C,EAAE,MAAM,8BAA8B,CAAC;AAE1F,MAAM,MAAM,0BAA0B,GAAG;IACvC,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB,CAAC;AAEF,MAAM,MAAM,0BAA0B,CAAC,CAAC,IAAI;IAC1C,KAAK,EAAE,CAAC,CAAC;IACT,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,2BAA2B,CAAC,CAAC,IAAI;IAC3C,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC,EAAE,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,0BAA0B,CAAC,CAAC,CAAC,CAAC,CAAC;IAC9E,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,CAAC;AAWF,wBAAsB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAEtE;AAED,wBAAsB,2BAA2B,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAUpF;AAmBD,wBAAgB,iCAAiC,CAC/C,QAAQ,EAAE,MAAM,EAChB,EAAE,EAAE,MAAM,GACT,0BAA0B,CAO5B;AAED,wBAAsB,0BAA0B,CAAC,MAAM,EAAE;IACvD,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;CACnB,GAAG,OAAO,CAAC,IAAI,CAAC,CAIhB;AAiLD,wBAAsB,0BAA0B,CAAC,MAAM,EAAE;IACvD,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,OAAO,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;CACpB,GAAG,OAAO,CAAC,IAAI,CAAC,CAEhB;AAgBD,wBAAsB,yBAAyB,CAAC,CAAC,EAC/C,QAAQ,EAAE,MAAM,EAChB,OAAO,GAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAO,GAClC,OAAO,CAAC,CAAC,CAAC,CAEZ;AAgBD,wBAAsB,wBAAwB,CAAC,KAAK,EAAE,0BAA0B,GAAG,OAAO,CAAC,IAAI,CAAC,CAE/F;AAED,wBAAsB,yBAAyB,CAAC,CAAC,EAAE,MAAM,EAAE;IACzD,KAAK,EAAE,0BAA0B,CAAC;IAClC,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC,EAAE,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,0BAA0B,CAAC,CAAC,CAAC,CAAC,CAAC;IAC9E,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,GAAG,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC,CAqBpB;AAED,wBAAsB,kCAAkC,CAAC,CAAC,EACxD,OAAO,EAAE,2BAA2B,CAAC,CAAC,CAAC,GACtC,OAAO,CAAC,CAAC,EAAE,CAAC,CAqEd;AAED,wBAAsB,iCAAiC,CAAC,MAAM,EAAE;IAC9D,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,EAAE,EAAE,MAAM,CAAC;CACZ,GAAG,OAAO,CAAC,IAAI,CAAC,CAShB"}
@@ -1,16 +1,15 @@
1
1
  import fs, {} from "node:fs";
2
2
  import path from "node:path";
3
- import { normalizeMaxBytes } from "./byte-budget.js";
4
- import { readBoundedAsync } from "./bounded-read.js";
5
3
  import { syncDirectory } from "./directory-durability.js";
6
4
  import { syncQueueDirectoryCreation } from "./json-durable-queue-directory.js";
7
- import { getErrorCode, acknowledgeDurableQueueEntry, claimDurableQueueEntry, completeDeliveredQueueEntry, moveDurableQueueEntryToFailed, } from "./json-durable-queue-ownership.js";
5
+ import { getErrorCode, acknowledgeDurableQueueEntry, claimDurableQueueEntry, completeDeliveredQueueEntry, migrateDurableQueueEntry, moveDurableQueueEntryToFailed, } from "./json-durable-queue-ownership.js";
6
+ import { withJsonDurableQueueEntry } from "./json-durable-queue-read.js";
8
7
  import { stringifyJsonDocument } from "./json-stringify.js";
9
- import { resolveReadOpenFlags } from "./read-open-flags.js";
8
+ import { realpathSync } from "./realpath.js";
9
+ import { recursiveMkdirPath } from "./recursive-mkdir-path.js";
10
10
  import { replaceFileAtomicWithDirectorySync } from "./replace-file.js";
11
11
  import { assertSafePathSegment } from "./safe-path-segment.js";
12
- import { inspectFileIdentity } from "./strict-file-identity.js";
13
- export const DEFAULT_JSON_DURABLE_QUEUE_ENTRY_MAX_BYTES = 16 * 1024 * 1024;
12
+ export { DEFAULT_JSON_DURABLE_QUEUE_ENTRY_MAX_BYTES } from "./json-durable-queue-read.js";
14
13
  function assertSafeQueueEntryId(id) {
15
14
  assertSafePathSegment(id, { label: "queue entry id" });
16
15
  }
@@ -60,7 +59,7 @@ async function ensureJsonDurableQueueDir(dir, validationRoot) {
60
59
  ? validationRoot
61
60
  : queueValidationRoot(dir);
62
61
  await assertNoSymlinkDirectorySegments(root, dir, true);
63
- await fs.promises.mkdir(dir, { recursive: true, mode: 0o700 });
62
+ await fs.promises.mkdir(recursiveMkdirPath(dir), { recursive: true, mode: 0o700 });
64
63
  await assertNoSymlinkDirectorySegments(root, dir, false);
65
64
  await chmodQueueDirectory(dir);
66
65
  await syncQueueDirectoryCreation(dir, root.path);
@@ -115,7 +114,7 @@ async function isDarwinSystemAlias(dir, stat) {
115
114
  return false;
116
115
  }
117
116
  try {
118
- return fs.realpathSync.native(resolved) === `/private${resolved}`;
117
+ return realpathSync.native(resolved) === `/private${resolved}`;
119
118
  }
120
119
  catch {
121
120
  return false;
@@ -131,7 +130,7 @@ async function assertNoSymlinkDirectorySegments(validationRoot, dir, allowMissin
131
130
  if (relative === ".." || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative)) {
132
131
  throw new Error(`durable queue path is not a directory: ${dir}`);
133
132
  }
134
- base = fs.realpathSync.native(base);
133
+ base = realpathSync.native(base);
135
134
  target = path.join(base, ...relative.split(path.sep).filter(Boolean));
136
135
  current = base;
137
136
  baseStat = fs.lstatSync(base);
@@ -159,7 +158,7 @@ async function assertNoSymlinkDirectorySegments(validationRoot, dir, allowMissin
159
158
  if (stat.isSymbolicLink() || !stat.isDirectory()) {
160
159
  if (stat.isSymbolicLink() && validationRoot.allowSymlinkBase) {
161
160
  if (await isDarwinSystemAlias(current, stat)) {
162
- current = fs.realpathSync.native(current);
161
+ current = realpathSync.native(current);
163
162
  continue;
164
163
  }
165
164
  }
@@ -211,67 +210,25 @@ async function chmodQueueDirectory(dir) {
211
210
  }
212
211
  }
213
212
  export async function writeJsonDurableQueueEntry(params) {
213
+ await writeQueueEntry(params);
214
+ }
215
+ async function writeQueueEntry(params, beforeRename) {
214
216
  await replaceFileAtomicWithDirectorySync({
215
217
  filePath: params.filePath,
216
218
  content: stringifyJsonDocument(params.entry, null, 2),
217
219
  mode: 0o600,
218
220
  tempPrefix: params.tempPrefix,
219
221
  syncTempFile: true,
222
+ beforeRename,
220
223
  }, syncDirectory);
221
224
  }
222
- async function inspectQueueEntry(inspect, maxBytes, expected) {
223
- let inspectionFailed = false;
224
- try {
225
- return await inspectFileIdentity(async () => {
226
- try {
227
- const stat = inspect();
228
- if (stat.isSymbolicLink() || !stat.isFile()) {
229
- throw new Error("queue entry is not a regular file");
230
- }
231
- if (stat.nlink > 1n)
232
- throw new Error("queue entry hardlinks are not allowed");
233
- if (stat.size > maxBytes)
234
- throw new Error(`queue entry exceeds ${maxBytes} bytes`);
235
- return stat;
236
- }
237
- catch (error) {
238
- inspectionFailed = true;
239
- throw error;
240
- }
241
- }, expected);
242
- }
243
- catch (error) {
244
- // Translate only the identity helper's rejection, never inspection errors.
245
- if (inspectionFailed)
246
- throw error;
247
- throw new Error("queue entry changed during read", { cause: error });
248
- }
249
- }
250
- async function readBoundedUtf8File(params) {
251
- const inspectPath = () => fs.lstatSync(params.filePath, { bigint: true });
252
- const initialStat = await inspectQueueEntry(inspectPath, params.maxBytes);
253
- const handle = await fs.promises.open(params.filePath, resolveReadOpenFlags());
254
- try {
255
- const openedStat = await inspectQueueEntry(() => fs.fstatSync(handle.fd, { bigint: true }), params.maxBytes, initialStat);
256
- await inspectQueueEntry(inspectPath, params.maxBytes, openedStat);
257
- const bytes = await readBoundedAsync(params.maxBytes, async (buffer, length) => (await handle.read(buffer, 0, length, null)).bytesRead, {
258
- initialSize: openedStat.size >= 0n && Number.isSafeInteger(Number(openedStat.size))
259
- ? Number(openedStat.size) : undefined,
260
- createLimitError: () => new Error(`queue entry exceeds ${params.maxBytes} bytes`),
261
- });
262
- return bytes.toString("utf8");
263
- }
264
- finally {
265
- await handle.close();
266
- }
267
- }
268
225
  export async function readJsonDurableQueueEntry(filePath, options = {}) {
269
- return JSON.parse(await readBoundedUtf8File({
270
- filePath,
271
- maxBytes: normalizeMaxBytes(options.maxBytes, {
272
- defaultValue: DEFAULT_JSON_DURABLE_QUEUE_ENTRY_MAX_BYTES,
273
- }),
274
- }));
226
+ return await withJsonDurableQueueEntry(filePath, options, async (entry) => entry);
227
+ }
228
+ async function migrateClaimedQueueEntry(params) {
229
+ await migrateDurableQueueEntry(params.paths, params.identity, params.releaseReadPin, async (filePath, beforePublish) => {
230
+ await writeQueueEntry({ filePath, entry: params.entry, tempPrefix: params.tempPrefix }, beforePublish);
231
+ });
275
232
  }
276
233
  export async function ackJsonDurableQueueEntry(paths) {
277
234
  await acknowledgeDurableQueueEntry(paths);
@@ -281,18 +238,17 @@ export async function loadJsonDurableQueueEntry(params) {
281
238
  const claimedPath = await claimDurableQueueEntry(params.paths);
282
239
  if (!claimedPath)
283
240
  return null;
284
- const raw = await readJsonDurableQueueEntry(claimedPath, {
241
+ return await withJsonDurableQueueEntry(claimedPath, {
285
242
  maxBytes: params.maxBytes,
243
+ }, async (raw, identity, releaseReadPin) => {
244
+ const result = params.read ? await params.read(raw, params.paths.jsonPath) : { entry: raw };
245
+ if (result.migrated) {
246
+ await migrateClaimedQueueEntry({
247
+ paths: params.paths, identity, releaseReadPin, entry: result.entry, tempPrefix: params.tempPrefix,
248
+ });
249
+ }
250
+ return result.entry;
286
251
  });
287
- const result = params.read ? await params.read(raw, params.paths.jsonPath) : { entry: raw };
288
- if (result.migrated) {
289
- await writeJsonDurableQueueEntry({
290
- filePath: claimedPath,
291
- entry: result.entry,
292
- tempPrefix: params.tempPrefix,
293
- });
294
- }
295
- return result.entry;
296
252
  }
297
253
  catch (error) {
298
254
  if (getErrorCode(error) === "ENOENT") {
@@ -356,22 +312,25 @@ export async function loadPendingJsonDurableQueueEntries(options) {
356
312
  const claimedPath = await claimDurableQueueEntry(paths, { skipUnowned: true });
357
313
  if (!claimedPath)
358
314
  continue;
359
- let result;
315
+ let migrationStarted = false;
360
316
  try {
361
- const raw = await readJsonDurableQueueEntry(claimedPath, { maxBytes: options.maxBytes });
362
- result = options.read ? await options.read(raw, paths.jsonPath) : { entry: raw };
317
+ const entry = await withJsonDurableQueueEntry(claimedPath, { maxBytes: options.maxBytes }, async (raw, identity, releaseReadPin) => {
318
+ const result = options.read ? await options.read(raw, paths.jsonPath) : { entry: raw };
319
+ if (result.migrated) {
320
+ migrationStarted = true;
321
+ await migrateClaimedQueueEntry({
322
+ paths, identity, releaseReadPin, entry: result.entry, tempPrefix: options.tempPrefix,
323
+ });
324
+ }
325
+ return result.entry;
326
+ });
327
+ entries.push(entry);
363
328
  }
364
- catch {
329
+ catch (error) {
330
+ if (migrationStarted)
331
+ throw error;
365
332
  continue;
366
333
  }
367
- if (result.migrated) {
368
- await writeJsonDurableQueueEntry({
369
- filePath: claimedPath,
370
- entry: result.entry,
371
- tempPrefix: options.tempPrefix,
372
- });
373
- }
374
- entries.push(result.entry);
375
334
  }
376
335
  return entries;
377
336
  }
@@ -1 +1 @@
1
- {"version":3,"file":"json.d.ts","sourceRoot":"","sources":["../src/json.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"json.d.ts","sourceRoot":"","sources":["../src/json.ts"],"names":[],"mappings":"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"}
package/dist/json.js CHANGED
@@ -8,6 +8,7 @@ import { stringifyJsonDocument } from "./json-stringify.js";
8
8
  import { readRegularFile, readRegularFileSync, statRegularFile } from "./regular-file.js";
9
9
  import { openRootFileSync } from "./root-file.js";
10
10
  import { resolveReadOpenFlags } from "./read-open-flags.js";
11
+ import { recursiveMkdirPath } from "./recursive-mkdir-path.js";
11
12
  import { writeTextAtomic } from "./text-atomic.js";
12
13
  import { sleep } from "./timing.js";
13
14
  const READ_RETRY_MAX_ATTEMPTS = 5;
@@ -169,7 +170,7 @@ export function writeJsonSync(pathname, data) {
169
170
  // Keep literal parent segments so staging follows the same symlinks as the target.
170
171
  const tmpPath = path.format({ ...path.parse(pathname), base: `.fs-safe-${randomUUID()}.tmp` });
171
172
  const payload = `${stringifyJsonDocument(data, null, 2)}\n`;
172
- fsSync.mkdirSync(path.dirname(pathname), { recursive: true, mode: JSON_DIR_MODE });
173
+ fsSync.mkdirSync(recursiveMkdirPath(path.dirname(pathname)), { recursive: true, mode: JSON_DIR_MODE });
173
174
  try {
174
175
  const tempIdentity = writeTempJsonFile(tmpPath, payload);
175
176
  trySetSecureMode(tmpPath, tempIdentity);
@@ -1 +1 @@
1
- {"version":3,"file":"local-roots.d.ts","sourceRoot":"","sources":["../src/local-roots.ts"],"names":[],"mappings":"AAOA,OAAO,EAAQ,KAAK,cAAc,EAAE,KAAK,UAAU,EAAE,KAAK,aAAa,EAAE,MAAM,WAAW,CAAC;AAE3F,MAAM,MAAM,oBAAoB,GAAG;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG,UAAU,GAAG;IAC9C,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,sBAAsB,GAAG;IACnC,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;IACzB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,oCAAoC,GAAG,sBAAsB,GAAG;IAC1E,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,6BAA6B,GAAG,sBAAsB,GAAG;IACnE,SAAS,CAAC,EAAE,cAAc,CAAC;IAC3B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,EAAE,aAAa,CAAC;CAC1B,CAAC;AAsDF,wBAAgB,6BAA6B,CAC3C,OAAO,EAAE,oCAAoC,GAC5C,oBAAoB,GAAG,IAAI,CAqC7B;AAED,wBAAsB,sBAAsB,CAC1C,OAAO,EAAE,6BAA6B,GACrC,OAAO,CAAC,oBAAoB,GAAG,IAAI,CAAC,CAiCtC"}
1
+ {"version":3,"file":"local-roots.d.ts","sourceRoot":"","sources":["../src/local-roots.ts"],"names":[],"mappings":"AAQA,OAAO,EAAQ,KAAK,cAAc,EAAE,KAAK,UAAU,EAAE,KAAK,aAAa,EAAE,MAAM,WAAW,CAAC;AAE3F,MAAM,MAAM,oBAAoB,GAAG;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG,UAAU,GAAG;IAC9C,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,sBAAsB,GAAG;IACnC,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;IACzB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,oCAAoC,GAAG,sBAAsB,GAAG;IAC1E,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,6BAA6B,GAAG,sBAAsB,GAAG;IACnE,SAAS,CAAC,EAAE,cAAc,CAAC;IAC3B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,EAAE,aAAa,CAAC;CAC1B,CAAC;AAsDF,wBAAgB,6BAA6B,CAC3C,OAAO,EAAE,oCAAoC,GAC5C,oBAAoB,GAAG,IAAI,CAqC7B;AAED,wBAAsB,sBAAsB,CAC1C,OAAO,EAAE,6BAA6B,GACrC,OAAO,CAAC,oBAAoB,GAAG,IAAI,CAAC,CAiCtC"}
@@ -4,6 +4,7 @@ import { normalizeMaxBytes } from "./byte-budget.js";
4
4
  import { FsSafeError } from "./errors.js";
5
5
  import { expandHomePrefix, resolveRequiredHomeDir } from "./home-dir.js";
6
6
  import { isFileUrl, safeFileURLToPath } from "./local-file-access.js";
7
+ import { realpathSync } from "./realpath.js";
7
8
  import { ROOT_PATH_ALIAS_POLICIES, resolveRootPathSync } from "./root-path.js";
8
9
  import { root } from "./root.js";
9
10
  function resolveLocalPathInput(input, label) {
@@ -52,7 +53,7 @@ function resolveRootRealSync(rootDir) {
52
53
  if (!stat.isDirectory()) {
53
54
  return null;
54
55
  }
55
- return fsSync.realpathSync(rootDir);
56
+ return realpathSync(rootDir);
56
57
  }
57
58
  catch {
58
59
  return null;
@@ -1 +1 @@
1
- {"version":3,"file":"move-path-stage.d.ts","sourceRoot":"","sources":["../src/move-path-stage.ts"],"names":[],"mappings":"AAAA,OAAe,EAAE,KAAK,WAAW,EAAE,MAAM,SAAS,CAAC;AAQnD,wBAAsB,oBAAoB,CAAC,MAAM,EAAE,MAAM;oBAkBrC,WAAW,GAAG,IAAI;yBAPV,IAAI;iBAkBf,IAAI;eACA,OAAO,CAAC,IAAI,CAAC;GAajC"}
1
+ {"version":3,"file":"move-path-stage.d.ts","sourceRoot":"","sources":["../src/move-path-stage.ts"],"names":[],"mappings":"AAAA,OAAe,EAAE,KAAK,WAAW,EAAE,MAAM,SAAS,CAAC;AASnD,wBAAsB,oBAAoB,CAAC,MAAM,EAAE,MAAM;oBAkBrC,WAAW,GAAG,IAAI;yBAPV,IAAI;iBAkBf,IAAI;eACA,OAAO,CAAC,IAAI,CAAC;GAajC"}
@@ -5,6 +5,7 @@ import { FsSafeError } from "./errors.js";
5
5
  import { guardedRm, guardedRmSync } from "./guarded-mutation.js";
6
6
  import { sameFileIdentityForCleanup } from "./file-identity.js";
7
7
  import { registerTempPathForExit } from "./temp-cleanup.js";
8
+ import { realpathSync } from "./realpath.js";
8
9
  export async function createMoveStageOwner(staged) {
9
10
  const parent = await createAsyncDirectoryGuard(path.dirname(staged), { bigint: true });
10
11
  let identity;
@@ -14,7 +15,7 @@ export async function createMoveStageOwner(staged) {
14
15
  const current = fsSync.lstatSync(parent.dir, { bigint: true });
15
16
  if (!current.isDirectory() || current.isSymbolicLink() ||
16
17
  !sameFileIdentityForCleanup(current, parent.stat) ||
17
- fsSync.realpathSync.native(parent.dir) !== parent.realPath)
18
+ realpathSync.native(parent.dir) !== parent.realPath)
18
19
  throw changed();
19
20
  };
20
21
  const assertCurrent = () => {
@@ -1 +1 @@
1
- {"version":3,"file":"move-path.d.ts","sourceRoot":"","sources":["../src/move-path.ts"],"names":[],"mappings":"AAsBA,MAAM,MAAM,0BAA0B,GAAG,QAAQ,CAAC;IAChD,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;CACb,CAAC,CAAC;AAEH,MAAM,MAAM,+BAA+B,GAAG;IAC5C,qFAAqF;IACrF,oBAAoB,CAAC,EAAE,MAAM,IAAI,CAAC;IAClC,uFAAuF;IACvF,kBAAkB,CAAC,EAAE,MAAM,IAAI,CAAC;IAChC,qFAAqF;IACrF,sBAAsB,CAAC,EAAE,CAAC,OAAO,EAAE,0BAA0B,KAAK,IAAI,CAAC;IACvE,IAAI,EAAE,MAAM,CAAC;IACb,eAAe,CAAC,EAAE,OAAO,GAAG,QAAQ,CAAC;IACrC,EAAE,EAAE,MAAM,CAAC;CACZ,CAAC;AAEF,KAAK,sBAAsB,GAAG,cAAc,GAAG,uBAAuB,CAAC;AAEvE,wBAAgB,oCAAoC,CAClD,KAAK,EAAE,OAAO,EACd,QAAQ,GAAE,MAAM,CAAC,QAA2B,GAC3C,sBAAsB,GAAG,SAAS,CASpC;AAiRD,wBAAsB,wBAAwB,CAC5C,OAAO,EAAE,+BAA+B,GACvC,OAAO,CAAC,IAAI,CAAC,CAwGf"}
1
+ {"version":3,"file":"move-path.d.ts","sourceRoot":"","sources":["../src/move-path.ts"],"names":[],"mappings":"AAuBA,MAAM,MAAM,0BAA0B,GAAG,QAAQ,CAAC;IAChD,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;CACb,CAAC,CAAC;AAEH,MAAM,MAAM,+BAA+B,GAAG;IAC5C,qFAAqF;IACrF,oBAAoB,CAAC,EAAE,MAAM,IAAI,CAAC;IAClC,uFAAuF;IACvF,kBAAkB,CAAC,EAAE,MAAM,IAAI,CAAC;IAChC,qFAAqF;IACrF,sBAAsB,CAAC,EAAE,CAAC,OAAO,EAAE,0BAA0B,KAAK,IAAI,CAAC;IACvE,IAAI,EAAE,MAAM,CAAC;IACb,eAAe,CAAC,EAAE,OAAO,GAAG,QAAQ,CAAC;IACrC,EAAE,EAAE,MAAM,CAAC;CACZ,CAAC;AAEF,KAAK,sBAAsB,GAAG,cAAc,GAAG,uBAAuB,CAAC;AAEvE,wBAAgB,oCAAoC,CAClD,KAAK,EAAE,OAAO,EACd,QAAQ,GAAE,MAAM,CAAC,QAA2B,GAC3C,sBAAsB,GAAG,SAAS,CASpC;AAiRD,wBAAsB,wBAAwB,CAC5C,OAAO,EAAE,+BAA+B,GACvC,OAAO,CAAC,IAAI,CAAC,CAwGf"}
package/dist/move-path.js CHANGED
@@ -9,6 +9,7 @@ import { assertSourceStillMatches, cleanupCopiedEntry, createCleanupCopiedEntryS
9
9
  import { resolveReadOpenFlags } from "./read-open-flags.js";
10
10
  import { cleanupPinnedFilePath } from "./replace-file-temp-owner.js";
11
11
  import { createMoveStageOwner } from "./move-path-stage.js";
12
+ import { realpathSync } from "./realpath.js";
12
13
  export function moveCopyFallbackReasonForRenameError(error, platform = process.platform) {
13
14
  const code = error?.code;
14
15
  if (code === "EXDEV") {
@@ -61,12 +62,12 @@ async function assertCopyDestinationOutsideSource(sourcePath, targetPath, expect
61
62
  }
62
63
  const normalizedSource = path.resolve(sourcePath);
63
64
  const normalizedTarget = path.resolve(targetPath);
64
- const sourceParentReal = fsSync.realpathSync.native(path.dirname(normalizedSource));
65
- const targetParentReal = fsSync.realpathSync.native(path.dirname(normalizedTarget));
65
+ const sourceParentReal = realpathSync.native(path.dirname(normalizedSource));
66
+ const targetParentReal = realpathSync.native(path.dirname(normalizedTarget));
66
67
  const sourceCandidate = path.join(sourceParentReal, path.basename(normalizedSource));
67
68
  const targetCandidate = path.join(targetParentReal, path.basename(normalizedTarget));
68
69
  const sourceBoundary = sourceStat.isDirectory()
69
- ? fsSync.realpathSync.native(sourcePath)
70
+ ? realpathSync.native(sourcePath)
70
71
  : sourceCandidate;
71
72
  const unsafeTarget = sourceStat.isDirectory()
72
73
  ? isSameOrDescendant(sourceBoundary, targetCandidate)
@@ -3,6 +3,7 @@ export declare class MutationAuthorityError extends FsSafeError {
3
3
  readonly rejection: unknown;
4
4
  constructor(rejection: unknown);
5
5
  }
6
+ export declare function assertSynchronousCallbackResult(returned: unknown, name: string): void;
6
7
  export declare function composeMutationAssertions(defaultAssertion?: () => void, callAssertion?: () => void): (() => void) | undefined;
7
8
  export declare function rethrowMutationAuthorityError(error: unknown): never;
8
9
  //# sourceMappingURL=mutation-authority.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"mutation-authority.d.ts","sourceRoot":"","sources":["../src/mutation-authority.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE1C,qBAAa,sBAAuB,SAAQ,WAAW;IACzC,QAAQ,CAAC,SAAS,EAAE,OAAO;IAAvC,YAAqB,SAAS,EAAE,OAAO,EAEtC;CACF;AAcD,wBAAgB,yBAAyB,CACvC,gBAAgB,CAAC,EAAE,MAAM,IAAI,EAC7B,aAAa,CAAC,EAAE,MAAM,IAAI,GACzB,CAAC,MAAM,IAAI,CAAC,GAAG,SAAS,CAU1B;AAED,wBAAgB,6BAA6B,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,CAInE"}
1
+ {"version":3,"file":"mutation-authority.d.ts","sourceRoot":"","sources":["../src/mutation-authority.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE1C,qBAAa,sBAAuB,SAAQ,WAAW;IACzC,QAAQ,CAAC,SAAS,EAAE,OAAO;IAAvC,YAAqB,SAAS,EAAE,OAAO,EAEtC;CACF;AAED,wBAAgB,+BAA+B,CAAC,QAAQ,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAUrF;AAED,wBAAgB,yBAAyB,CACvC,gBAAgB,CAAC,EAAE,MAAM,IAAI,EAC7B,aAAa,CAAC,EAAE,MAAM,IAAI,GACzB,CAAC,MAAM,IAAI,CAAC,GAAG,SAAS,CAU1B;AAED,wBAAgB,6BAA6B,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,CAInE"}
@@ -6,13 +6,13 @@ export class MutationAuthorityError extends FsSafeError {
6
6
  this.rejection = rejection;
7
7
  }
8
8
  }
9
- function assertSynchronous(returned) {
9
+ export function assertSynchronousCallbackResult(returned, name) {
10
10
  if (returned !== null &&
11
11
  (typeof returned === "object" || typeof returned === "function") &&
12
12
  typeof returned.then === "function") {
13
13
  // TypeScript permits async functions for () => void. Do not admit their work.
14
14
  void Promise.resolve(returned).catch(() => undefined);
15
- throw new TypeError("assertBeforeMutation must be synchronous");
15
+ throw new TypeError(`${name} must be synchronous`);
16
16
  }
17
17
  }
18
18
  export function composeMutationAssertions(defaultAssertion, callAssertion) {
@@ -20,8 +20,8 @@ export function composeMutationAssertions(defaultAssertion, callAssertion) {
20
20
  return undefined;
21
21
  return () => {
22
22
  try {
23
- assertSynchronous(defaultAssertion?.());
24
- assertSynchronous(callAssertion?.());
23
+ assertSynchronousCallbackResult(defaultAssertion?.(), "assertBeforeMutation");
24
+ assertSynchronousCallbackResult(callAssertion?.(), "assertBeforeMutation");
25
25
  }
26
26
  catch (error) {
27
27
  throw new MutationAuthorityError(error);
@@ -75,8 +75,18 @@ export interface NativeWindowsSecurityFacts {
75
75
  unsupportedAceTypes: number[];
76
76
  aces: NativeWindowsAccessControlEntry[];
77
77
  }
78
+ export interface NativeWindowsDescriptorSecurityFacts {
79
+ /** Canonical lowercase 32-bit volume serial and 64-bit file-index projection used by Node. */
80
+ identity: string;
81
+ security: NativeWindowsSecurityFacts;
82
+ }
78
83
  export interface NativeBinding {
79
- /** Windows-only byte transfer; callers retain both admitted descriptors until settlement. */
84
+ /** POSIX system canonicalization; confinement and identity policy stay with callers. */
85
+ canonicalizePath?(path: string, ordinary: boolean): {
86
+ path?: string;
87
+ errno?: number;
88
+ };
89
+ /** Linux/Windows byte transfer; callers retain both admitted descriptors until settlement. */
80
90
  copyFileContents?(sourceFd: number, targetFd: number, signal?: AbortSignal): Promise<void>;
81
91
  /** Internal: same private, immutable input ownership as the ZIP buffer reader. */
82
92
  openTarBufferNative(buffer: Buffer, kind: string, limits: TarMeterLimits, signal?: AbortSignal): Promise<{
@@ -89,7 +99,7 @@ export interface NativeBinding {
89
99
  readEntry(index: number, maxBytes: number, signal?: AbortSignal): Promise<Buffer>;
90
100
  }>;
91
101
  readCloneFileMetadata(paths: string[]): Promise<(Buffer | null)[]>;
92
- probeTreeClone(parentFd: number): "apfs" | "btrfs" | "refs" | "xfs" | null;
102
+ probeTreeClone(parentFd: number): "apfs" | "btrfs" | "refs" | "xfs" | "zfs" | null;
93
103
  cloneTree(sourceFd: number | null, parentFd: number, basename: string, concurrency: number, signal?: AbortSignal): Promise<void>;
94
104
  createStagedFile?(parentFd: number, basename: string): number;
95
105
  stagedFileMatches?(parentFd: number, basename: string, fileFd: number): boolean;
@@ -106,6 +116,8 @@ export interface NativeBinding {
106
116
  openBeneath(rootFd: number, relPath: string, flags: number): NativeOpenBeneathResult;
107
117
  readArchiveEntryNative(path: string, kind: string, requested: string, maxBytes: number, limits: TarMeterLimits, signal: AbortSignal): Promise<Buffer>;
108
118
  readOwnerAndDacl(path: string): NativeWindowsSecurityFacts;
119
+ /** Internal Windows-only inspection of the exact borrowed Node descriptor. */
120
+ inspectWindowsSecureFileHandle?(fd: number): NativeWindowsDescriptorSecurityFacts;
109
121
  ownedTreeRemovalAvailable?(parentFd: number): boolean;
110
122
  removeOwnedTree?(parentFd: number, basename: string, directoryFd: number): Promise<NativeOwnedTreeRemovalResult>;
111
123
  removeOwnedTreeSync?(parentFd: number, basename: string, directoryFd: number): NativeOwnedTreeRemovalResult;