@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
@@ -0,0 +1,393 @@
1
+ import fs from "node:fs";
2
+ import { getSyncLockAdmissions } from "./file-lock-sync-admission.js";
3
+ import { FsSafeError } from "./errors.js";
4
+ import { parseSidecarLockPayload, sidecarLockSnapshotMatches, } from "./sidecar-lock-reclaim.js";
5
+ import { createSuppressedError } from "./suppressed-error.js";
6
+ import {} from "./file-lock-sync-root.js";
7
+ import { fileLockSyncRootReceiptStillCurrent, fileLockSyncRootSnapshotStillCurrent, readFileLockSyncRootSnapshot, } from "./file-lock-sync-root-io.js";
8
+ import { removeFileLockSyncRootFile } from "./file-lock-sync-root-mutation.js";
9
+ import { ensureFileLockSyncRootExitCleanupRegistered } from "./file-lock-sync-root-registration.js";
10
+ const ROOT_SYNC_HELD_LOCKS_KEY = Symbol.for("fsSafe.syncRootSidecarLocks.v1");
11
+ const ROOT_SYNC_CLEANUP_ACTIVE_KEY = Symbol.for("fsSafe.syncRootSidecarLockCleanupActive.v1");
12
+ const MAX_CONTINUITY_CLEANUP_ATTEMPTS = 2;
13
+ const rootSyncHandleDispositions = new WeakMap();
14
+ function readRootSyncHeldReleaseState(held) {
15
+ return held.releaseState;
16
+ }
17
+ export function readRootSidecarSnapshotSync(rootPath, parsePayload, onOpenFailure, expectedReceipt) {
18
+ const rawSnapshot = readFileLockSyncRootSnapshot(rootPath, {
19
+ expectedReceipt,
20
+ onOpenFailure,
21
+ });
22
+ if (!rawSnapshot)
23
+ return null;
24
+ let parsedPayload;
25
+ try {
26
+ parsedPayload = parseSidecarLockPayload(rawSnapshot.snapshot.raw, parsePayload);
27
+ }
28
+ catch (parseError) {
29
+ if (parsePayload) {
30
+ try {
31
+ if (!fileLockSyncRootSnapshotStillCurrent(rootPath, rawSnapshot)) {
32
+ throw new FsSafeError("path-mismatch", "sidecar changed during payload parsing");
33
+ }
34
+ }
35
+ catch (admissionError) {
36
+ throw createSuppressedError(parseError, admissionError, "sidecar parser and post-callback admission both failed");
37
+ }
38
+ }
39
+ throw parseError;
40
+ }
41
+ if (parsePayload && !fileLockSyncRootSnapshotStillCurrent(rootPath, rawSnapshot)) {
42
+ return null;
43
+ }
44
+ return {
45
+ receipt: rawSnapshot.receipt,
46
+ snapshot: { ...rawSnapshot.snapshot, payload: parsedPayload },
47
+ };
48
+ }
49
+ export function getRootSyncHeldLocks() {
50
+ const globalWithState = globalThis;
51
+ if (!globalWithState[ROOT_SYNC_HELD_LOCKS_KEY]) {
52
+ globalWithState[ROOT_SYNC_HELD_LOCKS_KEY] = new Map();
53
+ }
54
+ return globalWithState[ROOT_SYNC_HELD_LOCKS_KEY];
55
+ }
56
+ function existingRootSyncHeldLocks() {
57
+ return globalThis[ROOT_SYNC_HELD_LOCKS_KEY];
58
+ }
59
+ function reactivateExactHeldLock(heldLocks, held, restoreConsumedReference) {
60
+ if (heldLocks.get(held.normalizedTargetPath) !== held || held.releaseState === "released") {
61
+ return false;
62
+ }
63
+ if (restoreConsumedReference)
64
+ held.refCount += 1;
65
+ held.releaseState = "active";
66
+ held.revision += 1;
67
+ return true;
68
+ }
69
+ function finishDeferredExitReleases(held) {
70
+ const deferred = held.deferredExitReleases;
71
+ if (!deferred)
72
+ return;
73
+ for (const disposition of deferred)
74
+ disposition.released = true;
75
+ deferred.clear();
76
+ held.deferredExitReleases = undefined;
77
+ }
78
+ function restoreDeferredExitReleases(held) {
79
+ const deferred = held.deferredExitReleases;
80
+ if (!deferred)
81
+ return 0;
82
+ const restored = deferred.size;
83
+ deferred.clear();
84
+ held.deferredExitReleases = undefined;
85
+ return restored;
86
+ }
87
+ function settleDeferredExitRelease(lock) {
88
+ const disposition = rootSyncHandleDispositions.get(lock);
89
+ if (!disposition || disposition.released)
90
+ return;
91
+ const deferred = disposition.held.deferredExitReleases;
92
+ // Scoped disposal may surrender only its own reference, after release has
93
+ // proven that exact handle token was tentatively consumed by exit cleanup.
94
+ if (!deferred?.delete(disposition))
95
+ return;
96
+ if (deferred.size === 0)
97
+ disposition.held.deferredExitReleases = undefined;
98
+ disposition.released = true;
99
+ }
100
+ function restoreExactHeldLockAfterExitCleanup(heldLocks, held) {
101
+ if (heldLocks.get(held.normalizedTargetPath) !== held || held.releaseState === "released")
102
+ return;
103
+ // Restore exactly the handle references tentatively consumed by exit
104
+ // callbacks. A retained successor is a distinct reference and must not be
105
+ // borrowed by a pending original handle.
106
+ held.refCount += restoreDeferredExitReleases(held);
107
+ if (held.refCount <= 0) {
108
+ // No tracked handle can retry. Drop only the record; the sidecar itself is
109
+ // deliberately left in place for policy-governed reclamation.
110
+ held.releaseState = "released";
111
+ held.revision += 1;
112
+ heldLocks.delete(held.normalizedTargetPath);
113
+ return;
114
+ }
115
+ held.releaseState = "active";
116
+ held.revision += 1;
117
+ }
118
+ function releaseAllRootSyncHeldLocks() {
119
+ const globalWithCleanup = globalThis;
120
+ if (globalWithCleanup[ROOT_SYNC_CLEANUP_ACTIVE_KEY])
121
+ return;
122
+ globalWithCleanup[ROOT_SYNC_CLEANUP_ACTIVE_KEY] = true;
123
+ try {
124
+ // A newListener callback can invoke the candidate handler before
125
+ // registration succeeds. Do not create acquisition state in that window.
126
+ const heldLocks = existingRootSyncHeldLocks();
127
+ if (!heldLocks)
128
+ return;
129
+ getSyncLockAdmissions().clear();
130
+ for (const [normalizedTargetPath, held] of heldLocks) {
131
+ if (heldLocks.get(normalizedTargetPath) !== held || held.releaseState !== "active")
132
+ continue;
133
+ held.releaseState = "exit-cleaning";
134
+ held.revision += 1;
135
+ let cleanupRevision = held.revision;
136
+ const timer = held.timer;
137
+ const fd = held.fd;
138
+ // Consume descriptor/timer ownership before either operation can invoke
139
+ // instrumented callbacks or recursively enter the public cleanup hook.
140
+ held.timer = undefined;
141
+ held.fd = undefined;
142
+ if (timer)
143
+ clearInterval(timer);
144
+ try {
145
+ if (fd !== undefined)
146
+ fs.closeSync(fd);
147
+ }
148
+ catch {
149
+ // Best-effort process-exit cleanup.
150
+ }
151
+ const entryIsContinuous = () => heldLocks.get(normalizedTargetPath) === held &&
152
+ held.releaseState === "exit-cleaning" &&
153
+ held.revision === cleanupRevision;
154
+ const beginFreshCleanupAttempt = () => {
155
+ held.releaseState = "exit-cleaning";
156
+ held.revision += 1;
157
+ cleanupRevision = held.revision;
158
+ };
159
+ if (!entryIsContinuous()) {
160
+ if (heldLocks.get(normalizedTargetPath) === held &&
161
+ readRootSyncHeldReleaseState(held) !== "released" && held.refCount === 0) {
162
+ beginFreshCleanupAttempt();
163
+ }
164
+ else {
165
+ restoreExactHeldLockAfterExitCleanup(heldLocks, held);
166
+ continue;
167
+ }
168
+ }
169
+ let cleaned = false;
170
+ for (let cleanupAttempt = 0; cleanupAttempt < MAX_CONTINUITY_CLEANUP_ATTEMPTS; cleanupAttempt += 1) {
171
+ let continuityInterrupted = false;
172
+ let removalFailed = false;
173
+ let removed = false;
174
+ try {
175
+ removed = removeFileLockSyncRootFile(held.rootPath, held.rootReceipt, held.snapshot, () => {
176
+ const current = entryIsContinuous();
177
+ if (!current)
178
+ continuityInterrupted = true;
179
+ return current;
180
+ });
181
+ }
182
+ catch {
183
+ // A surviving sidecar fails closed and can be reclaimed by policy.
184
+ removalFailed = true;
185
+ }
186
+ if (!removalFailed && removed && !continuityInterrupted && entryIsContinuous()) {
187
+ cleaned = true;
188
+ break;
189
+ }
190
+ const continuousEntry = entryIsContinuous();
191
+ const exactEntry = heldLocks.get(normalizedTargetPath) === held &&
192
+ readRootSyncHeldReleaseState(held) !== "released";
193
+ if (exactEntry && (continuityInterrupted || !continuousEntry) &&
194
+ held.refCount === 0 && cleanupAttempt + 1 < MAX_CONTINUITY_CLEANUP_ATTEMPTS) {
195
+ beginFreshCleanupAttempt();
196
+ continue;
197
+ }
198
+ if (continuousEntry && held.refCount > 0) {
199
+ // Preserve established best-effort process-exit behavior: after
200
+ // authority-safe removal declines or throws, discard the live record
201
+ // without attempting any less-restricted deletion.
202
+ cleaned = true;
203
+ break;
204
+ }
205
+ restoreExactHeldLockAfterExitCleanup(heldLocks, held);
206
+ break;
207
+ }
208
+ if (!cleaned)
209
+ continue;
210
+ finishDeferredExitReleases(held);
211
+ held.releaseState = "released";
212
+ held.revision += 1;
213
+ if (heldLocks.get(normalizedTargetPath) === held) {
214
+ heldLocks.delete(normalizedTargetPath);
215
+ }
216
+ }
217
+ }
218
+ finally {
219
+ globalWithCleanup[ROOT_SYNC_CLEANUP_ACTIVE_KEY] = false;
220
+ }
221
+ }
222
+ export function ensureRootSyncExitCleanupRegistered() {
223
+ ensureFileLockSyncRootExitCleanupRegistered(releaseAllRootSyncHeldLocks);
224
+ }
225
+ export function verifyRootSyncHeldLock(held) {
226
+ const current = readRootSidecarSnapshotSync(held.rootPath, held.parsePayload, undefined, held.rootReceipt);
227
+ if (!current)
228
+ return false;
229
+ return current.receipt.identity.dev === held.rootReceipt.identity.dev &&
230
+ current.receipt.identity.ino === held.rootReceipt.identity.ino &&
231
+ sidecarLockSnapshotMatches(current.snapshot, held.snapshot);
232
+ }
233
+ function releaseRootSyncHeldLock(held, disposition) {
234
+ const heldLocks = getRootSyncHeldLocks();
235
+ if (heldLocks.get(held.normalizedTargetPath) !== held || held.releaseState === "released")
236
+ return true;
237
+ if (held.releaseState !== "active") {
238
+ if (held.deferredExitReleases?.has(disposition))
239
+ return false;
240
+ if (held.refCount <= 0)
241
+ return false;
242
+ if (!fileLockSyncRootReceiptStillCurrent(held.rootPath, held.rootReceipt)) {
243
+ throw new FsSafeError("path-mismatch", "held sidecar lock changed before release");
244
+ }
245
+ held.refCount -= 1;
246
+ held.revision += 1;
247
+ // Exit cleanup, unlike an ordinary final release, has not consumed a
248
+ // handle reference of its own. Keep the last callback-entered handle
249
+ // retryable until cleanup either succeeds or restores it.
250
+ if (held.releaseState !== "exit-cleaning" || held.refCount > 0)
251
+ return true;
252
+ (held.deferredExitReleases ??= new Set()).add(disposition);
253
+ return false;
254
+ }
255
+ if (held.refCount > 1) {
256
+ if (!fileLockSyncRootReceiptStillCurrent(held.rootPath, held.rootReceipt)) {
257
+ throw new FsSafeError("path-mismatch", "held sidecar lock changed before release");
258
+ }
259
+ held.refCount -= 1;
260
+ held.revision += 1;
261
+ return true;
262
+ }
263
+ if (held.refCount !== 1)
264
+ return false;
265
+ held.refCount = 0;
266
+ held.releaseState = "releasing";
267
+ held.revision += 1;
268
+ let releaseRevision = held.revision;
269
+ const entryIsContinuous = () => heldLocks.get(held.normalizedTargetPath) === held &&
270
+ held.releaseState === "releasing" &&
271
+ held.revision === releaseRevision &&
272
+ held.refCount === 0;
273
+ const beginFreshReleaseAttempt = () => {
274
+ held.releaseState = "releasing";
275
+ held.revision += 1;
276
+ releaseRevision = held.revision;
277
+ };
278
+ const fd = held.fd;
279
+ held.fd = undefined;
280
+ try {
281
+ if (fd !== undefined)
282
+ fs.closeSync(fd);
283
+ }
284
+ catch (error) {
285
+ reactivateExactHeldLock(heldLocks, held, true);
286
+ throw error;
287
+ }
288
+ if (!entryIsContinuous()) {
289
+ if (heldLocks.get(held.normalizedTargetPath) !== held ||
290
+ readRootSyncHeldReleaseState(held) === "released") {
291
+ return true;
292
+ }
293
+ if (held.refCount > 0) {
294
+ reactivateExactHeldLock(heldLocks, held, false);
295
+ return true;
296
+ }
297
+ beginFreshReleaseAttempt();
298
+ }
299
+ for (let releaseAttempt = 0; releaseAttempt < MAX_CONTINUITY_CLEANUP_ATTEMPTS; releaseAttempt += 1) {
300
+ let continuityInterrupted = false;
301
+ let removed;
302
+ try {
303
+ removed = removeFileLockSyncRootFile(held.rootPath, held.rootReceipt, held.snapshot, () => {
304
+ const current = entryIsContinuous();
305
+ if (!current)
306
+ continuityInterrupted = true;
307
+ return current;
308
+ });
309
+ }
310
+ catch (error) {
311
+ reactivateExactHeldLock(heldLocks, held, true);
312
+ throw error;
313
+ }
314
+ if (!continuityInterrupted && entryIsContinuous()) {
315
+ if (!removed) {
316
+ reactivateExactHeldLock(heldLocks, held, true);
317
+ throw new FsSafeError("path-mismatch", "owned sidecar lock changed before release");
318
+ }
319
+ const timer = held.timer;
320
+ held.timer = undefined;
321
+ held.releaseState = "released";
322
+ held.revision += 1;
323
+ if (heldLocks.get(held.normalizedTargetPath) === held) {
324
+ heldLocks.delete(held.normalizedTargetPath);
325
+ }
326
+ if (timer)
327
+ clearInterval(timer);
328
+ return true;
329
+ }
330
+ if (heldLocks.get(held.normalizedTargetPath) !== held ||
331
+ readRootSyncHeldReleaseState(held) === "released") {
332
+ return true;
333
+ }
334
+ if (held.refCount > 0) {
335
+ reactivateExactHeldLock(heldLocks, held, false);
336
+ return true;
337
+ }
338
+ if (held.refCount === 0 && releaseAttempt + 1 < MAX_CONTINUITY_CLEANUP_ATTEMPTS) {
339
+ beginFreshReleaseAttempt();
340
+ continue;
341
+ }
342
+ reactivateExactHeldLock(heldLocks, held, true);
343
+ throw new FsSafeError("path-mismatch", "held sidecar release continuity changed repeatedly");
344
+ }
345
+ throw new FsSafeError("path-mismatch", "held sidecar release could not complete");
346
+ }
347
+ export function createRootSyncHeldLockHandle(held) {
348
+ const disposition = { held, released: false };
349
+ let releaseInProgress = false;
350
+ const release = () => {
351
+ if (disposition.released || releaseInProgress)
352
+ return;
353
+ releaseInProgress = true;
354
+ try {
355
+ if (releaseRootSyncHeldLock(held, disposition))
356
+ disposition.released = true;
357
+ }
358
+ finally {
359
+ releaseInProgress = false;
360
+ }
361
+ };
362
+ const handle = {
363
+ lockPath: held.lockPath,
364
+ normalizedTargetPath: held.normalizedTargetPath,
365
+ verifyStillHeld: () => verifyRootSyncHeldLock(held),
366
+ release,
367
+ [Symbol.dispose]: release,
368
+ };
369
+ rootSyncHandleDispositions.set(handle, disposition);
370
+ return handle;
371
+ }
372
+ export function isRootSyncHeldLockHandle(handle) {
373
+ return rootSyncHandleDispositions.has(handle);
374
+ }
375
+ export function withRootSyncHeldLockHandle(lock, fn) {
376
+ let result;
377
+ try {
378
+ result = fn();
379
+ }
380
+ catch (callbackError) {
381
+ try {
382
+ lock.release();
383
+ settleDeferredExitRelease(lock);
384
+ }
385
+ catch (releaseError) {
386
+ throw createSuppressedError(releaseError, callbackError, "file lock release and protected callback both failed");
387
+ }
388
+ throw callbackError;
389
+ }
390
+ lock.release();
391
+ settleDeferredExitRelease(lock);
392
+ return result;
393
+ }
@@ -0,0 +1,44 @@
1
+ import { type BigIntStats, type Stats } from "node:fs";
2
+ import type { RootDefaults } from "./root-options.js";
3
+ import { type FileLockSyncRootPath } from "./file-lock-sync-root.js";
4
+ export type ExactIdentity = Readonly<{
5
+ dev: bigint;
6
+ ino: bigint;
7
+ }>;
8
+ export type DirectoryReceipt = Readonly<{
9
+ identity: ExactIdentity;
10
+ path: string;
11
+ realPath: string;
12
+ }>;
13
+ export type FileLockSyncRootFileReceipt = Readonly<{
14
+ identity: ExactIdentity;
15
+ parent: DirectoryReceipt;
16
+ }>;
17
+ export type FileLockSyncRootDirectoryReceipt = Readonly<{
18
+ identity: ExactIdentity;
19
+ parent: DirectoryReceipt;
20
+ }>;
21
+ export type FileLockSyncRootDiskSnapshot = {
22
+ ownershipToken?: never;
23
+ payload: unknown;
24
+ raw: string;
25
+ stat: Stats;
26
+ };
27
+ export type FileLockSyncRootSnapshot = Readonly<{
28
+ receipt: FileLockSyncRootFileReceipt;
29
+ snapshot: FileLockSyncRootDiskSnapshot;
30
+ }>;
31
+ export declare function observeDirectory(pathname: string, initial?: BigIntStats): DirectoryReceipt;
32
+ export declare function assertDirectoryCurrent(receipt: DirectoryReceipt, initial?: BigIntStats): void;
33
+ export declare function assertRetainedParentCurrent(pathAuthority: FileLockSyncRootPath, parent: DirectoryReceipt): void;
34
+ export declare function exactFileIdentity(stat: BigIntStats): ExactIdentity;
35
+ export declare function assertRegularFile(stat: BigIntStats, hardlinks: RootDefaults["hardlinks"]): void;
36
+ export declare function sameExactIdentity(left: ExactIdentity, right: ExactIdentity): boolean;
37
+ export declare function readFileLockSyncRootSnapshot(pathAuthority: FileLockSyncRootPath, options?: {
38
+ expectedReceipt?: FileLockSyncRootFileReceipt;
39
+ onOpenFailure?: (error: unknown) => void;
40
+ }): FileLockSyncRootSnapshot | null;
41
+ export declare function fileLockSyncRootSnapshotStillCurrent(pathAuthority: FileLockSyncRootPath, observed: FileLockSyncRootSnapshot): boolean;
42
+ export declare function fileReceiptCurrentAfterParentCheck(pathAuthority: FileLockSyncRootPath, receipt: FileLockSyncRootFileReceipt): boolean;
43
+ export declare function fileLockSyncRootReceiptStillCurrent(pathAuthority: FileLockSyncRootPath, receipt: FileLockSyncRootFileReceipt): boolean;
44
+ //# sourceMappingURL=file-lock-sync-root-io.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-lock-sync-root-io.d.ts","sourceRoot":"","sources":["../src/file-lock-sync-root-io.ts"],"names":[],"mappings":"AAAA,OAAW,EAAE,KAAK,WAAW,EAAE,KAAK,KAAK,EAAE,MAAM,SAAS,CAAC;AAQ3D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAQtD,OAAO,EAEL,KAAK,oBAAoB,EAC1B,MAAM,0BAA0B,CAAC;AAKlC,MAAM,MAAM,aAAa,GAAG,QAAQ,CAAC;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAEnE,MAAM,MAAM,gBAAgB,GAAG,QAAQ,CAAC;IACtC,QAAQ,EAAE,aAAa,CAAC;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC,CAAC;AAEH,MAAM,MAAM,2BAA2B,GAAG,QAAQ,CAAC;IACjD,QAAQ,EAAE,aAAa,CAAC;IACxB,MAAM,EAAE,gBAAgB,CAAC;CAC1B,CAAC,CAAC;AAEH,MAAM,MAAM,gCAAgC,GAAG,QAAQ,CAAC;IACtD,QAAQ,EAAE,aAAa,CAAC;IACxB,MAAM,EAAE,gBAAgB,CAAC;CAC1B,CAAC,CAAC;AAEH,MAAM,MAAM,4BAA4B,GAAG;IACzC,cAAc,CAAC,EAAE,KAAK,CAAC;IACvB,OAAO,EAAE,OAAO,CAAC;IACjB,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,KAAK,CAAC;CACb,CAAC;AAEF,MAAM,MAAM,wBAAwB,GAAG,QAAQ,CAAC;IAC9C,OAAO,EAAE,2BAA2B,CAAC;IACrC,QAAQ,EAAE,4BAA4B,CAAC;CACxC,CAAC,CAAC;AAKH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,WAAW,GAAG,gBAAgB,CAY1F;AAED,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,gBAAgB,EAAE,OAAO,CAAC,EAAE,WAAW,GAAG,IAAI,CAO7F;AAED,wBAAgB,2BAA2B,CACzC,aAAa,EAAE,oBAAoB,EACnC,MAAM,EAAE,gBAAgB,GACvB,IAAI,CAQN;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,WAAW,GAAG,aAAa,CAElE;AAED,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,WAAW,EACjB,SAAS,EAAE,YAAY,CAAC,WAAW,CAAC,GACnC,IAAI,CAKN;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,aAAa,EAAE,KAAK,EAAE,aAAa,GAAG,OAAO,CAEpF;AAqBD,wBAAgB,4BAA4B,CAC1C,aAAa,EAAE,oBAAoB,EACnC,OAAO,GAAE;IACP,eAAe,CAAC,EAAE,2BAA2B,CAAC;IAC9C,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;CACrC,GACL,wBAAwB,GAAG,IAAI,CA4GjC;AAED,wBAAgB,oCAAoC,CAClD,aAAa,EAAE,oBAAoB,EACnC,QAAQ,EAAE,wBAAwB,GACjC,OAAO,CAOT;AAED,wBAAgB,kCAAkC,CAChD,aAAa,EAAE,oBAAoB,EACnC,OAAO,EAAE,2BAA2B,GACnC,OAAO,CAeT;AAED,wBAAgB,mCAAmC,CACjD,aAAa,EAAE,oBAAoB,EACnC,OAAO,EAAE,2BAA2B,GACnC,OAAO,CAGT"}
@@ -0,0 +1,209 @@
1
+ import fs, {} from "node:fs";
2
+ import path from "node:path";
3
+ import { readFileDescriptorBoundedSync } from "./bounded-read.js";
4
+ import { inspectDirectoryIdentitySync } from "./directory-guard.js";
5
+ import { FsSafeError } from "./errors.js";
6
+ import { isNotFoundPathError } from "./path.js";
7
+ import { assertRootIdentityCurrentSync } from "./root-context.js";
8
+ import { hardlinkedPathNotAllowedError } from "./root-errors.js";
9
+ import { resolveReadOpenFlags } from "./read-open-flags.js";
10
+ import { realpathSync } from "./realpath.js";
11
+ import { sidecarLockSnapshotMatches, } from "./sidecar-lock-reclaim.js";
12
+ import { inspectFileIdentitySync } from "./strict-file-identity.js";
13
+ import { createSuppressedError } from "./suppressed-error.js";
14
+ import { assertFileLockSyncRootResolvedPathCurrent, } from "./file-lock-sync-root.js";
15
+ import { assertNoWindowsPathAlias, pathForWindowsFilesystem } from "./windows-path-alias.js";
16
+ const MAX_SIDECAR_LOCK_PAYLOAD_BYTES = 1024 * 1024;
17
+ const samePath = (left, right) => path.relative(path.resolve(left), path.resolve(right)) === "";
18
+ export function observeDirectory(pathname, initial) {
19
+ const stat = inspectDirectoryIdentitySync(pathname, undefined, initial);
20
+ const realPath = realpathSync.native(pathForWindowsFilesystem(pathname));
21
+ assertNoWindowsPathAlias(realPath, "filesystem", "sidecar lock parent uses a Windows filesystem namespace alias");
22
+ if (!samePath(realPath, pathname)) {
23
+ throw new FsSafeError("path-mismatch", "sidecar lock parent changed during operation");
24
+ }
25
+ return Object.freeze({
26
+ identity: Object.freeze({ dev: stat.dev, ino: stat.ino }),
27
+ path: pathname,
28
+ realPath,
29
+ });
30
+ }
31
+ export function assertDirectoryCurrent(receipt, initial) {
32
+ inspectDirectoryIdentitySync(receipt.path, receipt.identity, initial);
33
+ const currentReal = realpathSync.native(pathForWindowsFilesystem(receipt.path));
34
+ assertNoWindowsPathAlias(currentReal, "filesystem", "sidecar lock parent uses a Windows filesystem namespace alias");
35
+ if (currentReal !== receipt.realPath) {
36
+ throw new FsSafeError("path-mismatch", "sidecar lock parent changed during operation");
37
+ }
38
+ }
39
+ export function assertRetainedParentCurrent(pathAuthority, parent) {
40
+ const context = pathAuthority.authority.context;
41
+ const sharedIdentity = parent.path === context.rootReal &&
42
+ parent.identity.dev === context.rootIdentity.dev && parent.identity.ino === context.rootIdentity.ino;
43
+ let rootStat;
44
+ assertRootIdentityCurrentSync(context, sharedIdentity ? stat => { rootStat = stat; } : undefined);
45
+ assertDirectoryCurrent(parent, rootStat);
46
+ assertRootIdentityCurrentSync(context);
47
+ }
48
+ export function exactFileIdentity(stat) {
49
+ return Object.freeze({ dev: stat.dev, ino: stat.ino });
50
+ }
51
+ export function assertRegularFile(stat, hardlinks) {
52
+ if (stat.isSymbolicLink() || !stat.isFile()) {
53
+ throw new FsSafeError("not-file", "sidecar lock is not a regular file");
54
+ }
55
+ if (hardlinks !== "allow" && stat.nlink > 1n)
56
+ throw hardlinkedPathNotAllowedError();
57
+ }
58
+ export function sameExactIdentity(left, right) {
59
+ return left.dev === right.dev && left.ino === right.ino;
60
+ }
61
+ function assertFreshPathCurrent(pathAuthority) {
62
+ // The exact Root observation is deliberately last: this helper is used both
63
+ // immediately before fresh pathname I/O and immediately before returning it.
64
+ assertFileLockSyncRootResolvedPathCurrent(pathAuthority);
65
+ assertRootIdentityCurrentSync(pathAuthority.authority.context);
66
+ }
67
+ function missingAfterCurrentRead(pathAuthority, expectedReceipt) {
68
+ if (expectedReceipt) {
69
+ assertRetainedParentCurrent(pathAuthority, expectedReceipt.parent);
70
+ }
71
+ else {
72
+ assertFreshPathCurrent(pathAuthority);
73
+ }
74
+ return null;
75
+ }
76
+ export function readFileLockSyncRootSnapshot(pathAuthority, options = {}) {
77
+ const expectedReceipt = options.expectedReceipt;
78
+ if (expectedReceipt) {
79
+ assertRetainedParentCurrent(pathAuthority, expectedReceipt.parent);
80
+ }
81
+ else {
82
+ assertFreshPathCurrent(pathAuthority);
83
+ }
84
+ let before;
85
+ try {
86
+ before = inspectFileIdentitySync(() => fs.lstatSync(pathForWindowsFilesystem(pathAuthority.path), { bigint: true }), expectedReceipt?.identity);
87
+ }
88
+ catch (error) {
89
+ if (isNotFoundPathError(error) || (expectedReceipt && error instanceof FsSafeError && error.code === "path-mismatch"))
90
+ return missingAfterCurrentRead(pathAuthority, expectedReceipt);
91
+ throw error;
92
+ }
93
+ assertRegularFile(before, pathAuthority.authority.hardlinks);
94
+ const parent = expectedReceipt?.parent ?? observeDirectory(path.dirname(pathAuthority.path));
95
+ let fd;
96
+ let operationError;
97
+ let operationFailed = false;
98
+ try {
99
+ try {
100
+ fd = fs.openSync(pathForWindowsFilesystem(pathAuthority.path), resolveReadOpenFlags());
101
+ }
102
+ catch (error) {
103
+ if (isNotFoundPathError(error))
104
+ return missingAfterCurrentRead(pathAuthority, expectedReceipt);
105
+ options.onOpenFailure?.(error);
106
+ throw error;
107
+ }
108
+ let opened;
109
+ try {
110
+ opened = inspectFileIdentitySync(() => fs.fstatSync(fd, { bigint: true }), exactFileIdentity(before));
111
+ }
112
+ catch (error) {
113
+ if (error instanceof FsSafeError && error.code === "path-mismatch") {
114
+ if (expectedReceipt) {
115
+ assertRetainedParentCurrent(pathAuthority, parent);
116
+ }
117
+ else {
118
+ assertDirectoryCurrent(parent);
119
+ assertFreshPathCurrent(pathAuthority);
120
+ }
121
+ return null;
122
+ }
123
+ throw error;
124
+ }
125
+ assertRegularFile(opened, pathAuthority.authority.hardlinks);
126
+ const raw = readFileDescriptorBoundedSync(fd, MAX_SIDECAR_LOCK_PAYLOAD_BYTES).toString("utf8");
127
+ let after;
128
+ try {
129
+ after = inspectFileIdentitySync(() => fs.lstatSync(pathForWindowsFilesystem(pathAuthority.path), { bigint: true }), exactFileIdentity(opened));
130
+ }
131
+ catch (error) {
132
+ if (isNotFoundPathError(error) ||
133
+ (error instanceof FsSafeError && error.code === "path-mismatch")) {
134
+ if (expectedReceipt) {
135
+ assertRetainedParentCurrent(pathAuthority, parent);
136
+ }
137
+ else {
138
+ assertDirectoryCurrent(parent);
139
+ assertFreshPathCurrent(pathAuthority);
140
+ }
141
+ return null;
142
+ }
143
+ throw error;
144
+ }
145
+ assertRegularFile(after, pathAuthority.authority.hardlinks);
146
+ const stat = fs.fstatSync(fd);
147
+ if (expectedReceipt) {
148
+ assertRetainedParentCurrent(pathAuthority, parent);
149
+ }
150
+ else {
151
+ assertDirectoryCurrent(parent);
152
+ assertFreshPathCurrent(pathAuthority);
153
+ }
154
+ const snapshot = {
155
+ raw,
156
+ payload: null,
157
+ stat,
158
+ };
159
+ return Object.freeze({
160
+ receipt: Object.freeze({ identity: exactFileIdentity(opened), parent }),
161
+ snapshot,
162
+ });
163
+ }
164
+ catch (error) {
165
+ operationFailed = true;
166
+ operationError = error;
167
+ throw error;
168
+ }
169
+ finally {
170
+ if (fd !== undefined) {
171
+ try {
172
+ fs.closeSync(fd);
173
+ }
174
+ catch (closeError) {
175
+ if (operationFailed) {
176
+ throw createSuppressedError(closeError, operationError, "sidecar snapshot admission and descriptor close both failed");
177
+ }
178
+ throw closeError;
179
+ }
180
+ }
181
+ }
182
+ }
183
+ export function fileLockSyncRootSnapshotStillCurrent(pathAuthority, observed) {
184
+ const current = readFileLockSyncRootSnapshot(pathAuthority, {
185
+ expectedReceipt: observed.receipt,
186
+ });
187
+ return !!current &&
188
+ sameExactIdentity(current.receipt.identity, observed.receipt.identity) &&
189
+ sidecarLockSnapshotMatches(current.snapshot, observed.snapshot);
190
+ }
191
+ export function fileReceiptCurrentAfterParentCheck(pathAuthority, receipt) {
192
+ let current;
193
+ try {
194
+ current = inspectFileIdentitySync(() => fs.lstatSync(pathForWindowsFilesystem(pathAuthority.path), { bigint: true }), receipt.identity);
195
+ }
196
+ catch (error) {
197
+ if (isNotFoundPathError(error) ||
198
+ (error instanceof FsSafeError && error.code === "path-mismatch"))
199
+ return false;
200
+ throw error;
201
+ }
202
+ assertRegularFile(current, pathAuthority.authority.hardlinks);
203
+ assertRetainedParentCurrent(pathAuthority, receipt.parent);
204
+ return true;
205
+ }
206
+ export function fileLockSyncRootReceiptStillCurrent(pathAuthority, receipt) {
207
+ assertRetainedParentCurrent(pathAuthority, receipt.parent);
208
+ return fileReceiptCurrentAfterParentCheck(pathAuthority, receipt);
209
+ }
@@ -0,0 +1,17 @@
1
+ import { type SidecarLockSnapshot } from "./sidecar-lock-reclaim.js";
2
+ import { type FileLockSyncRootPath } from "./file-lock-sync-root.js";
3
+ import { type FileLockSyncRootDirectoryReceipt, type FileLockSyncRootFileReceipt } from "./file-lock-sync-root-io.js";
4
+ export declare function createFileLockSyncRootFile(pathAuthority: FileLockSyncRootPath, mode: number, options?: {
5
+ assertBeforeOpen?: () => void;
6
+ onOpenFailure?: (error: unknown) => void;
7
+ }): {
8
+ fd: number;
9
+ receipt: FileLockSyncRootFileReceipt;
10
+ };
11
+ export declare function refreshFileLockSyncRootFileReceipt(pathAuthority: FileLockSyncRootPath, receipt: FileLockSyncRootFileReceipt): FileLockSyncRootFileReceipt;
12
+ export declare function removeFileLockSyncRootFile(pathAuthority: FileLockSyncRootPath, receipt: FileLockSyncRootFileReceipt, expected?: SidecarLockSnapshot, assertBeforeRemove?: () => boolean | void): boolean;
13
+ export declare function fileLockSyncRootGuardExists(pathAuthority: FileLockSyncRootPath, pathAlreadyCurrent?: boolean): boolean;
14
+ export declare function createFileLockSyncRootDirectory(pathAuthority: FileLockSyncRootPath): FileLockSyncRootDirectoryReceipt | null;
15
+ export declare function removeFileLockSyncRootDirectory(pathAuthority: FileLockSyncRootPath, receipt: FileLockSyncRootDirectoryReceipt): boolean;
16
+ export declare function fileLockSyncRootDirectoryReceiptStillCurrent(pathAuthority: FileLockSyncRootPath, receipt: FileLockSyncRootDirectoryReceipt): boolean;
17
+ //# sourceMappingURL=file-lock-sync-root-mutation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-lock-sync-root-mutation.d.ts","sourceRoot":"","sources":["../src/file-lock-sync-root-mutation.ts"],"names":[],"mappings":"AAWA,OAAO,EAEL,KAAK,mBAAmB,EACzB,MAAM,2BAA2B,CAAC;AAGnC,OAAO,EAIL,KAAK,oBAAoB,EAC1B,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAWL,KAAK,gCAAgC,EACrC,KAAK,2BAA2B,EACjC,MAAM,6BAA6B,CAAC;AA+DrC,wBAAgB,0BAA0B,CACxC,aAAa,EAAE,oBAAoB,EACnC,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE;IACP,gBAAgB,CAAC,EAAE,MAAM,IAAI,CAAC;IAC9B,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;CACrC,GACL;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,2BAA2B,CAAA;CAAE,CA4CtD;AAED,wBAAgB,kCAAkC,CAChD,aAAa,EAAE,oBAAoB,EACnC,OAAO,EAAE,2BAA2B,GACnC,2BAA2B,CAK7B;AAED,wBAAgB,0BAA0B,CACxC,aAAa,EAAE,oBAAoB,EACnC,OAAO,EAAE,2BAA2B,EACpC,QAAQ,CAAC,EAAE,mBAAmB,EAC9B,kBAAkB,CAAC,EAAE,MAAM,OAAO,GAAG,IAAI,GACxC,OAAO,CA0DT;AAED,wBAAgB,2BAA2B,CACzC,aAAa,EAAE,oBAAoB,EACnC,kBAAkB,UAAQ,GACzB,OAAO,CAkCT;AAED,wBAAgB,+BAA+B,CAC7C,aAAa,EAAE,oBAAoB,GAClC,gCAAgC,GAAG,IAAI,CAiBzC;AAED,wBAAgB,+BAA+B,CAC7C,aAAa,EAAE,oBAAoB,EACnC,OAAO,EAAE,gCAAgC,GACxC,OAAO,CAST;AAED,wBAAgB,4CAA4C,CAC1D,aAAa,EAAE,oBAAoB,EACnC,OAAO,EAAE,gCAAgC,GACxC,OAAO,CAWT"}