@openclaw/fs-safe 0.14.0 → 0.16.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 (370) hide show
  1. package/CHANGELOG.md +88 -0
  2. package/README.md +38 -10
  3. package/dist/advanced.d.ts +2 -1
  4. package/dist/advanced.d.ts.map +1 -1
  5. package/dist/advanced.js +2 -1
  6. package/dist/archive-kind.d.ts +0 -1
  7. package/dist/archive-kind.d.ts.map +1 -1
  8. package/dist/archive-kind.js +5 -17
  9. package/dist/archive-merge.d.ts.map +1 -1
  10. package/dist/archive-merge.js +113 -46
  11. package/dist/archive-parser.wasm +0 -0
  12. package/dist/archive-read.d.ts.map +1 -1
  13. package/dist/archive-read.js +10 -11
  14. package/dist/archive-tar-stream.d.ts +3 -0
  15. package/dist/archive-tar-stream.d.ts.map +1 -1
  16. package/dist/archive-tar-stream.js +56 -37
  17. package/dist/archive-tar-wasm.d.ts +16 -4
  18. package/dist/archive-tar-wasm.d.ts.map +1 -1
  19. package/dist/archive-tar-wasm.js +134 -34
  20. package/dist/archive-zip-directory.d.ts +4 -0
  21. package/dist/archive-zip-directory.d.ts.map +1 -1
  22. package/dist/archive-zip-directory.js +2 -0
  23. package/dist/archive-zip-entry.d.ts +6 -2
  24. package/dist/archive-zip-entry.d.ts.map +1 -1
  25. package/dist/archive-zip-entry.js +23 -8
  26. package/dist/archive-zip-integrity.d.ts.map +1 -1
  27. package/dist/archive-zip-integrity.js +3 -4
  28. package/dist/archive-zip-loader.d.ts.map +1 -1
  29. package/dist/archive-zip-loader.js +107 -31
  30. package/dist/archive-zip-names.d.ts +1 -0
  31. package/dist/archive-zip-names.d.ts.map +1 -1
  32. package/dist/archive-zip-names.js +6 -0
  33. package/dist/archive.d.ts.map +1 -1
  34. package/dist/archive.js +11 -11
  35. package/dist/bounded-read-stream.d.ts +0 -1
  36. package/dist/bounded-read-stream.d.ts.map +1 -1
  37. package/dist/bounded-read-stream.js +0 -6
  38. package/dist/clone-metadata.d.ts +1 -0
  39. package/dist/clone-metadata.d.ts.map +1 -1
  40. package/dist/clone-metadata.js +6 -2
  41. package/dist/copy-publication.d.ts +6 -0
  42. package/dist/copy-publication.d.ts.map +1 -1
  43. package/dist/copy-publication.js +3 -0
  44. package/dist/copy-tree-portable.d.ts.map +1 -1
  45. package/dist/copy-tree-portable.js +44 -24
  46. package/dist/copy.d.ts.map +1 -1
  47. package/dist/copy.js +29 -11
  48. package/dist/create-directory.d.ts +20 -0
  49. package/dist/create-directory.d.ts.map +1 -0
  50. package/dist/create-directory.js +130 -0
  51. package/dist/create-file-async.d.ts +7 -0
  52. package/dist/create-file-async.d.ts.map +1 -0
  53. package/dist/create-file-async.js +121 -0
  54. package/dist/create-file.d.ts +8 -0
  55. package/dist/create-file.d.ts.map +1 -0
  56. package/dist/create-file.js +190 -0
  57. package/dist/create-owned-file.d.ts +8 -0
  58. package/dist/create-owned-file.d.ts.map +1 -0
  59. package/dist/create-owned-file.js +16 -0
  60. package/dist/create.d.ts +4 -0
  61. package/dist/create.d.ts.map +1 -0
  62. package/dist/create.js +2 -0
  63. package/dist/creation-darwin.d.ts +7 -0
  64. package/dist/creation-darwin.d.ts.map +1 -0
  65. package/dist/creation-darwin.js +79 -0
  66. package/dist/creation-file-state.d.ts +19 -0
  67. package/dist/creation-file-state.d.ts.map +1 -0
  68. package/dist/creation-file-state.js +118 -0
  69. package/dist/creation-path.d.ts +21 -0
  70. package/dist/creation-path.d.ts.map +1 -0
  71. package/dist/creation-path.js +71 -0
  72. package/dist/creation-permissions.d.ts +19 -0
  73. package/dist/creation-permissions.d.ts.map +1 -0
  74. package/dist/creation-permissions.js +125 -0
  75. package/dist/directory-durability.d.ts +1 -1
  76. package/dist/directory-durability.d.ts.map +1 -1
  77. package/dist/directory-durability.js +22 -80
  78. package/dist/directory-guard.d.ts +3 -0
  79. package/dist/directory-guard.d.ts.map +1 -1
  80. package/dist/directory-mode-node.d.ts +2 -0
  81. package/dist/directory-mode-node.d.ts.map +1 -1
  82. package/dist/directory-mode-node.js +8 -0
  83. package/dist/directory-mode-owner.js +5 -5
  84. package/dist/directory-receipt.d.ts +24 -0
  85. package/dist/directory-receipt.d.ts.map +1 -0
  86. package/dist/directory-receipt.js +127 -0
  87. package/dist/file-cleanup.d.ts +19 -0
  88. package/dist/file-cleanup.d.ts.map +1 -0
  89. package/dist/file-cleanup.js +78 -0
  90. package/dist/file-handle-transfer.d.ts +2 -0
  91. package/dist/file-handle-transfer.d.ts.map +1 -1
  92. package/dist/file-handle-transfer.js +57 -2
  93. package/dist/file-identity.d.ts.map +1 -1
  94. package/dist/file-identity.js +18 -4
  95. package/dist/file-lock-sync-admission.d.ts +19 -0
  96. package/dist/file-lock-sync-admission.d.ts.map +1 -0
  97. package/dist/file-lock-sync-admission.js +93 -0
  98. package/dist/file-lock-sync-root-acquire.d.ts +4 -0
  99. package/dist/file-lock-sync-root-acquire.d.ts.map +1 -0
  100. package/dist/file-lock-sync-root-acquire.js +370 -0
  101. package/dist/file-lock-sync-root-arbitration.d.ts +18 -0
  102. package/dist/file-lock-sync-root-arbitration.d.ts.map +1 -0
  103. package/dist/file-lock-sync-root-arbitration.js +66 -0
  104. package/dist/file-lock-sync-root-held.d.ts +34 -0
  105. package/dist/file-lock-sync-root-held.d.ts.map +1 -0
  106. package/dist/file-lock-sync-root-held.js +393 -0
  107. package/dist/file-lock-sync-root-io.d.ts +44 -0
  108. package/dist/file-lock-sync-root-io.d.ts.map +1 -0
  109. package/dist/file-lock-sync-root-io.js +209 -0
  110. package/dist/file-lock-sync-root-mutation.d.ts +17 -0
  111. package/dist/file-lock-sync-root-mutation.d.ts.map +1 -0
  112. package/dist/file-lock-sync-root-mutation.js +277 -0
  113. package/dist/file-lock-sync-root-options.d.ts +20 -0
  114. package/dist/file-lock-sync-root-options.d.ts.map +1 -0
  115. package/dist/file-lock-sync-root-options.js +58 -0
  116. package/dist/file-lock-sync-root-registration.d.ts +2 -0
  117. package/dist/file-lock-sync-root-registration.d.ts.map +1 -0
  118. package/dist/file-lock-sync-root-registration.js +90 -0
  119. package/dist/file-lock-sync-root.d.ts +36 -0
  120. package/dist/file-lock-sync-root.d.ts.map +1 -0
  121. package/dist/file-lock-sync-root.js +361 -0
  122. package/dist/file-lock-sync-stale-admission.d.ts +24 -0
  123. package/dist/file-lock-sync-stale-admission.d.ts.map +1 -0
  124. package/dist/file-lock-sync-stale-admission.js +205 -0
  125. package/dist/file-lock-sync.d.ts.map +1 -1
  126. package/dist/file-lock-sync.js +245 -205
  127. package/dist/file-observation.d.ts +1 -1
  128. package/dist/file-observation.d.ts.map +1 -1
  129. package/dist/file-store-boundary.d.ts +2 -6
  130. package/dist/file-store-boundary.d.ts.map +1 -1
  131. package/dist/file-store-boundary.js +3 -9
  132. package/dist/file-store-prune.d.ts.map +1 -1
  133. package/dist/file-store-prune.js +5 -1
  134. package/dist/file-store-sync-write.d.ts.map +1 -1
  135. package/dist/file-store-sync-write.js +5 -8
  136. package/dist/file-store.d.ts.map +1 -1
  137. package/dist/file-store.js +47 -12
  138. package/dist/guarded-mkdir.d.ts +1 -0
  139. package/dist/guarded-mkdir.d.ts.map +1 -1
  140. package/dist/guarded-mkdir.js +36 -7
  141. package/dist/json-document-store.d.ts.map +1 -1
  142. package/dist/json-document-store.js +22 -15
  143. package/dist/json-durable-queue-ownership.d.ts +0 -1
  144. package/dist/json-durable-queue-ownership.d.ts.map +1 -1
  145. package/dist/json-durable-queue-ownership.js +0 -6
  146. package/dist/move-path.js +1 -1
  147. package/dist/native-binding.d.ts +13 -1
  148. package/dist/native-binding.d.ts.map +1 -1
  149. package/dist/native-fallback-warning.d.ts +4 -0
  150. package/dist/native-fallback-warning.d.ts.map +1 -0
  151. package/dist/native-fallback-warning.js +11 -0
  152. package/dist/native-operations.d.ts +0 -2
  153. package/dist/native-operations.d.ts.map +1 -1
  154. package/dist/native-operations.js +0 -24
  155. package/dist/native-parent-admission.d.ts +5 -2
  156. package/dist/native-parent-admission.d.ts.map +1 -1
  157. package/dist/native-parent-admission.js +27 -7
  158. package/dist/native-pinned-write-windows.d.ts +1 -1
  159. package/dist/native-pinned-write-windows.d.ts.map +1 -1
  160. package/dist/native-pinned-write-windows.js +174 -29
  161. package/dist/native-pinned-write.d.ts.map +1 -1
  162. package/dist/native-pinned-write.js +26 -14
  163. package/dist/native-policy-parent-windows.d.ts +14 -0
  164. package/dist/native-policy-parent-windows.d.ts.map +1 -0
  165. package/dist/native-policy-parent-windows.js +209 -0
  166. package/dist/native-rename-outcome.d.ts +4 -0
  167. package/dist/native-rename-outcome.d.ts.map +1 -0
  168. package/dist/native-rename-outcome.js +8 -0
  169. package/dist/native-staged-file.d.ts +3 -2
  170. package/dist/native-staged-file.d.ts.map +1 -1
  171. package/dist/native-staged-file.js +121 -72
  172. package/dist/output.d.ts.map +1 -1
  173. package/dist/output.js +12 -8
  174. package/dist/owner-dacl.d.ts.map +1 -1
  175. package/dist/owner-dacl.js +10 -4
  176. package/dist/path-prefix.d.ts.map +1 -1
  177. package/dist/path-prefix.js +30 -8
  178. package/dist/path-suffix-aliases.d.ts +2 -0
  179. package/dist/path-suffix-aliases.d.ts.map +1 -1
  180. package/dist/path-suffix-aliases.js +25 -17
  181. package/dist/permission-exec.d.ts +2 -0
  182. package/dist/permission-exec.d.ts.map +1 -1
  183. package/dist/permission-exec.js +150 -21
  184. package/dist/permissions-windows.js +1 -1
  185. package/dist/pinned-mutation-admission.d.ts.map +1 -1
  186. package/dist/pinned-mutation-admission.js +10 -5
  187. package/dist/pinned-mutation-observation.d.ts +0 -1
  188. package/dist/pinned-mutation-observation.d.ts.map +1 -1
  189. package/dist/pinned-mutation-observation.js +0 -19
  190. package/dist/pinned-mutation-shared-route.d.ts +1 -0
  191. package/dist/pinned-mutation-shared-route.d.ts.map +1 -1
  192. package/dist/pinned-mutation-shared-route.js +1 -1
  193. package/dist/pinned-write-input.d.ts +4 -0
  194. package/dist/pinned-write-input.d.ts.map +1 -0
  195. package/dist/pinned-write-input.js +25 -0
  196. package/dist/pinned-write-mode.d.ts +5 -0
  197. package/dist/pinned-write-mode.d.ts.map +1 -0
  198. package/dist/pinned-write-mode.js +24 -0
  199. package/dist/pinned-write-staged.d.ts +6 -0
  200. package/dist/pinned-write-staged.d.ts.map +1 -0
  201. package/dist/pinned-write-staged.js +187 -0
  202. package/dist/pinned-write-types.d.ts +5 -0
  203. package/dist/pinned-write-types.d.ts.map +1 -1
  204. package/dist/pinned-write.d.ts.map +1 -1
  205. package/dist/pinned-write.js +35 -145
  206. package/dist/private-directory.d.ts.map +1 -1
  207. package/dist/private-directory.js +18 -4
  208. package/dist/private-producer-handoff-sync.d.ts +14 -0
  209. package/dist/private-producer-handoff-sync.d.ts.map +1 -0
  210. package/dist/private-producer-handoff-sync.js +114 -0
  211. package/dist/private-producer-handoff.d.ts +22 -4
  212. package/dist/private-producer-handoff.d.ts.map +1 -1
  213. package/dist/private-producer-handoff.js +140 -77
  214. package/dist/private-temp-workspace.d.ts.map +1 -1
  215. package/dist/private-temp-workspace.js +75 -121
  216. package/dist/publish-copy-stage.d.ts +2 -1
  217. package/dist/publish-copy-stage.d.ts.map +1 -1
  218. package/dist/publish-copy-stage.js +16 -7
  219. package/dist/publish-file.d.ts.map +1 -1
  220. package/dist/publish-file.js +2 -2
  221. package/dist/regular-file.d.ts.map +1 -1
  222. package/dist/regular-file.js +1 -15
  223. package/dist/replace-directory.d.ts.map +1 -1
  224. package/dist/replace-directory.js +256 -18
  225. package/dist/replace-file-copy-fallback.d.ts.map +1 -1
  226. package/dist/replace-file-copy-fallback.js +62 -70
  227. package/dist/replace-file-copy-source.d.ts.map +1 -1
  228. package/dist/replace-file-copy-source.js +10 -12
  229. package/dist/replace-file-temp-owner.d.ts +5 -9
  230. package/dist/replace-file-temp-owner.d.ts.map +1 -1
  231. package/dist/replace-file-temp-owner.js +56 -72
  232. package/dist/replace-file.js +6 -6
  233. package/dist/retained-directory-replacement.d.ts +26 -0
  234. package/dist/retained-directory-replacement.d.ts.map +1 -0
  235. package/dist/retained-directory-replacement.js +193 -0
  236. package/dist/root-boundary.d.ts +1 -0
  237. package/dist/root-boundary.d.ts.map +1 -1
  238. package/dist/root-boundary.js +4 -0
  239. package/dist/root-context.d.ts +0 -8
  240. package/dist/root-context.d.ts.map +1 -1
  241. package/dist/root-context.js +0 -3
  242. package/dist/root-create-input.d.ts +8 -1
  243. package/dist/root-create-input.d.ts.map +1 -1
  244. package/dist/root-create-input.js +17 -4
  245. package/dist/root-directory-creation.d.ts +3 -3
  246. package/dist/root-directory-creation.d.ts.map +1 -1
  247. package/dist/root-directory-creation.js +15 -3
  248. package/dist/root-directory-list.d.ts +1 -0
  249. package/dist/root-directory-list.d.ts.map +1 -1
  250. package/dist/root-directory-list.js +1 -0
  251. package/dist/root-impl.d.ts.map +1 -1
  252. package/dist/root-impl.js +46 -17
  253. package/dist/root-move-noreplace.d.ts.map +1 -1
  254. package/dist/root-move-noreplace.js +24 -15
  255. package/dist/root-options.d.ts +12 -4
  256. package/dist/root-options.d.ts.map +1 -1
  257. package/dist/root-path-errors.d.ts +1 -0
  258. package/dist/root-path-errors.d.ts.map +1 -1
  259. package/dist/root-path-errors.js +11 -2
  260. package/dist/root-path-existing.d.ts.map +1 -1
  261. package/dist/root-path-existing.js +11 -35
  262. package/dist/root-path-stat.d.ts.map +1 -1
  263. package/dist/root-path-stat.js +59 -7
  264. package/dist/root-path.js +1 -13
  265. package/dist/root-remove.d.ts +1 -0
  266. package/dist/root-remove.d.ts.map +1 -1
  267. package/dist/root-remove.js +4 -0
  268. package/dist/root-walk.d.ts +1 -1
  269. package/dist/root-walk.d.ts.map +1 -1
  270. package/dist/root-walk.js +17 -2
  271. package/dist/root-write-admission.d.ts +0 -2
  272. package/dist/root-write-admission.d.ts.map +1 -1
  273. package/dist/root-write-admission.js +1 -15
  274. package/dist/root-write-complete-parent.d.ts.map +1 -1
  275. package/dist/root-write-complete-parent.js +7 -23
  276. package/dist/root-write-publication.js +1 -1
  277. package/dist/root-write-verification.d.ts.map +1 -1
  278. package/dist/root-write-verification.js +29 -42
  279. package/dist/secret-file.d.ts.map +1 -1
  280. package/dist/secret-file.js +3 -24
  281. package/dist/secret-read-async.d.ts.map +1 -1
  282. package/dist/secret-read-async.js +3 -24
  283. package/dist/secret-read-policy.d.ts +6 -2
  284. package/dist/secret-read-policy.d.ts.map +1 -1
  285. package/dist/secret-read-policy.js +26 -2
  286. package/dist/secure-file-windows.d.ts +6 -0
  287. package/dist/secure-file-windows.d.ts.map +1 -1
  288. package/dist/secure-file-windows.js +34 -117
  289. package/dist/secure-file.js +2 -2
  290. package/dist/sibling-temp.d.ts.map +1 -1
  291. package/dist/sibling-temp.js +23 -12
  292. package/dist/sidecar-lock-acquire.d.ts +2 -28
  293. package/dist/sidecar-lock-acquire.d.ts.map +1 -1
  294. package/dist/sidecar-lock-acquire.js +288 -199
  295. package/dist/sidecar-lock-admission-context.d.ts +19 -0
  296. package/dist/sidecar-lock-admission-context.d.ts.map +1 -0
  297. package/dist/sidecar-lock-admission-context.js +60 -0
  298. package/dist/sidecar-lock-admission-parser.d.ts +43 -0
  299. package/dist/sidecar-lock-admission-parser.d.ts.map +1 -0
  300. package/dist/sidecar-lock-admission-parser.js +113 -0
  301. package/dist/sidecar-lock-admission.d.ts +35 -0
  302. package/dist/sidecar-lock-admission.d.ts.map +1 -0
  303. package/dist/sidecar-lock-admission.js +7 -0
  304. package/dist/sidecar-lock-reclaim.d.ts +9 -4
  305. package/dist/sidecar-lock-reclaim.d.ts.map +1 -1
  306. package/dist/sidecar-lock-reclaim.js +80 -25
  307. package/dist/sidecar-lock-root.d.ts.map +1 -1
  308. package/dist/sidecar-lock-root.js +2 -1
  309. package/dist/sidecar-lock-stale-admission.d.ts +39 -0
  310. package/dist/sidecar-lock-stale-admission.d.ts.map +1 -0
  311. package/dist/sidecar-lock-stale-admission.js +232 -0
  312. package/dist/sidecar-lock-target.d.ts +8 -0
  313. package/dist/sidecar-lock-target.d.ts.map +1 -0
  314. package/dist/sidecar-lock-target.js +55 -0
  315. package/dist/sidecar-lock.d.ts.map +1 -1
  316. package/dist/sidecar-lock.js +100 -16
  317. package/dist/staged-directory.d.ts.map +1 -1
  318. package/dist/staged-directory.js +6 -6
  319. package/dist/staged-file-settlement.d.ts +17 -0
  320. package/dist/staged-file-settlement.d.ts.map +1 -0
  321. package/dist/staged-file-settlement.js +57 -0
  322. package/dist/temp-workspace-descriptor.d.ts.map +1 -1
  323. package/dist/temp-workspace-descriptor.js +9 -27
  324. package/dist/temp-workspace-owner.d.ts.map +1 -1
  325. package/dist/temp-workspace-owner.js +8 -8
  326. package/dist/walk.d.ts +5 -1
  327. package/dist/walk.d.ts.map +1 -1
  328. package/dist/walk.js +19 -6
  329. package/dist/windows-owner.d.ts.map +1 -1
  330. package/dist/windows-owner.js +4 -3
  331. package/dist/windows-security-bridge.cs +336 -0
  332. package/dist/windows-security-bridge.ps1 +15 -0
  333. package/dist/windows-security-command.d.ts +26 -0
  334. package/dist/windows-security-command.d.ts.map +1 -0
  335. package/dist/windows-security-command.js +363 -0
  336. package/dist/windows-security-facts.d.ts +6 -0
  337. package/dist/windows-security-facts.d.ts.map +1 -0
  338. package/dist/windows-security-facts.js +108 -0
  339. package/docs/advanced.md +4 -2
  340. package/docs/archive.md +97 -46
  341. package/docs/atomic.md +85 -8
  342. package/docs/config.md +6 -2
  343. package/docs/contributing.md +44 -4
  344. package/docs/copy.md +37 -0
  345. package/docs/creation.md +128 -0
  346. package/docs/durability.md +24 -0
  347. package/docs/file-store.md +19 -0
  348. package/docs/install.md +31 -7
  349. package/docs/json-store.md +5 -0
  350. package/docs/migrating-to-0.5.md +15 -6
  351. package/docs/migrating-to-0.6.md +9 -4
  352. package/docs/native-helper.md +32 -12
  353. package/docs/native.md +38 -7
  354. package/docs/output.md +6 -0
  355. package/docs/path-prefix.md +10 -0
  356. package/docs/path-suffix-aliases.md +51 -6
  357. package/docs/permissions.md +50 -14
  358. package/docs/public-api.md +3 -2
  359. package/docs/root.md +56 -3
  360. package/docs/secret-file.md +11 -2
  361. package/docs/secure-file.md +9 -4
  362. package/docs/sidecar-lock.md +114 -8
  363. package/docs/staged-file.md +12 -3
  364. package/docs/temp.md +20 -3
  365. package/docs/walk.md +67 -1
  366. package/docs/writing.md +76 -6
  367. package/package.json +19 -16
  368. package/dist/darwin-acl.d.ts +0 -4
  369. package/dist/darwin-acl.d.ts.map +0 -1
  370. package/dist/darwin-acl.js +0 -24
@@ -1,7 +1,8 @@
1
1
  import syncFs, {} from "node:fs";
2
2
  import fs, {} from "node:fs/promises";
3
3
  import path from "node:path";
4
- import { assertAsyncDirectoryGuard, createAsyncDirectoryGuard } from "./directory-guard.js";
4
+ import { createAsyncDirectoryGuard } from "./directory-guard.js";
5
+ import { hasErrorCode, removeOwnedPath } from "./file-cleanup.js";
5
6
  import { resolveReadOpenFlags } from "./read-open-flags.js";
6
7
  import { FsSafeError } from "./errors.js";
7
8
  import { sameFileIdentityForCleanup, sha256Hex } from "./file-identity.js";
@@ -9,6 +10,22 @@ import { withAsyncDirectoryGuards } from "./guarded-mutation.js";
9
10
  import { inspectFileIdentity, inspectFileIdentitySync } from "./strict-file-identity.js";
10
11
  import { registerTempPathForExit } from "./temp-cleanup.js";
11
12
  const PUBLISHED_READ_FLAGS = resolveReadOpenFlags();
13
+ function describeFailure(error) {
14
+ try {
15
+ return String(error);
16
+ }
17
+ catch {
18
+ return "<unprintable failure>";
19
+ }
20
+ }
21
+ function isErrorValue(error) {
22
+ try {
23
+ return error instanceof Error;
24
+ }
25
+ catch {
26
+ return false;
27
+ }
28
+ }
12
29
  export async function removePathIfIdentityUnchanged(targetPath, identity) {
13
30
  const parentGuard = await createAsyncDirectoryGuard(path.dirname(targetPath), { bigint: true });
14
31
  await withAsyncDirectoryGuards([parentGuard], async () => {
@@ -34,65 +51,24 @@ function missingOwnedFile(pathname, cause) {
34
51
  cause,
35
52
  });
36
53
  }
37
- function cleanupFailure(originalError, cleanupError) {
38
- if (originalError !== undefined) {
39
- return new Error(`Atomic file replace failed (${String(originalError)}); cleanup also failed (${String(cleanupError)})`, { cause: originalError });
54
+ function cleanupFailure(originalFailure, cleanupError) {
55
+ if (originalFailure) {
56
+ return new Error(`Atomic file replace failed (${describeFailure(originalFailure.error)}); cleanup also failed (${describeFailure(cleanupError)})`, { cause: originalFailure.error });
40
57
  }
41
- return cleanupError instanceof Error ? cleanupError : new Error(String(cleanupError));
58
+ return isErrorValue(cleanupError) ? cleanupError : new Error(describeFailure(cleanupError));
42
59
  }
43
60
  async function cleanupOwnedPath(params) {
44
- if (!params.identity)
45
- return true;
46
61
  try {
47
- const current = params.fsModule === fs
48
- ? syncFs.lstatSync(params.pathname, { bigint: true })
49
- : await params.fsModule.lstat(params.pathname, { bigint: true });
50
- if (current.isSymbolicLink() ||
51
- !current.isFile() ||
52
- current.nlink !== 1n ||
53
- !sameFileIdentityForCleanup(current, params.identity)) {
54
- return true;
55
- }
56
- await params.fsModule.unlink(params.pathname);
62
+ await removeOwnedPath(params);
57
63
  return true;
58
64
  }
59
65
  catch (cleanupError) {
60
- if (cleanupError.code === "ENOENT")
61
- return true;
62
66
  if (params.throwOnCleanupError) {
63
- throw cleanupFailure(params.originalError, cleanupError);
67
+ throw cleanupFailure(params.originalFailure, cleanupError);
64
68
  }
65
69
  return false;
66
70
  }
67
71
  }
68
- // Borrowed handle: the caller retains it until this best-effort cleanup finishes.
69
- export async function cleanupPinnedFilePath(params) {
70
- if (!params.identity)
71
- return;
72
- try {
73
- const guard = params.parentGuard;
74
- if ([guard.stat.dev, guard.stat.ino].some((value) => typeof value === "number" && !Number.isSafeInteger(value)))
75
- return;
76
- await assertAsyncDirectoryGuard(guard);
77
- const parent = syncFs.lstatSync(guard.dir, { bigint: true });
78
- if (parent.isSymbolicLink() || !parent.isDirectory() ||
79
- !sameFileIdentityForCleanup(parent, guard.stat))
80
- return;
81
- const opened = syncFs.fstatSync(params.handle.fd, { bigint: true });
82
- if (!opened.isFile() || opened.nlink !== 1n ||
83
- !sameFileIdentityForCleanup(opened, params.identity))
84
- return;
85
- await cleanupOwnedPath({
86
- fsModule: fs,
87
- pathname: params.pathname,
88
- identity: params.identity,
89
- throwOnCleanupError: false,
90
- });
91
- }
92
- catch {
93
- // Unverifiable authority must preserve the path and the original write failure.
94
- }
95
- }
96
72
  function cleanupOwnedPathSync(params) {
97
73
  if (!params.identity)
98
74
  return true;
@@ -108,10 +84,10 @@ function cleanupOwnedPathSync(params) {
108
84
  return true;
109
85
  }
110
86
  catch (cleanupError) {
111
- if (cleanupError.code === "ENOENT")
87
+ if (hasErrorCode(cleanupError, "ENOENT"))
112
88
  return true;
113
89
  if (params.throwOnCleanupError) {
114
- throw cleanupFailure(params.originalError, cleanupError);
90
+ throw cleanupFailure(params.originalFailure, cleanupError);
115
91
  }
116
92
  return false;
117
93
  }
@@ -202,7 +178,9 @@ export class AsyncAtomicTempOwner {
202
178
  if (sha256Hex(await published.readFile()) !== expectedHash) {
203
179
  throw new FsSafeError("path-mismatch", `Atomic replace published content changed: ${pathname}`);
204
180
  }
205
- await this.#handle?.close();
181
+ const previousHandle = this.#handle;
182
+ this.#handle = undefined;
183
+ await previousHandle?.close();
206
184
  this.#handle = published;
207
185
  this.#identity = identity;
208
186
  published = undefined;
@@ -216,7 +194,7 @@ export class AsyncAtomicTempOwner {
216
194
  this.#unregister();
217
195
  }
218
196
  async finish(params) {
219
- let deferredError;
197
+ let deferredFailure;
220
198
  let cleanupComplete = !this.#exists;
221
199
  if (this.#exists) {
222
200
  try {
@@ -224,28 +202,32 @@ export class AsyncAtomicTempOwner {
224
202
  fsModule: params.fsModule,
225
203
  pathname: this.pathname,
226
204
  identity: this.#identity,
227
- originalError: params.originalError,
205
+ originalFailure: params.originalFailure,
228
206
  throwOnCleanupError: params.throwOnCleanupError,
229
207
  });
230
208
  }
231
209
  catch (error) {
232
- deferredError = error;
210
+ deferredFailure = { error };
233
211
  }
234
212
  }
235
213
  if (cleanupComplete)
236
214
  this.#unregister();
215
+ const handle = this.#handle;
216
+ this.#handle = undefined;
237
217
  try {
238
- await this.#handle?.close();
218
+ await handle?.close();
239
219
  }
240
220
  catch (closeError) {
241
- deferredError = deferredError
242
- ? new AggregateError([deferredError, closeError], "Atomic temp cleanup and close failed")
243
- : params.originalError !== undefined
244
- ? new AggregateError([params.originalError, closeError], "Atomic file replace and close failed")
245
- : closeError;
221
+ deferredFailure = {
222
+ error: deferredFailure
223
+ ? new AggregateError([deferredFailure.error, closeError], "Atomic temp cleanup and close failed")
224
+ : params.originalFailure
225
+ ? new AggregateError([params.originalFailure.error, closeError], "Atomic file replace and close failed")
226
+ : closeError,
227
+ };
246
228
  }
247
- if (deferredError)
248
- throw deferredError;
229
+ if (deferredFailure)
230
+ throw deferredFailure.error;
249
231
  }
250
232
  }
251
233
  export class SyncAtomicTempOwner {
@@ -353,7 +335,7 @@ export class SyncAtomicTempOwner {
353
335
  this.#unregister();
354
336
  }
355
337
  finish(params) {
356
- let deferredError;
338
+ let deferredFailure;
357
339
  let cleanupComplete = !this.#exists;
358
340
  if (this.#exists) {
359
341
  try {
@@ -361,12 +343,12 @@ export class SyncAtomicTempOwner {
361
343
  fsModule: params.fsModule,
362
344
  pathname: this.pathname,
363
345
  identity: this.#identity,
364
- originalError: params.originalError,
346
+ originalFailure: params.originalFailure,
365
347
  throwOnCleanupError: params.throwOnCleanupError,
366
348
  });
367
349
  }
368
350
  catch (error) {
369
- deferredError = error;
351
+ deferredFailure = { error };
370
352
  }
371
353
  }
372
354
  if (cleanupComplete)
@@ -378,14 +360,16 @@ export class SyncAtomicTempOwner {
378
360
  params.fsModule.closeSync(fd);
379
361
  }
380
362
  catch (closeError) {
381
- deferredError = deferredError
382
- ? new AggregateError([deferredError, closeError], "Atomic temp cleanup and close failed")
383
- : params.originalError !== undefined
384
- ? new AggregateError([params.originalError, closeError], "Atomic file replace and close failed")
385
- : closeError;
363
+ deferredFailure = {
364
+ error: deferredFailure
365
+ ? new AggregateError([deferredFailure.error, closeError], "Atomic temp cleanup and close failed")
366
+ : params.originalFailure
367
+ ? new AggregateError([params.originalFailure.error, closeError], "Atomic file replace and close failed")
368
+ : closeError,
369
+ };
386
370
  }
387
371
  }
388
- if (deferredError)
389
- throw deferredError;
372
+ if (deferredFailure)
373
+ throw deferredFailure.error;
390
374
  }
391
375
  }
@@ -171,7 +171,7 @@ async function replaceFileAtomicUnserialized(options, filePath, renameIdentity,
171
171
  const expectedHash = atomicExpectedContentHash(renameIdentity, options.content);
172
172
  const tempPath = buildReplaceTempPath(filePath, options.tempPrefix);
173
173
  const tempOwner = new AsyncAtomicTempOwner(tempPath);
174
- let originalError;
174
+ let originalFailure;
175
175
  try {
176
176
  await fsModule.mkdir(fsModule === fs ? recursiveMkdirPath(dir) : dir, { recursive: true, mode: dirMode });
177
177
  await applyDirectoryMode({ fsModule, dirPath: dir, mode: dirMode });
@@ -229,13 +229,13 @@ async function replaceFileAtomicUnserialized(options, filePath, renameIdentity,
229
229
  return result;
230
230
  }
231
231
  catch (error) {
232
- originalError = error;
232
+ originalFailure = { error };
233
233
  throw error;
234
234
  }
235
235
  finally {
236
236
  await tempOwner.finish({
237
237
  fsModule,
238
- originalError,
238
+ originalFailure,
239
239
  throwOnCleanupError: options.throwOnCleanupError === true,
240
240
  });
241
241
  }
@@ -271,7 +271,7 @@ function replaceFileAtomicSyncUnserialized(options, filePath, renameIdentity) {
271
271
  }
272
272
  const tempPath = buildReplaceTempPath(filePath, options.tempPrefix);
273
273
  const tempOwner = new SyncAtomicTempOwner(tempPath);
274
- let originalError;
274
+ let originalFailure;
275
275
  try {
276
276
  fsModule.mkdirSync(fsModule === syncFs ? recursiveMkdirPath(dir) : dir, { recursive: true, mode: dirMode });
277
277
  applyDirectoryModeSync({ fsModule, dirPath: dir, mode: dirMode, fchmodSync });
@@ -327,13 +327,13 @@ function replaceFileAtomicSyncUnserialized(options, filePath, renameIdentity) {
327
327
  return result;
328
328
  }
329
329
  catch (error) {
330
- originalError = error;
330
+ originalFailure = { error };
331
331
  throw error;
332
332
  }
333
333
  finally {
334
334
  tempOwner.finish({
335
335
  fsModule,
336
- originalError,
336
+ originalFailure,
337
337
  throwOnCleanupError: options.throwOnCleanupError === true,
338
338
  });
339
339
  }
@@ -0,0 +1,26 @@
1
+ import type { BigIntStats } from "node:fs";
2
+ import type { NativeBinding, NativeOwnedTreeRemovalResult } from "./native-binding.js";
3
+ type RenameBinding = NativeBinding & Required<Pick<NativeBinding, "renameNoReplaceWithIdentity">>;
4
+ export type RetainedReplacementParent = Readonly<{
5
+ path: string;
6
+ realPath: string;
7
+ identity: Pick<BigIntStats, "dev" | "ino">;
8
+ }>;
9
+ export declare class RetainedDirectoryReplacement {
10
+ #private;
11
+ private constructor();
12
+ static requireBinding(): RenameBinding;
13
+ static retain(params: {
14
+ targetParent: RetainedReplacementParent;
15
+ stagedParent: RetainedReplacementParent;
16
+ originalTarget?: Readonly<{
17
+ path: string;
18
+ identity: Pick<BigIntStats, "dev" | "ino">;
19
+ }>;
20
+ }, binding?: RenameBinding): RetainedDirectoryReplacement;
21
+ renameNoReplace(sourceParent: "staged" | "target", sourceBasename: string, targetBasename: string, expectedSource: Pick<BigIntStats, "dev" | "ino">): void;
22
+ removeOriginal(backupBasename: string): Promise<NativeOwnedTreeRemovalResult["outcome"]>;
23
+ close(): void;
24
+ }
25
+ export {};
26
+ //# sourceMappingURL=retained-directory-replacement.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"retained-directory-replacement.d.ts","sourceRoot":"","sources":["../src/retained-directory-replacement.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAI3C,OAAO,KAAK,EAAE,aAAa,EAAE,4BAA4B,EAAE,MAAM,qBAAqB,CAAC;AAGvF,KAAK,aAAa,GAAG,aAAa,GAAG,QAAQ,CAAC,IAAI,CAAC,aAAa,EAC9D,6BAA6B,CAAC,CAAC,CAAC;AAIlC,MAAM,MAAM,yBAAyB,GAAG,QAAQ,CAAC;IAC/C,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,IAAI,CAAC,WAAW,EAAE,KAAK,GAAG,KAAK,CAAC,CAAC;CAC5C,CAAC,CAAC;AAsHH,qBAAa,4BAA4B;;IAQvC,OAAO,eAUN;IAED,MAAM,CAAC,cAAc,IAAI,aAAa,CAQrC;IAED,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE;QACpB,YAAY,EAAE,yBAAyB,CAAC;QACxC,YAAY,EAAE,yBAAyB,CAAC;QACxC,cAAc,CAAC,EAAE,QAAQ,CAAC;YACxB,IAAI,EAAE,MAAM,CAAC;YACb,QAAQ,EAAE,IAAI,CAAC,WAAW,EAAE,KAAK,GAAG,KAAK,CAAC,CAAC;SAC5C,CAAC,CAAC;KACJ,EAAE,OAAO,gBAAgD,GAAG,4BAA4B,CAiDxF;IAED,eAAe,CACb,YAAY,EAAE,QAAQ,GAAG,QAAQ,EACjC,cAAc,EAAE,MAAM,EACtB,cAAc,EAAE,MAAM,EACtB,cAAc,EAAE,IAAI,CAAC,WAAW,EAAE,KAAK,GAAG,KAAK,CAAC,GAC/C,IAAI,CAYN;IAEK,cAAc,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,4BAA4B,CAAC,SAAS,CAAC,CAAC,CA2B7F;IAED,KAAK,IAAI,IAAI,CAQZ;CACF"}
@@ -0,0 +1,193 @@
1
+ import fsSync from "node:fs";
2
+ import { assertDirectoryIdentitySync } from "./directory-guard.js";
3
+ import { FsSafeError } from "./errors.js";
4
+ import { getNativeBinding } from "./native.js";
5
+ function helperUnavailable(message, cause) {
6
+ return new FsSafeError("helper-unavailable", message, {
7
+ ...(cause instanceof Error ? { cause } : {}),
8
+ });
9
+ }
10
+ function copyOperationalCode(target, source) {
11
+ const code = source?.code;
12
+ if (typeof code === "string") {
13
+ Object.defineProperty(target, "code", { configurable: true, value: code });
14
+ }
15
+ }
16
+ function primaryWithCloseFailure(error, closeError, message) {
17
+ const cause = new AggregateError([error, closeError], message);
18
+ if (error instanceof FsSafeError) {
19
+ return new FsSafeError(error.code, message, {
20
+ cause,
21
+ ...(error.details ? { details: error.details } : {}),
22
+ });
23
+ }
24
+ const wrapped = new Error(message, { cause });
25
+ copyOperationalCode(wrapped, error);
26
+ return wrapped;
27
+ }
28
+ function supportsRename(binding) {
29
+ return typeof binding?.renameNoReplaceWithIdentity === "function";
30
+ }
31
+ function supportsCleanup(binding) {
32
+ return typeof binding.ownedTreeRemovalAvailable === "function" &&
33
+ typeof binding.removeOwnedTree === "function";
34
+ }
35
+ function closeDescriptors(descriptors) {
36
+ const errors = [];
37
+ const closed = new Set();
38
+ for (const fd of descriptors) {
39
+ if (fd === undefined || closed.has(fd))
40
+ continue;
41
+ closed.add(fd);
42
+ try {
43
+ fsSync.closeSync(fd);
44
+ }
45
+ catch (error) {
46
+ errors.push(error);
47
+ }
48
+ }
49
+ if (errors.length === 1)
50
+ throw errors[0];
51
+ if (errors.length > 1) {
52
+ throw new AggregateError(errors, "retained directory replacement descriptors failed to close");
53
+ }
54
+ }
55
+ const retainedDirectoryOpenFlags = fsSync.constants.O_RDONLY |
56
+ fsSync.constants.O_DIRECTORY |
57
+ fsSync.constants.O_NOFOLLOW |
58
+ fsSync.constants.O_NONBLOCK;
59
+ function openRetainedDirectory(label, expected) {
60
+ let fd;
61
+ try {
62
+ fd = fsSync.openSync(expected.path, retainedDirectoryOpenFlags);
63
+ }
64
+ catch (error) {
65
+ const code = error?.code;
66
+ if (code === "EACCES" || code === "EPERM") {
67
+ const purpose = label === "original target"
68
+ ? "original target for bounded cleanup"
69
+ : label;
70
+ throw helperUnavailable(`atomic directory replacement cannot retain the ${purpose}`, error);
71
+ }
72
+ throw error;
73
+ }
74
+ try {
75
+ const descriptor = fsSync.fstatSync(fd, { bigint: true });
76
+ if (!descriptor.isDirectory()) {
77
+ throw new FsSafeError("not-file", `directory replacement ${label} must be a directory`);
78
+ }
79
+ if (descriptor.dev !== expected.identity.dev || descriptor.ino !== expected.identity.ino) {
80
+ throw new FsSafeError("path-mismatch", `directory replacement ${label} changed during admission`);
81
+ }
82
+ assertDirectoryIdentitySync(expected.path, {
83
+ dev: expected.identity.dev,
84
+ ino: expected.identity.ino,
85
+ ...(expected.realPath === undefined ? {} : { realPath: expected.realPath }),
86
+ });
87
+ }
88
+ catch (error) {
89
+ try {
90
+ fsSync.closeSync(fd);
91
+ }
92
+ catch (closeError) {
93
+ throw primaryWithCloseFailure(error, closeError, `directory replacement ${label} admission and close failed`);
94
+ }
95
+ throw error;
96
+ }
97
+ return fd;
98
+ }
99
+ export class RetainedDirectoryReplacement {
100
+ #binding;
101
+ #originalDirectoryFd;
102
+ #stagedParentFd;
103
+ #targetParentFd;
104
+ #cleanupUsed = false;
105
+ #closed = false;
106
+ constructor(binding, targetParentFd, stagedParentFd, originalDirectoryFd) {
107
+ this.#binding = binding;
108
+ this.#targetParentFd = targetParentFd;
109
+ this.#stagedParentFd = stagedParentFd;
110
+ this.#originalDirectoryFd = originalDirectoryFd;
111
+ }
112
+ static requireBinding() {
113
+ const binding = getNativeBinding();
114
+ if (!supportsRename(binding)) {
115
+ throw helperUnavailable("atomic directory replacement requires native identity-fenced no-replace rename support");
116
+ }
117
+ return binding;
118
+ }
119
+ static retain(params, binding = RetainedDirectoryReplacement.requireBinding()) {
120
+ let targetParentFd;
121
+ let stagedParentFd;
122
+ let originalDirectoryFd;
123
+ try {
124
+ targetParentFd = openRetainedDirectory("target parent", params.targetParent);
125
+ stagedParentFd = params.stagedParent.path === params.targetParent.path
126
+ ? targetParentFd
127
+ : openRetainedDirectory("staged parent", params.stagedParent);
128
+ if (params.originalTarget) {
129
+ if (!supportsCleanup(binding)) {
130
+ throw helperUnavailable("replacing an existing directory requires native bounded owned-tree cleanup");
131
+ }
132
+ let available = false;
133
+ try {
134
+ available = binding.ownedTreeRemovalAvailable(targetParentFd) === true;
135
+ }
136
+ catch (error) {
137
+ throw helperUnavailable("native owned-tree cleanup capability probe failed", error);
138
+ }
139
+ if (!available) {
140
+ throw helperUnavailable("native owned-tree cleanup is unavailable for the target parent");
141
+ }
142
+ originalDirectoryFd = openRetainedDirectory("original target", {
143
+ path: params.originalTarget.path,
144
+ identity: params.originalTarget.identity,
145
+ });
146
+ }
147
+ return new RetainedDirectoryReplacement(binding, targetParentFd, stagedParentFd, originalDirectoryFd);
148
+ }
149
+ catch (error) {
150
+ try {
151
+ closeDescriptors([originalDirectoryFd, stagedParentFd, targetParentFd]);
152
+ }
153
+ catch (closeError) {
154
+ throw primaryWithCloseFailure(error, closeError, "retained directory replacement admission and descriptor close failed");
155
+ }
156
+ throw error;
157
+ }
158
+ }
159
+ renameNoReplace(sourceParent, sourceBasename, targetBasename, expectedSource) {
160
+ if (this.#closed) {
161
+ throw new FsSafeError("path-mismatch", "retained directory rename authority is unavailable");
162
+ }
163
+ this.#binding.renameNoReplaceWithIdentity(sourceParent === "staged" ? this.#stagedParentFd : this.#targetParentFd, sourceBasename, this.#targetParentFd, targetBasename, expectedSource.dev, expectedSource.ino);
164
+ }
165
+ async removeOriginal(backupBasename) {
166
+ if (this.#closed || this.#cleanupUsed || this.#originalDirectoryFd === undefined ||
167
+ !supportsCleanup(this.#binding)) {
168
+ throw new FsSafeError("path-mismatch", "retained owned-tree cleanup authority is unavailable");
169
+ }
170
+ this.#cleanupUsed = true;
171
+ const result = await this.#binding.removeOwnedTree(this.#targetParentFd, backupBasename, this.#originalDirectoryFd);
172
+ if (result.errorCode) {
173
+ if (result.errorCode === "path-mismatch") {
174
+ throw new FsSafeError("path-mismatch", result.errorMessage ?? "native owned-tree cleanup lost retained identity");
175
+ }
176
+ throw Object.assign(new Error(result.errorMessage ?? "native owned-tree cleanup failed"), { code: result.errorCode });
177
+ }
178
+ if (result.outcome !== "removed" && result.outcome !== "preserved") {
179
+ throw helperUnavailable("native owned-tree cleanup returned an invalid result");
180
+ }
181
+ return result.outcome;
182
+ }
183
+ close() {
184
+ if (this.#closed)
185
+ return;
186
+ this.#closed = true;
187
+ closeDescriptors([
188
+ this.#originalDirectoryFd,
189
+ this.#stagedParentFd,
190
+ this.#targetParentFd,
191
+ ]);
192
+ }
193
+ }
@@ -1,3 +1,4 @@
1
+ export declare function sameNormalizedPathSpelling(left: string, right: string): boolean;
1
2
  export type RootBoundaryIdentity = Readonly<{
2
3
  dev: number | bigint;
3
4
  ino: number | bigint;
@@ -1 +1 @@
1
- {"version":3,"file":"root-boundary.d.ts","sourceRoot":"","sources":["../src/root-boundary.ts"],"names":[],"mappings":"AAKA,MAAM,MAAM,oBAAoB,GAAG,QAAQ,CAAC;IAC1C,GAAG,EAAE,MAAM,GAAG,MAAM,CAAC;IACrB,GAAG,EAAE,MAAM,GAAG,MAAM,CAAC;CACtB,CAAC,CAAC;AAEH,MAAM,MAAM,gBAAgB,GAAG,QAAQ,CAAC;IACtC,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,OAAO,GAAG,UAAU,CAAC;CACjC,CAAC,CAAC;AAwHH;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE;IAC1C,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,CAAC,EAAE,oBAAoB,CAAC;IACpC,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,aAAa,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACrC,oBAAoB,CAAC,EAAE,CACrB,iBAAiB,EAAE,MAAM,EACzB,QAAQ,EAAE,QAAQ,CAAC;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC,KAC7C,IAAI,CAAC;CACX,GAAG,gBAAgB,GAAG,SAAS,CAiE/B"}
1
+ {"version":3,"file":"root-boundary.d.ts","sourceRoot":"","sources":["../src/root-boundary.ts"],"names":[],"mappings":"AAKA,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAG/E;AAED,MAAM,MAAM,oBAAoB,GAAG,QAAQ,CAAC;IAC1C,GAAG,EAAE,MAAM,GAAG,MAAM,CAAC;IACrB,GAAG,EAAE,MAAM,GAAG,MAAM,CAAC;CACtB,CAAC,CAAC;AAEH,MAAM,MAAM,gBAAgB,GAAG,QAAQ,CAAC;IACtC,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,OAAO,GAAG,UAAU,CAAC;CACjC,CAAC,CAAC;AAwHH;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE;IAC1C,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,CAAC,EAAE,oBAAoB,CAAC;IACpC,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,aAAa,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACrC,oBAAoB,CAAC,EAAE,CACrB,iBAAiB,EAAE,MAAM,EACzB,QAAQ,EAAE,QAAQ,CAAC;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC,KAC7C,IAAI,CAAC;CACX,GAAG,gBAAgB,GAAG,SAAS,CAiE/B"}
@@ -2,6 +2,10 @@ import path from "node:path";
2
2
  import { inspectDirectoryIdentitySync } from "./directory-guard.js";
3
3
  import { isPathRelativeEscape } from "./path.js";
4
4
  import { realpathSync } from "./realpath.js";
5
+ export function sameNormalizedPathSpelling(left, right) {
6
+ // Preserve case on Windows, where individual directories can be case-sensitive.
7
+ return path.resolve(left) === path.resolve(right);
8
+ }
5
9
  function windowsComparablePath(value, normalize) {
6
10
  const original = normalize
7
11
  ? path.win32.normalize(value.replaceAll("/", "\\"))
@@ -41,12 +41,4 @@ export declare function resolvePathInRoot(root: RootContext, relativePath: strin
41
41
  rootWithSep: string;
42
42
  resolved: string;
43
43
  }>;
44
- export declare function resolvePathWithinRoot(params: {
45
- rootDir: string;
46
- relativePath: string;
47
- }): Promise<{
48
- rootReal: string;
49
- rootWithSep: string;
50
- resolved: string;
51
- }>;
52
44
  //# sourceMappingURL=root-context.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"root-context.d.ts","sourceRoot":"","sources":["../src/root-context.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,EAAE,EAAE,KAAK,WAAW,EAAE,MAAM,SAAS,CAAC;AAG/C,OAAO,EAIL,KAAK,mBAAmB,EACxB,KAAK,yBAAyB,EAC/B,MAAM,sBAAsB,CAAC;AAuB9B,MAAM,MAAM,WAAW,GAAG;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,mBAAmB,CAAC,WAAW,CAAC,CAAC;IAC7C,YAAY,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,GAAG;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC;IAC1E,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;CACrB,CAAC;AAQF,eAAO,MAAM,iBAAiB,UAAW,MAAM,WACM,CAAC;AAEtD,wBAAgB,2BAA2B,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAOtE;AAED,wBAAgB,8BAA8B,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAKzE;AAMD,wBAAsB,0BAA0B,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAyBtF;AAED,wBAAsB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAiC9E;AAED,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAyBhF;AAED,wBAAgB,6BAA6B,CAC3C,IAAI,EAAE,WAAW,EACjB,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,WAAW,KAAK,IAAI,GACvC,IAAI,CAmBN;AAED,wBAAsB,yBAAyB,CAC7C,IAAI,EAAE,WAAW,EACjB,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,WAAW,KAAK,IAAI,GACvC,OAAO,CAAC,IAAI,CAAC,CAEf;AAED;;;;;GAKG;AACH,wBAAsB,0BAA0B,CAC9C,IAAI,EAAE,WAAW,GAChB,OAAO,CAAC,yBAAyB,GAAG,SAAS,CAAC,CAYhD;AAED,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,WAAW,EACjB,YAAY,EAAE,MAAM,EACpB,OAAO,CAAC,EAAE;IACR,cAAc,CAAC,EAAE,mBAAmB,GAAG,YAAY,CAAC;IACpD,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,uBAAuB,CAAC,EAAE,OAAO,CAAC;IAClC,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,sBAAsB,CAAC,EAAE,OAAO,CAAC;CAClC,GACA,OAAO,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CAuEtE;AAED,wBAAsB,qBAAqB,CAAC,MAAM,EAAE;IAClD,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;CACtB,GAAG,OAAO,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CAEvE"}
1
+ {"version":3,"file":"root-context.d.ts","sourceRoot":"","sources":["../src/root-context.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,EAAE,EAAE,KAAK,WAAW,EAAE,MAAM,SAAS,CAAC;AAG/C,OAAO,EAIL,KAAK,mBAAmB,EACxB,KAAK,yBAAyB,EAC/B,MAAM,sBAAsB,CAAC;AAuB9B,MAAM,MAAM,WAAW,GAAG;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,mBAAmB,CAAC,WAAW,CAAC,CAAC;IAC7C,YAAY,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,GAAG;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC;IAC1E,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;CACrB,CAAC;AAQF,eAAO,MAAM,iBAAiB,UAAW,MAAM,WACM,CAAC;AAEtD,wBAAgB,2BAA2B,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAOtE;AAED,wBAAgB,8BAA8B,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAKzE;AAMD,wBAAsB,0BAA0B,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAyBtF;AAED,wBAAsB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAiC9E;AAED,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAyBhF;AAED,wBAAgB,6BAA6B,CAC3C,IAAI,EAAE,WAAW,EACjB,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,WAAW,KAAK,IAAI,GACvC,IAAI,CAmBN;AAED,wBAAsB,yBAAyB,CAC7C,IAAI,EAAE,WAAW,EACjB,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,WAAW,KAAK,IAAI,GACvC,OAAO,CAAC,IAAI,CAAC,CAEf;AAED;;;;;GAKG;AACH,wBAAsB,0BAA0B,CAC9C,IAAI,EAAE,WAAW,GAChB,OAAO,CAAC,yBAAyB,GAAG,SAAS,CAAC,CAYhD;AAED,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,WAAW,EACjB,YAAY,EAAE,MAAM,EACpB,OAAO,CAAC,EAAE;IACR,cAAc,CAAC,EAAE,mBAAmB,GAAG,YAAY,CAAC;IACpD,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,uBAAuB,CAAC,EAAE,OAAO,CAAC;IAClC,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,sBAAsB,CAAC,EAAE,OAAO,CAAC;CAClC,GACA,OAAO,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CAuEtE"}
@@ -234,6 +234,3 @@ export async function resolvePathInRoot(root, relativePath, options) {
234
234
  }
235
235
  return { rootReal: root.rootReal, rootWithSep: root.rootWithSep, resolved };
236
236
  }
237
- export async function resolvePathWithinRoot(params) {
238
- return await resolvePathInRoot(await resolveRootContext(params.rootDir), params.relativePath);
239
- }
@@ -1,10 +1,17 @@
1
1
  import type { PinnedWriteInput } from "./pinned-write.js";
2
2
  import type { RootCreateOptions, RootCreateStreamOptions, RootWriteOptions } from "./root-options.js";
3
- export type RootWriteParams = RootWriteOptions & RootCreateStreamOptions & {
3
+ declare const exclusiveSidecarCreate: unique symbol;
4
+ export declare const sidecarExclusiveCreate: Readonly<{
5
+ [exclusiveSidecarCreate]: true;
6
+ }>;
7
+ export type RootWriteParams = RootWriteOptions & RootCreateOptions & RootCreateStreamOptions & {
4
8
  relativePath: string;
5
9
  data: string | Buffer | AsyncIterable<Uint8Array>;
10
+ strictFileSync?: boolean;
11
+ [exclusiveSidecarCreate]?: true;
6
12
  };
7
13
  export declare function rethrowCreateInputError(error: unknown): never;
8
14
  export declare function createInputOptions(data: RootWriteParams["data"], options: RootCreateOptions & RootCreateStreamOptions, defaultMaxBytes?: number): RootCreateOptions & RootCreateStreamOptions;
9
15
  export declare function rootWriteInput(params: RootWriteParams): PinnedWriteInput;
16
+ export {};
10
17
  //# sourceMappingURL=root-create-input.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"root-create-input.d.ts","sourceRoot":"","sources":["../src/root-create-input.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAC1D,OAAO,KAAK,EAAE,iBAAiB,EAAE,uBAAuB,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAEtG,MAAM,MAAM,eAAe,GAAG,gBAAgB,GAAG,uBAAuB,GAAG;IACzE,YAAY,EAAE,MAAM,CAAC;IACrB,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,aAAa,CAAC,UAAU,CAAC,CAAC;CACnD,CAAC;AAQF,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,CAG7D;AAED,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,eAAe,CAAC,MAAM,CAAC,EAC7B,OAAO,EAAE,iBAAiB,GAAG,uBAAuB,EACpD,eAAe,CAAC,EAAE,MAAM,GACvB,iBAAiB,GAAG,uBAAuB,CAe7C;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,eAAe,GAAG,gBAAgB,CAwCxE"}
1
+ {"version":3,"file":"root-create-input.d.ts","sourceRoot":"","sources":["../src/root-create-input.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAC1D,OAAO,KAAK,EAAE,iBAAiB,EAAE,uBAAuB,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAItG,QAAA,MAAM,sBAAsB,eAAmC,CAAC;AAChE,eAAO,MAAM,sBAAsB;;EAA6D,CAAC;AAEjG,MAAM,MAAM,eAAe,GAAG,gBAAgB,GAAG,iBAAiB,GAAG,uBAAuB,GAAG;IAC7F,YAAY,EAAE,MAAM,CAAC;IACrB,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,aAAa,CAAC,UAAU,CAAC,CAAC;IAClD,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,CAAC,sBAAsB,CAAC,CAAC,EAAE,IAAI,CAAC;CACjC,CAAC;AAQF,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,CAG7D;AAED,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,eAAe,CAAC,MAAM,CAAC,EAC7B,OAAO,EAAE,iBAAiB,GAAG,uBAAuB,EACpD,eAAe,CAAC,EAAE,MAAM,GACvB,iBAAiB,GAAG,uBAAuB,CAkB7C;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,eAAe,GAAG,gBAAgB,CA8CxE"}
@@ -1,6 +1,10 @@
1
1
  import { normalizeMaxBytes } from "./byte-budget.js";
2
2
  import { FsSafeError } from "./errors.js";
3
3
  import { MutationAuthorityError } from "./mutation-authority.js";
4
+ // Lock records need exclusive creation, not private-before-visible publication.
5
+ // Keep this composition hook off the public Root options and package exports.
6
+ const exclusiveSidecarCreate = Symbol("exclusiveSidecarCreate");
7
+ export const sidecarExclusiveCreate = Object.freeze({ [exclusiveSidecarCreate]: true });
4
8
  class CreateInputError extends FsSafeError {
5
9
  rejection;
6
10
  constructor(rejection) {
@@ -16,24 +20,33 @@ export function rethrowCreateInputError(error) {
16
20
  export function createInputOptions(data, options, defaultMaxBytes) {
17
21
  if (typeof data === "string" || Buffer.isBuffer(data))
18
22
  return options;
19
- options.signal?.throwIfAborted();
23
+ const { signal } = options;
24
+ signal?.throwIfAborted();
20
25
  if (options.encoding !== undefined || options.renameIdentity !== undefined) {
21
26
  throw new TypeError("streamed create does not accept encoding or renameIdentity");
22
27
  }
23
28
  const maxBytes = normalizeMaxBytes(options.maxBytes, { defaultValue: defaultMaxBytes });
29
+ const { assertBeforeMutation } = options;
24
30
  return {
25
31
  ...options,
26
32
  maxBytes,
33
+ signal,
27
34
  assertBeforeMutation: () => {
28
- options.signal?.throwIfAborted();
29
- return options.assertBeforeMutation?.();
35
+ signal?.throwIfAborted();
36
+ return assertBeforeMutation == null ? undefined : Reflect.apply(assertBeforeMutation, options, []);
30
37
  },
31
38
  };
32
39
  }
33
40
  export function rootWriteInput(params) {
41
+ if (params.atomic !== undefined && typeof params.atomic !== "boolean") {
42
+ throw new TypeError("atomic must be a boolean");
43
+ }
34
44
  const data = params.data;
35
45
  if (typeof data === "string" || Buffer.isBuffer(data)) {
36
- return { kind: "buffer", data, encoding: params.encoding };
46
+ return {
47
+ kind: "buffer", data, encoding: params.encoding,
48
+ stageBeforePublish: params[exclusiveSidecarCreate] ? false : params.atomic === true ? true : undefined,
49
+ };
37
50
  }
38
51
  return {
39
52
  kind: "stream",
@@ -1,13 +1,13 @@
1
1
  import type { PinnedWriteMutationAdmission } from "./pinned-write.js";
2
- import type { RootContext } from "./root-context.js";
2
+ import { type RootContext } from "./root-context.js";
3
3
  /**
4
4
  * Complete the common Root.mkdir case with one exact existing parent. This is
5
5
  * optional evidence: every uncertain probe returns to the ordered root walk.
6
6
  */
7
7
  export declare function tryMkdirAtExactParent(root: RootContext, targetPath: string, mutationAdmission: PinnedWriteMutationAdmission): Promise<boolean>;
8
- export declare function prepareRootWriteTarget(root: RootContext, targetPath: string, assertBeforeMutation?: () => void, mutationAdmission?: PinnedWriteMutationAdmission): Promise<string>;
8
+ export declare function prepareRootWriteTarget(root: RootContext, targetPath: string, assertBeforeMutation?: () => void, mutationAdmission?: PinnedWriteMutationAdmission, privateMode?: boolean): Promise<string>;
9
9
  export declare function mkdirPathFallback(root: RootContext, resolved: {
10
10
  rootReal: string;
11
11
  resolved: string;
12
- }, assertBeforeMutation?: () => void, rejectSymlinks?: boolean, mutationAdmission?: PinnedWriteMutationAdmission): Promise<void>;
12
+ }, assertBeforeMutation?: () => void, rejectSymlinks?: boolean, mutationAdmission?: PinnedWriteMutationAdmission, privateMode?: boolean): Promise<void>;
13
13
  //# sourceMappingURL=root-directory-creation.d.ts.map