open-multi-agent-kit 0.90.8 → 0.91.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 (350) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/README.md +21 -2
  3. package/dist/cli/args.d.ts.map +1 -1
  4. package/dist/cli/args.js +4 -0
  5. package/dist/cli/args.js.map +1 -1
  6. package/dist/commands/doctor-provider-cli.d.ts +33 -0
  7. package/dist/commands/doctor-provider-cli.d.ts.map +1 -0
  8. package/dist/commands/doctor-provider-cli.js +215 -0
  9. package/dist/commands/doctor-provider-cli.js.map +1 -0
  10. package/dist/commands/doctor-provider-transport.d.ts +35 -0
  11. package/dist/commands/doctor-provider-transport.d.ts.map +1 -0
  12. package/dist/commands/doctor-provider-transport.js +185 -0
  13. package/dist/commands/doctor-provider-transport.js.map +1 -0
  14. package/dist/commands/doctor-provider.d.ts +121 -7
  15. package/dist/commands/doctor-provider.d.ts.map +1 -1
  16. package/dist/commands/doctor-provider.js +1295 -102
  17. package/dist/commands/doctor-provider.js.map +1 -1
  18. package/dist/commands/session-doctor-cli.d.ts +43 -0
  19. package/dist/commands/session-doctor-cli.d.ts.map +1 -0
  20. package/dist/commands/session-doctor-cli.js +494 -0
  21. package/dist/commands/session-doctor-cli.js.map +1 -0
  22. package/dist/commands/verify-ci.d.ts +21 -0
  23. package/dist/commands/verify-ci.d.ts.map +1 -0
  24. package/dist/commands/verify-ci.js +66 -0
  25. package/dist/commands/verify-ci.js.map +1 -0
  26. package/dist/core/agent-session-services.d.ts +3 -0
  27. package/dist/core/agent-session-services.d.ts.map +1 -1
  28. package/dist/core/agent-session-services.js +3 -0
  29. package/dist/core/agent-session-services.js.map +1 -1
  30. package/dist/core/agent-session.d.ts +140 -2
  31. package/dist/core/agent-session.d.ts.map +1 -1
  32. package/dist/core/agent-session.js +1179 -95
  33. package/dist/core/agent-session.js.map +1 -1
  34. package/dist/core/agent-tool-settings.d.ts +35 -0
  35. package/dist/core/agent-tool-settings.d.ts.map +1 -0
  36. package/dist/core/agent-tool-settings.js +155 -0
  37. package/dist/core/agent-tool-settings.js.map +1 -0
  38. package/dist/core/atomic-session-file.d.ts +14 -0
  39. package/dist/core/atomic-session-file.d.ts.map +1 -0
  40. package/dist/core/atomic-session-file.js +104 -0
  41. package/dist/core/atomic-session-file.js.map +1 -0
  42. package/dist/core/command-safety.d.ts.map +1 -1
  43. package/dist/core/command-safety.js +0 -8
  44. package/dist/core/command-safety.js.map +1 -1
  45. package/dist/core/compaction/compaction.d.ts +6 -0
  46. package/dist/core/compaction/compaction.d.ts.map +1 -1
  47. package/dist/core/compaction/compaction.js +12 -1
  48. package/dist/core/compaction/compaction.js.map +1 -1
  49. package/dist/core/compaction/hysteresis.d.ts +27 -0
  50. package/dist/core/compaction/hysteresis.d.ts.map +1 -0
  51. package/dist/core/compaction/hysteresis.js +84 -0
  52. package/dist/core/compaction/hysteresis.js.map +1 -0
  53. package/dist/core/compaction/index.d.ts +2 -0
  54. package/dist/core/compaction/index.d.ts.map +1 -1
  55. package/dist/core/compaction/index.js +2 -0
  56. package/dist/core/compaction/index.js.map +1 -1
  57. package/dist/core/compaction/transaction.d.ts +172 -0
  58. package/dist/core/compaction/transaction.d.ts.map +1 -0
  59. package/dist/core/compaction/transaction.js +824 -0
  60. package/dist/core/compaction/transaction.js.map +1 -0
  61. package/dist/core/context-budget-governor-v2.d.ts +2 -1
  62. package/dist/core/context-budget-governor-v2.d.ts.map +1 -1
  63. package/dist/core/context-budget-governor-v2.js +2 -1
  64. package/dist/core/context-budget-governor-v2.js.map +1 -1
  65. package/dist/core/context-budget-reserved-tokens.d.ts +110 -0
  66. package/dist/core/context-budget-reserved-tokens.d.ts.map +1 -0
  67. package/dist/core/context-budget-reserved-tokens.js +188 -0
  68. package/dist/core/context-budget-reserved-tokens.js.map +1 -0
  69. package/dist/core/context-budget-v2-cache-invalidation.d.ts +133 -0
  70. package/dist/core/context-budget-v2-cache-invalidation.d.ts.map +1 -0
  71. package/dist/core/context-budget-v2-cache-invalidation.js +452 -0
  72. package/dist/core/context-budget-v2-cache-invalidation.js.map +1 -0
  73. package/dist/core/context-budget-v2-cache-keys.d.ts +3 -0
  74. package/dist/core/context-budget-v2-cache-keys.d.ts.map +1 -1
  75. package/dist/core/context-budget-v2-cache-keys.js +6 -0
  76. package/dist/core/context-budget-v2-cache-keys.js.map +1 -1
  77. package/dist/core/context-budget-v2-cache-provider.d.ts +4 -0
  78. package/dist/core/context-budget-v2-cache-provider.d.ts.map +1 -1
  79. package/dist/core/context-budget-v2-cache-provider.js +19 -0
  80. package/dist/core/context-budget-v2-cache-provider.js.map +1 -1
  81. package/dist/core/context-budget-v2-plan-cache-keys.d.ts.map +1 -1
  82. package/dist/core/context-budget-v2-plan-cache-keys.js +1 -0
  83. package/dist/core/context-budget-v2-plan-cache-keys.js.map +1 -1
  84. package/dist/core/context-budget-v2-planner.d.ts.map +1 -1
  85. package/dist/core/context-budget-v2-planner.js +1 -0
  86. package/dist/core/context-budget-v2-planner.js.map +1 -1
  87. package/dist/core/context-budget-v2-types.d.ts +4 -0
  88. package/dist/core/context-budget-v2-types.d.ts.map +1 -1
  89. package/dist/core/context-budget-v2-types.js.map +1 -1
  90. package/dist/core/durable-file-directory.d.ts +4 -0
  91. package/dist/core/durable-file-directory.d.ts.map +1 -0
  92. package/dist/core/durable-file-directory.js +76 -0
  93. package/dist/core/durable-file-directory.js.map +1 -0
  94. package/dist/core/durable-file-identity.d.ts +26 -0
  95. package/dist/core/durable-file-identity.d.ts.map +1 -0
  96. package/dist/core/durable-file-identity.js +118 -0
  97. package/dist/core/durable-file-identity.js.map +1 -0
  98. package/dist/core/durable-file-io.d.ts +29 -0
  99. package/dist/core/durable-file-io.d.ts.map +1 -0
  100. package/dist/core/durable-file-io.js +179 -0
  101. package/dist/core/durable-file-io.js.map +1 -0
  102. package/dist/core/durable-file-lock-cleanup.d.ts +12 -0
  103. package/dist/core/durable-file-lock-cleanup.d.ts.map +1 -0
  104. package/dist/core/durable-file-lock-cleanup.js +39 -0
  105. package/dist/core/durable-file-lock-cleanup.js.map +1 -0
  106. package/dist/core/durable-file-lock-owner.d.ts +18 -0
  107. package/dist/core/durable-file-lock-owner.d.ts.map +1 -0
  108. package/dist/core/durable-file-lock-owner.js +78 -0
  109. package/dist/core/durable-file-lock-owner.js.map +1 -0
  110. package/dist/core/durable-file-lock-storage.d.ts +35 -0
  111. package/dist/core/durable-file-lock-storage.d.ts.map +1 -0
  112. package/dist/core/durable-file-lock-storage.js +263 -0
  113. package/dist/core/durable-file-lock-storage.js.map +1 -0
  114. package/dist/core/durable-file-mode.d.ts +8 -0
  115. package/dist/core/durable-file-mode.d.ts.map +1 -0
  116. package/dist/core/durable-file-mode.js +41 -0
  117. package/dist/core/durable-file-mode.js.map +1 -0
  118. package/dist/core/extensions/loader.d.ts +12 -0
  119. package/dist/core/extensions/loader.d.ts.map +1 -1
  120. package/dist/core/extensions/loader.js +46 -37
  121. package/dist/core/extensions/loader.js.map +1 -1
  122. package/dist/core/footer-data-provider.d.ts +7 -1
  123. package/dist/core/footer-data-provider.d.ts.map +1 -1
  124. package/dist/core/footer-data-provider.js +12 -0
  125. package/dist/core/footer-data-provider.js.map +1 -1
  126. package/dist/core/index.d.ts +7 -2
  127. package/dist/core/index.d.ts.map +1 -1
  128. package/dist/core/index.js +6 -0
  129. package/dist/core/index.js.map +1 -1
  130. package/dist/core/metrics-sampler.d.ts +20 -0
  131. package/dist/core/metrics-sampler.d.ts.map +1 -1
  132. package/dist/core/metrics-sampler.js +52 -0
  133. package/dist/core/metrics-sampler.js.map +1 -1
  134. package/dist/core/model-registry.d.ts.map +1 -1
  135. package/dist/core/model-registry.js +4 -1
  136. package/dist/core/model-registry.js.map +1 -1
  137. package/dist/core/model-resolver.d.ts.map +1 -1
  138. package/dist/core/model-resolver.js +16 -7
  139. package/dist/core/model-resolver.js.map +1 -1
  140. package/dist/core/provider-resilience.d.ts +51 -0
  141. package/dist/core/provider-resilience.d.ts.map +1 -0
  142. package/dist/core/provider-resilience.js +86 -0
  143. package/dist/core/provider-resilience.js.map +1 -0
  144. package/dist/core/redaction.d.ts +8 -0
  145. package/dist/core/redaction.d.ts.map +1 -0
  146. package/dist/core/redaction.js +31 -0
  147. package/dist/core/redaction.js.map +1 -0
  148. package/dist/core/resource-loader.d.ts.map +1 -1
  149. package/dist/core/resource-loader.js +36 -0
  150. package/dist/core/resource-loader.js.map +1 -1
  151. package/dist/core/run-journal-store.d.ts +53 -0
  152. package/dist/core/run-journal-store.d.ts.map +1 -0
  153. package/dist/core/run-journal-store.js +278 -0
  154. package/dist/core/run-journal-store.js.map +1 -0
  155. package/dist/core/run-journal.d.ts +172 -0
  156. package/dist/core/run-journal.d.ts.map +1 -0
  157. package/dist/core/run-journal.js +1045 -0
  158. package/dist/core/run-journal.js.map +1 -0
  159. package/dist/core/sdk.d.ts +7 -0
  160. package/dist/core/sdk.d.ts.map +1 -1
  161. package/dist/core/sdk.js +94 -3
  162. package/dist/core/sdk.js.map +1 -1
  163. package/dist/core/session-doctor-plan.d.ts +128 -0
  164. package/dist/core/session-doctor-plan.d.ts.map +1 -0
  165. package/dist/core/session-doctor-plan.js +833 -0
  166. package/dist/core/session-doctor-plan.js.map +1 -0
  167. package/dist/core/session-file-compaction.d.ts +6 -0
  168. package/dist/core/session-file-compaction.d.ts.map +1 -0
  169. package/dist/core/session-file-compaction.js +169 -0
  170. package/dist/core/session-file-compaction.js.map +1 -0
  171. package/dist/core/session-file-head.d.ts +10 -0
  172. package/dist/core/session-file-head.d.ts.map +1 -0
  173. package/dist/core/session-file-head.js +78 -0
  174. package/dist/core/session-file-head.js.map +1 -0
  175. package/dist/core/session-file-persistence.d.ts +30 -0
  176. package/dist/core/session-file-persistence.d.ts.map +1 -0
  177. package/dist/core/session-file-persistence.js +106 -0
  178. package/dist/core/session-file-persistence.js.map +1 -0
  179. package/dist/core/session-integrity.d.ts +43 -0
  180. package/dist/core/session-integrity.d.ts.map +1 -0
  181. package/dist/core/session-integrity.js +420 -0
  182. package/dist/core/session-integrity.js.map +1 -0
  183. package/dist/core/session-manager.d.ts +24 -4
  184. package/dist/core/session-manager.d.ts.map +1 -1
  185. package/dist/core/session-manager.js +242 -125
  186. package/dist/core/session-manager.js.map +1 -1
  187. package/dist/core/session-owner-lease.d.ts +22 -0
  188. package/dist/core/session-owner-lease.d.ts.map +1 -0
  189. package/dist/core/session-owner-lease.js +176 -0
  190. package/dist/core/session-owner-lease.js.map +1 -0
  191. package/dist/core/session-path-policy.d.ts +132 -0
  192. package/dist/core/session-path-policy.d.ts.map +1 -0
  193. package/dist/core/session-path-policy.js +780 -0
  194. package/dist/core/session-path-policy.js.map +1 -0
  195. package/dist/core/session-repair-plan.d.ts +49 -0
  196. package/dist/core/session-repair-plan.d.ts.map +1 -0
  197. package/dist/core/session-repair-plan.js +202 -0
  198. package/dist/core/session-repair-plan.js.map +1 -0
  199. package/dist/core/session-termination.d.ts +106 -0
  200. package/dist/core/session-termination.d.ts.map +1 -0
  201. package/dist/core/session-termination.js +445 -0
  202. package/dist/core/session-termination.js.map +1 -0
  203. package/dist/core/settings-manager.d.ts +52 -0
  204. package/dist/core/settings-manager.d.ts.map +1 -1
  205. package/dist/core/settings-manager.js +54 -5
  206. package/dist/core/settings-manager.js.map +1 -1
  207. package/dist/core/system-prompt.d.ts.map +1 -1
  208. package/dist/core/system-prompt.js +1 -1
  209. package/dist/core/system-prompt.js.map +1 -1
  210. package/dist/core/tools/grep.d.ts.map +1 -1
  211. package/dist/core/tools/grep.js +47 -0
  212. package/dist/core/tools/grep.js.map +1 -1
  213. package/dist/core/tools/omp-pure-seams.d.ts +41 -0
  214. package/dist/core/tools/omp-pure-seams.d.ts.map +1 -0
  215. package/dist/core/tools/omp-pure-seams.js +142 -0
  216. package/dist/core/tools/omp-pure-seams.js.map +1 -0
  217. package/dist/core/tools/omp-seam-runtime.d.ts +100 -0
  218. package/dist/core/tools/omp-seam-runtime.d.ts.map +1 -0
  219. package/dist/core/tools/omp-seam-runtime.js +40 -0
  220. package/dist/core/tools/omp-seam-runtime.js.map +1 -0
  221. package/dist/core/tools/read.d.ts.map +1 -1
  222. package/dist/core/tools/read.js +34 -1
  223. package/dist/core/tools/read.js.map +1 -1
  224. package/dist/core/verified-bash-adapter.d.ts +36 -0
  225. package/dist/core/verified-bash-adapter.d.ts.map +1 -0
  226. package/dist/core/verified-bash-adapter.js +90 -0
  227. package/dist/core/verified-bash-adapter.js.map +1 -0
  228. package/dist/guardrails/command-redaction.d.ts +32 -0
  229. package/dist/guardrails/command-redaction.d.ts.map +1 -0
  230. package/dist/guardrails/command-redaction.js +776 -0
  231. package/dist/guardrails/command-redaction.js.map +1 -0
  232. package/dist/guardrails/evidence-attestation.d.ts +31 -0
  233. package/dist/guardrails/evidence-attestation.d.ts.map +1 -0
  234. package/dist/guardrails/evidence-attestation.js +178 -0
  235. package/dist/guardrails/evidence-attestation.js.map +1 -0
  236. package/dist/guardrails/evidence-receipt-store.d.ts +33 -0
  237. package/dist/guardrails/evidence-receipt-store.d.ts.map +1 -0
  238. package/dist/guardrails/evidence-receipt-store.js +446 -0
  239. package/dist/guardrails/evidence-receipt-store.js.map +1 -0
  240. package/dist/guardrails/evidence-receipt.d.ts +58 -0
  241. package/dist/guardrails/evidence-receipt.d.ts.map +1 -0
  242. package/dist/guardrails/evidence-receipt.js +389 -0
  243. package/dist/guardrails/evidence-receipt.js.map +1 -0
  244. package/dist/guardrails/evidence-system.d.ts +39 -15
  245. package/dist/guardrails/evidence-system.d.ts.map +1 -1
  246. package/dist/guardrails/evidence-system.js +255 -97
  247. package/dist/guardrails/evidence-system.js.map +1 -1
  248. package/dist/guardrails/replay-ledger-io.d.ts +6 -0
  249. package/dist/guardrails/replay-ledger-io.d.ts.map +1 -0
  250. package/dist/guardrails/replay-ledger-io.js +42 -0
  251. package/dist/guardrails/replay-ledger-io.js.map +1 -0
  252. package/dist/guardrails/replay-ledger-lock-owner.d.ts +32 -0
  253. package/dist/guardrails/replay-ledger-lock-owner.d.ts.map +1 -0
  254. package/dist/guardrails/replay-ledger-lock-owner.js +138 -0
  255. package/dist/guardrails/replay-ledger-lock-owner.js.map +1 -0
  256. package/dist/guardrails/replay-ledger-lock.d.ts +24 -0
  257. package/dist/guardrails/replay-ledger-lock.d.ts.map +1 -0
  258. package/dist/guardrails/replay-ledger-lock.js +135 -0
  259. package/dist/guardrails/replay-ledger-lock.js.map +1 -0
  260. package/dist/guardrails/replay-ledger-mutation-gate.d.ts +24 -0
  261. package/dist/guardrails/replay-ledger-mutation-gate.d.ts.map +1 -0
  262. package/dist/guardrails/replay-ledger-mutation-gate.js +205 -0
  263. package/dist/guardrails/replay-ledger-mutation-gate.js.map +1 -0
  264. package/dist/guardrails/replay-ledger-quarantine.d.ts +2 -0
  265. package/dist/guardrails/replay-ledger-quarantine.d.ts.map +1 -0
  266. package/dist/guardrails/replay-ledger-quarantine.js +19 -0
  267. package/dist/guardrails/replay-ledger-quarantine.js.map +1 -0
  268. package/dist/guardrails/replay-ledger-store.d.ts +32 -0
  269. package/dist/guardrails/replay-ledger-store.d.ts.map +1 -0
  270. package/dist/guardrails/replay-ledger-store.js +211 -0
  271. package/dist/guardrails/replay-ledger-store.js.map +1 -0
  272. package/dist/guardrails/shell-command-name-glob.d.ts +3 -0
  273. package/dist/guardrails/shell-command-name-glob.d.ts.map +1 -0
  274. package/dist/guardrails/shell-command-name-glob.js +233 -0
  275. package/dist/guardrails/shell-command-name-glob.js.map +1 -0
  276. package/dist/guardrails/shell-command-option.d.ts +7 -0
  277. package/dist/guardrails/shell-command-option.d.ts.map +1 -0
  278. package/dist/guardrails/shell-command-option.js +24 -0
  279. package/dist/guardrails/shell-command-option.js.map +1 -0
  280. package/dist/guardrails/shell-command-tokenizer.d.ts +29 -0
  281. package/dist/guardrails/shell-command-tokenizer.d.ts.map +1 -0
  282. package/dist/guardrails/shell-command-tokenizer.js +145 -0
  283. package/dist/guardrails/shell-command-tokenizer.js.map +1 -0
  284. package/dist/guardrails/shell-command-wrapper.d.ts +18 -0
  285. package/dist/guardrails/shell-command-wrapper.d.ts.map +1 -0
  286. package/dist/guardrails/shell-command-wrapper.js +42 -0
  287. package/dist/guardrails/shell-command-wrapper.js.map +1 -0
  288. package/dist/guardrails/verified-executor.d.ts +65 -0
  289. package/dist/guardrails/verified-executor.d.ts.map +1 -0
  290. package/dist/guardrails/verified-executor.js +171 -0
  291. package/dist/guardrails/verified-executor.js.map +1 -0
  292. package/dist/guardrails/workspace-fingerprint.d.ts +23 -0
  293. package/dist/guardrails/workspace-fingerprint.d.ts.map +1 -0
  294. package/dist/guardrails/workspace-fingerprint.js +634 -0
  295. package/dist/guardrails/workspace-fingerprint.js.map +1 -0
  296. package/dist/index.d.ts +15 -3
  297. package/dist/index.d.ts.map +1 -1
  298. package/dist/index.js +13 -1
  299. package/dist/index.js.map +1 -1
  300. package/dist/main.d.ts.map +1 -1
  301. package/dist/main.js +12 -0
  302. package/dist/main.js.map +1 -1
  303. package/dist/modes/interactive/components/footer.d.ts +1 -1
  304. package/dist/modes/interactive/components/footer.d.ts.map +1 -1
  305. package/dist/modes/interactive/components/footer.js +11 -8
  306. package/dist/modes/interactive/components/footer.js.map +1 -1
  307. package/dist/modes/interactive/interactive-mode.d.ts +3 -0
  308. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  309. package/dist/modes/interactive/interactive-mode.js +59 -9
  310. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  311. package/dist/modes/interactive/theme/omk-aurora-dark.json +80 -0
  312. package/dist/modes/interactive/theme/omk-aurora-light.json +80 -0
  313. package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
  314. package/dist/modes/interactive/theme/theme.js +5 -0
  315. package/dist/modes/interactive/theme/theme.js.map +1 -1
  316. package/dist/modes/print-mode.d.ts.map +1 -1
  317. package/dist/modes/print-mode.js +43 -2
  318. package/dist/modes/print-mode.js.map +1 -1
  319. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  320. package/dist/modes/rpc/rpc-mode.js +23 -5
  321. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  322. package/dist/modes/rpc/rpc-types.d.ts +3 -0
  323. package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
  324. package/dist/modes/rpc/rpc-types.js.map +1 -1
  325. package/dist/types/evidence.d.ts +211 -1
  326. package/dist/types/evidence.d.ts.map +1 -1
  327. package/dist/types/evidence.js.map +1 -1
  328. package/dist/verify-ci.d.ts +3 -0
  329. package/dist/verify-ci.d.ts.map +1 -0
  330. package/dist/verify-ci.js +30 -0
  331. package/dist/verify-ci.js.map +1 -0
  332. package/docs/adaptorch-preview.md +1 -1
  333. package/docs/compaction.md +38 -4
  334. package/docs/extensions.md +2 -0
  335. package/docs/provider-resilience.md +87 -0
  336. package/docs/sdk.md +238 -1
  337. package/docs/settings.md +80 -2
  338. package/docs/themes.md +2 -0
  339. package/docs/usage.md +38 -0
  340. package/examples/extensions/custom-provider-anthropic/package-lock.json +5 -5
  341. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  342. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  343. package/examples/extensions/gondolin/package-lock.json +2 -2
  344. package/examples/extensions/gondolin/package.json +1 -1
  345. package/examples/extensions/sandbox/package-lock.json +2 -2
  346. package/examples/extensions/sandbox/package.json +1 -1
  347. package/examples/extensions/with-deps/package-lock.json +4 -4
  348. package/examples/extensions/with-deps/package.json +1 -1
  349. package/npm-shrinkwrap.json +22 -13
  350. package/package.json +6 -5
@@ -0,0 +1,179 @@
1
+ import { createHash } from "node:crypto";
2
+ import { closeSync, existsSync, fstatSync, fsyncSync, openSync, readFileSync, realpathSync, statSync, unlinkSync, writeSync, } from "node:fs";
3
+ import { basename, dirname, resolve } from "node:path";
4
+ import { ensureDurableParentDirectorySync, fsyncDirectorySync } from "./durable-file-directory.js";
5
+ import { enforcePrivateFileDescriptorModeSync } from "./durable-file-mode.js";
6
+ export { ensureDurableDirectorySync, ensureDurableParentDirectorySync, fsyncDirectorySync, } from "./durable-file-directory.js";
7
+ export class DurableFileReadRaceError extends Error {
8
+ constructor() {
9
+ super("Durable file identity or bytes changed while being read");
10
+ this.name = "DurableFileReadRaceError";
11
+ }
12
+ }
13
+ function generation(stat) {
14
+ return Object.freeze({
15
+ dev: stat.dev.toString(),
16
+ ino: stat.ino.toString(),
17
+ birthtimeNs: stat.birthtimeNs.toString(),
18
+ ctimeNs: stat.ctimeNs.toString(),
19
+ });
20
+ }
21
+ export function normalizeDurablePathIdentity(path, platform) {
22
+ if (platform === "darwin")
23
+ return path.normalize("NFD").toLowerCase();
24
+ if (platform === "win32")
25
+ return path.normalize("NFC").toLowerCase();
26
+ return path;
27
+ }
28
+ function canonicalizePath(path) {
29
+ const requested = resolve(path);
30
+ let cursor = requested;
31
+ const tail = [];
32
+ while (true) {
33
+ try {
34
+ return resolve(realpathSync.native(cursor), ...tail);
35
+ }
36
+ catch (error) {
37
+ const code = error instanceof Error && "code" in error ? Reflect.get(error, "code") : undefined;
38
+ if (code !== "ENOENT" && code !== "ENOTDIR")
39
+ throw error;
40
+ const parent = dirname(cursor);
41
+ if (parent === cursor)
42
+ return requested;
43
+ tail.unshift(basename(cursor));
44
+ cursor = parent;
45
+ }
46
+ }
47
+ }
48
+ export function resolveDurableFileIdentity(path) {
49
+ const canonicalPath = canonicalizePath(path);
50
+ let fileGeneration = null;
51
+ try {
52
+ fileGeneration = generation(statSync(path, { bigint: true }));
53
+ }
54
+ catch (error) {
55
+ const code = error instanceof Error && "code" in error ? Reflect.get(error, "code") : undefined;
56
+ if (code !== "ENOENT" && code !== "ENOTDIR")
57
+ throw error;
58
+ }
59
+ const lockPathIdentity = normalizeDurablePathIdentity(canonicalPath, process.platform);
60
+ const pathKey = createHash("sha256").update(`path\0${lockPathIdentity}`, "utf8").digest("hex");
61
+ const aliasKey = fileGeneration
62
+ ? createHash("sha256")
63
+ .update(`inode\0${fileGeneration.dev}\0${fileGeneration.ino}\0${fileGeneration.birthtimeNs}`, "utf8")
64
+ .digest("hex")
65
+ : pathKey;
66
+ return Object.freeze({
67
+ canonicalPath,
68
+ generation: fileGeneration,
69
+ pathKey,
70
+ aliasKey,
71
+ lockKeys: Object.freeze([...new Set([pathKey, aliasKey])].sort()),
72
+ });
73
+ }
74
+ export function sameDurableFileGeneration(left, right) {
75
+ if (left === null || right === null)
76
+ return left === right;
77
+ return (left.dev === right.dev &&
78
+ left.ino === right.ino &&
79
+ left.birthtimeNs === right.birthtimeNs &&
80
+ left.ctimeNs === right.ctimeNs);
81
+ }
82
+ export function readDurableFileSnapshot(path) {
83
+ let fd;
84
+ try {
85
+ fd = openSync(path, "r");
86
+ }
87
+ catch (error) {
88
+ if (error instanceof Error && "code" in error && Reflect.get(error, "code") === "ENOENT")
89
+ return null;
90
+ throw error;
91
+ }
92
+ try {
93
+ enforcePrivateFileDescriptorModeSync(fd, path);
94
+ const before = fstatSync(fd, { bigint: true });
95
+ const bytes = readFileSync(fd);
96
+ const after = fstatSync(fd, { bigint: true });
97
+ let current;
98
+ try {
99
+ current = statSync(path, { bigint: true });
100
+ }
101
+ catch {
102
+ throw new DurableFileReadRaceError();
103
+ }
104
+ if (before.dev !== after.dev ||
105
+ before.ino !== after.ino ||
106
+ before.size !== after.size ||
107
+ before.mtimeNs !== after.mtimeNs ||
108
+ before.ctimeNs !== after.ctimeNs ||
109
+ before.dev !== current.dev ||
110
+ before.ino !== current.ino ||
111
+ before.size !== current.size ||
112
+ before.mtimeNs !== current.mtimeNs ||
113
+ before.ctimeNs !== current.ctimeNs ||
114
+ before.birthtimeNs !== current.birthtimeNs) {
115
+ throw new DurableFileReadRaceError();
116
+ }
117
+ return Object.freeze({ bytes, generation: generation(before) });
118
+ }
119
+ finally {
120
+ closeSync(fd);
121
+ }
122
+ }
123
+ function writeAll(fd, bytes) {
124
+ let offset = 0;
125
+ while (offset < bytes.byteLength) {
126
+ offset += writeSync(fd, bytes, offset, bytes.byteLength - offset);
127
+ }
128
+ }
129
+ /** Append without truncation. On an indeterminate fsync failure, visible bytes are retained. */
130
+ export function appendFileDurablySync(path, bytes) {
131
+ ensureDurableParentDirectorySync(path);
132
+ const existed = existsSync(path);
133
+ const fd = openSync(path, "a", 0o600);
134
+ try {
135
+ enforcePrivateFileDescriptorModeSync(fd, path);
136
+ writeAll(fd, bytes);
137
+ fsyncSync(fd);
138
+ }
139
+ finally {
140
+ closeSync(fd);
141
+ }
142
+ if (!existed)
143
+ fsyncDirectorySync(dirname(path));
144
+ }
145
+ export function writeExclusiveFileDurablySync(path, bytes) {
146
+ ensureDurableParentDirectorySync(path);
147
+ const fd = openSync(path, "wx", 0o600);
148
+ try {
149
+ enforcePrivateFileDescriptorModeSync(fd, path);
150
+ writeAll(fd, bytes);
151
+ fsyncSync(fd);
152
+ }
153
+ catch (error) {
154
+ closeSync(fd);
155
+ try {
156
+ unlinkSync(path);
157
+ fsyncDirectorySync(dirname(path));
158
+ }
159
+ catch (cleanupError) {
160
+ throw new AggregateError([error, cleanupError], "Exclusive durable file cleanup failed");
161
+ }
162
+ throw error;
163
+ }
164
+ closeSync(fd);
165
+ try {
166
+ fsyncDirectorySync(dirname(path));
167
+ }
168
+ catch (error) {
169
+ try {
170
+ unlinkSync(path);
171
+ fsyncDirectorySync(dirname(path));
172
+ }
173
+ catch (cleanupError) {
174
+ throw new AggregateError([error, cleanupError], "Exclusive durable file rollback failed");
175
+ }
176
+ throw error;
177
+ }
178
+ }
179
+ //# sourceMappingURL=durable-file-io.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"durable-file-io.js","sourceRoot":"","sources":["../../src/core/durable-file-io.ts"],"names":[],"mappings":"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","sourcesContent":["import { createHash } from \"node:crypto\";\nimport {\n\ttype BigIntStats,\n\tcloseSync,\n\texistsSync,\n\tfstatSync,\n\tfsyncSync,\n\topenSync,\n\treadFileSync,\n\trealpathSync,\n\tstatSync,\n\tunlinkSync,\n\twriteSync,\n} from \"node:fs\";\nimport { basename, dirname, resolve } from \"node:path\";\nimport { ensureDurableParentDirectorySync, fsyncDirectorySync } from \"./durable-file-directory.ts\";\nimport { enforcePrivateFileDescriptorModeSync } from \"./durable-file-mode.ts\";\n\nexport {\n\tensureDurableDirectorySync,\n\tensureDurableParentDirectorySync,\n\tfsyncDirectorySync,\n} from \"./durable-file-directory.ts\";\n\nexport interface DurableFileGeneration {\n\treadonly dev: string;\n\treadonly ino: string;\n\treadonly birthtimeNs: string;\n\treadonly ctimeNs: string;\n}\n\nexport interface DurableFileIdentity {\n\treadonly canonicalPath: string;\n\treadonly generation: DurableFileGeneration | null;\n\treadonly pathKey: string;\n\treadonly aliasKey: string;\n\treadonly lockKeys: readonly string[];\n}\n\nexport interface DurableFileSnapshot {\n\treadonly bytes: Uint8Array;\n\treadonly generation: DurableFileGeneration;\n}\n\nexport class DurableFileReadRaceError extends Error {\n\tconstructor() {\n\t\tsuper(\"Durable file identity or bytes changed while being read\");\n\t\tthis.name = \"DurableFileReadRaceError\";\n\t}\n}\n\nfunction generation(stat: BigIntStats): DurableFileGeneration {\n\treturn Object.freeze({\n\t\tdev: stat.dev.toString(),\n\t\tino: stat.ino.toString(),\n\t\tbirthtimeNs: stat.birthtimeNs.toString(),\n\t\tctimeNs: stat.ctimeNs.toString(),\n\t});\n}\n\nexport function normalizeDurablePathIdentity(path: string, platform: NodeJS.Platform): string {\n\tif (platform === \"darwin\") return path.normalize(\"NFD\").toLowerCase();\n\tif (platform === \"win32\") return path.normalize(\"NFC\").toLowerCase();\n\treturn path;\n}\n\nfunction canonicalizePath(path: string): string {\n\tconst requested = resolve(path);\n\tlet cursor = requested;\n\tconst tail: string[] = [];\n\twhile (true) {\n\t\ttry {\n\t\t\treturn resolve(realpathSync.native(cursor), ...tail);\n\t\t} catch (error) {\n\t\t\tconst code = error instanceof Error && \"code\" in error ? Reflect.get(error, \"code\") : undefined;\n\t\t\tif (code !== \"ENOENT\" && code !== \"ENOTDIR\") throw error;\n\t\t\tconst parent = dirname(cursor);\n\t\t\tif (parent === cursor) return requested;\n\t\t\ttail.unshift(basename(cursor));\n\t\t\tcursor = parent;\n\t\t}\n\t}\n}\n\nexport function resolveDurableFileIdentity(path: string): DurableFileIdentity {\n\tconst canonicalPath = canonicalizePath(path);\n\tlet fileGeneration: DurableFileGeneration | null = null;\n\ttry {\n\t\tfileGeneration = generation(statSync(path, { bigint: true }));\n\t} catch (error) {\n\t\tconst code = error instanceof Error && \"code\" in error ? Reflect.get(error, \"code\") : undefined;\n\t\tif (code !== \"ENOENT\" && code !== \"ENOTDIR\") throw error;\n\t}\n\tconst lockPathIdentity = normalizeDurablePathIdentity(canonicalPath, process.platform);\n\tconst pathKey = createHash(\"sha256\").update(`path\\0${lockPathIdentity}`, \"utf8\").digest(\"hex\");\n\tconst aliasKey = fileGeneration\n\t\t? createHash(\"sha256\")\n\t\t\t\t.update(`inode\\0${fileGeneration.dev}\\0${fileGeneration.ino}\\0${fileGeneration.birthtimeNs}`, \"utf8\")\n\t\t\t\t.digest(\"hex\")\n\t\t: pathKey;\n\treturn Object.freeze({\n\t\tcanonicalPath,\n\t\tgeneration: fileGeneration,\n\t\tpathKey,\n\t\taliasKey,\n\t\tlockKeys: Object.freeze([...new Set([pathKey, aliasKey])].sort()),\n\t});\n}\n\nexport function sameDurableFileGeneration(\n\tleft: DurableFileGeneration | null,\n\tright: DurableFileGeneration | null,\n): boolean {\n\tif (left === null || right === null) return left === right;\n\treturn (\n\t\tleft.dev === right.dev &&\n\t\tleft.ino === right.ino &&\n\t\tleft.birthtimeNs === right.birthtimeNs &&\n\t\tleft.ctimeNs === right.ctimeNs\n\t);\n}\n\nexport function readDurableFileSnapshot(path: string): DurableFileSnapshot | null {\n\tlet fd: number;\n\ttry {\n\t\tfd = openSync(path, \"r\");\n\t} catch (error) {\n\t\tif (error instanceof Error && \"code\" in error && Reflect.get(error, \"code\") === \"ENOENT\") return null;\n\t\tthrow error;\n\t}\n\ttry {\n\t\tenforcePrivateFileDescriptorModeSync(fd, path);\n\t\tconst before = fstatSync(fd, { bigint: true });\n\t\tconst bytes: Uint8Array = readFileSync(fd);\n\t\tconst after = fstatSync(fd, { bigint: true });\n\t\tlet current: BigIntStats;\n\t\ttry {\n\t\t\tcurrent = statSync(path, { bigint: true });\n\t\t} catch {\n\t\t\tthrow new DurableFileReadRaceError();\n\t\t}\n\t\tif (\n\t\t\tbefore.dev !== after.dev ||\n\t\t\tbefore.ino !== after.ino ||\n\t\t\tbefore.size !== after.size ||\n\t\t\tbefore.mtimeNs !== after.mtimeNs ||\n\t\t\tbefore.ctimeNs !== after.ctimeNs ||\n\t\t\tbefore.dev !== current.dev ||\n\t\t\tbefore.ino !== current.ino ||\n\t\t\tbefore.size !== current.size ||\n\t\t\tbefore.mtimeNs !== current.mtimeNs ||\n\t\t\tbefore.ctimeNs !== current.ctimeNs ||\n\t\t\tbefore.birthtimeNs !== current.birthtimeNs\n\t\t) {\n\t\t\tthrow new DurableFileReadRaceError();\n\t\t}\n\t\treturn Object.freeze({ bytes, generation: generation(before) });\n\t} finally {\n\t\tcloseSync(fd);\n\t}\n}\n\nfunction writeAll(fd: number, bytes: Uint8Array): void {\n\tlet offset = 0;\n\twhile (offset < bytes.byteLength) {\n\t\toffset += writeSync(fd, bytes, offset, bytes.byteLength - offset);\n\t}\n}\n\n/** Append without truncation. On an indeterminate fsync failure, visible bytes are retained. */\nexport function appendFileDurablySync(path: string, bytes: Uint8Array): void {\n\tensureDurableParentDirectorySync(path);\n\tconst existed = existsSync(path);\n\tconst fd = openSync(path, \"a\", 0o600);\n\ttry {\n\t\tenforcePrivateFileDescriptorModeSync(fd, path);\n\t\twriteAll(fd, bytes);\n\t\tfsyncSync(fd);\n\t} finally {\n\t\tcloseSync(fd);\n\t}\n\tif (!existed) fsyncDirectorySync(dirname(path));\n}\n\nexport function writeExclusiveFileDurablySync(path: string, bytes: Uint8Array): void {\n\tensureDurableParentDirectorySync(path);\n\tconst fd = openSync(path, \"wx\", 0o600);\n\ttry {\n\t\tenforcePrivateFileDescriptorModeSync(fd, path);\n\t\twriteAll(fd, bytes);\n\t\tfsyncSync(fd);\n\t} catch (error) {\n\t\tcloseSync(fd);\n\t\ttry {\n\t\t\tunlinkSync(path);\n\t\t\tfsyncDirectorySync(dirname(path));\n\t\t} catch (cleanupError) {\n\t\t\tthrow new AggregateError([error, cleanupError], \"Exclusive durable file cleanup failed\");\n\t\t}\n\t\tthrow error;\n\t}\n\tcloseSync(fd);\n\ttry {\n\t\tfsyncDirectorySync(dirname(path));\n\t} catch (error) {\n\t\ttry {\n\t\t\tunlinkSync(path);\n\t\t\tfsyncDirectorySync(dirname(path));\n\t\t} catch (cleanupError) {\n\t\t\tthrow new AggregateError([error, cleanupError], \"Exclusive durable file rollback failed\");\n\t\t}\n\t\tthrow error;\n\t}\n}\n"]}
@@ -0,0 +1,12 @@
1
+ /** Mutable progress is retained by the owning lock so failed cleanup can be retried. */
2
+ export interface DurableFileLockRetirement {
3
+ retiredPath: string | undefined;
4
+ retirementSynced: boolean;
5
+ ownerRemoved: boolean;
6
+ directoryRemoved: boolean;
7
+ cleanupSynced: boolean;
8
+ }
9
+ export declare function createDurableFileLockRetirement(): DurableFileLockRetirement;
10
+ /** Atomically retire an active lock and durably clean its private artifact. */
11
+ export declare function retireDurableFileLockSync(lockPath: string, retirement: DurableFileLockRetirement): void;
12
+ //# sourceMappingURL=durable-file-lock-cleanup.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"durable-file-lock-cleanup.d.ts","sourceRoot":"","sources":["../../src/core/durable-file-lock-cleanup.ts"],"names":[],"mappings":"AAKA,wFAAwF;AACxF,MAAM,WAAW,yBAAyB;IACzC,WAAW,EAAE,MAAM,GAAG,SAAS,CAAC;IAChC,gBAAgB,EAAE,OAAO,CAAC;IAC1B,YAAY,EAAE,OAAO,CAAC;IACtB,gBAAgB,EAAE,OAAO,CAAC;IAC1B,aAAa,EAAE,OAAO,CAAC;CACvB;AAED,wBAAgB,+BAA+B,IAAI,yBAAyB,CAQ3E;AAED,+EAA+E;AAC/E,wBAAgB,yBAAyB,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,yBAAyB,GAAG,IAAI,CAuBvG","sourcesContent":["import { randomUUID } from \"node:crypto\";\nimport { renameSync, rmdirSync, unlinkSync } from \"node:fs\";\nimport { dirname } from \"node:path\";\nimport { fsyncDirectorySync } from \"./durable-file-io.ts\";\n\n/** Mutable progress is retained by the owning lock so failed cleanup can be retried. */\nexport interface DurableFileLockRetirement {\n\tretiredPath: string | undefined;\n\tretirementSynced: boolean;\n\townerRemoved: boolean;\n\tdirectoryRemoved: boolean;\n\tcleanupSynced: boolean;\n}\n\nexport function createDurableFileLockRetirement(): DurableFileLockRetirement {\n\treturn {\n\t\tretiredPath: undefined,\n\t\tretirementSynced: false,\n\t\townerRemoved: false,\n\t\tdirectoryRemoved: false,\n\t\tcleanupSynced: false,\n\t};\n}\n\n/** Atomically retire an active lock and durably clean its private artifact. */\nexport function retireDurableFileLockSync(lockPath: string, retirement: DurableFileLockRetirement): void {\n\tif (retirement.retiredPath === undefined) {\n\t\tconst retiredPath = `${lockPath}.release-${randomUUID()}`;\n\t\trenameSync(lockPath, retiredPath);\n\t\tretirement.retiredPath = retiredPath;\n\t}\n\tif (!retirement.retirementSynced) {\n\t\tfsyncDirectorySync(dirname(lockPath));\n\t\tretirement.retirementSynced = true;\n\t}\n\tconst retiredPath = retirement.retiredPath;\n\tif (!retirement.ownerRemoved) {\n\t\tunlinkSync(`${retiredPath}/owner.json`);\n\t\tretirement.ownerRemoved = true;\n\t}\n\tif (!retirement.directoryRemoved) {\n\t\trmdirSync(retiredPath);\n\t\tretirement.directoryRemoved = true;\n\t}\n\tif (!retirement.cleanupSynced) {\n\t\tfsyncDirectorySync(dirname(lockPath));\n\t\tretirement.cleanupSynced = true;\n\t}\n}\n"]}
@@ -0,0 +1,39 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { renameSync, rmdirSync, unlinkSync } from "node:fs";
3
+ import { dirname } from "node:path";
4
+ import { fsyncDirectorySync } from "./durable-file-io.js";
5
+ export function createDurableFileLockRetirement() {
6
+ return {
7
+ retiredPath: undefined,
8
+ retirementSynced: false,
9
+ ownerRemoved: false,
10
+ directoryRemoved: false,
11
+ cleanupSynced: false,
12
+ };
13
+ }
14
+ /** Atomically retire an active lock and durably clean its private artifact. */
15
+ export function retireDurableFileLockSync(lockPath, retirement) {
16
+ if (retirement.retiredPath === undefined) {
17
+ const retiredPath = `${lockPath}.release-${randomUUID()}`;
18
+ renameSync(lockPath, retiredPath);
19
+ retirement.retiredPath = retiredPath;
20
+ }
21
+ if (!retirement.retirementSynced) {
22
+ fsyncDirectorySync(dirname(lockPath));
23
+ retirement.retirementSynced = true;
24
+ }
25
+ const retiredPath = retirement.retiredPath;
26
+ if (!retirement.ownerRemoved) {
27
+ unlinkSync(`${retiredPath}/owner.json`);
28
+ retirement.ownerRemoved = true;
29
+ }
30
+ if (!retirement.directoryRemoved) {
31
+ rmdirSync(retiredPath);
32
+ retirement.directoryRemoved = true;
33
+ }
34
+ if (!retirement.cleanupSynced) {
35
+ fsyncDirectorySync(dirname(lockPath));
36
+ retirement.cleanupSynced = true;
37
+ }
38
+ }
39
+ //# sourceMappingURL=durable-file-lock-cleanup.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"durable-file-lock-cleanup.js","sourceRoot":"","sources":["../../src/core/durable-file-lock-cleanup.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAC5D,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAW1D,MAAM,UAAU,+BAA+B,GAA8B;IAC5E,OAAO;QACN,WAAW,EAAE,SAAS;QACtB,gBAAgB,EAAE,KAAK;QACvB,YAAY,EAAE,KAAK;QACnB,gBAAgB,EAAE,KAAK;QACvB,aAAa,EAAE,KAAK;KACpB,CAAC;AAAA,CACF;AAED,+EAA+E;AAC/E,MAAM,UAAU,yBAAyB,CAAC,QAAgB,EAAE,UAAqC,EAAQ;IACxG,IAAI,UAAU,CAAC,WAAW,KAAK,SAAS,EAAE,CAAC;QAC1C,MAAM,WAAW,GAAG,GAAG,QAAQ,YAAY,UAAU,EAAE,EAAE,CAAC;QAC1D,UAAU,CAAC,QAAQ,EAAE,WAAW,CAAC,CAAC;QAClC,UAAU,CAAC,WAAW,GAAG,WAAW,CAAC;IACtC,CAAC;IACD,IAAI,CAAC,UAAU,CAAC,gBAAgB,EAAE,CAAC;QAClC,kBAAkB,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC;QACtC,UAAU,CAAC,gBAAgB,GAAG,IAAI,CAAC;IACpC,CAAC;IACD,MAAM,WAAW,GAAG,UAAU,CAAC,WAAW,CAAC;IAC3C,IAAI,CAAC,UAAU,CAAC,YAAY,EAAE,CAAC;QAC9B,UAAU,CAAC,GAAG,WAAW,aAAa,CAAC,CAAC;QACxC,UAAU,CAAC,YAAY,GAAG,IAAI,CAAC;IAChC,CAAC;IACD,IAAI,CAAC,UAAU,CAAC,gBAAgB,EAAE,CAAC;QAClC,SAAS,CAAC,WAAW,CAAC,CAAC;QACvB,UAAU,CAAC,gBAAgB,GAAG,IAAI,CAAC;IACpC,CAAC;IACD,IAAI,CAAC,UAAU,CAAC,aAAa,EAAE,CAAC;QAC/B,kBAAkB,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC;QACtC,UAAU,CAAC,aAAa,GAAG,IAAI,CAAC;IACjC,CAAC;AAAA,CACD","sourcesContent":["import { randomUUID } from \"node:crypto\";\nimport { renameSync, rmdirSync, unlinkSync } from \"node:fs\";\nimport { dirname } from \"node:path\";\nimport { fsyncDirectorySync } from \"./durable-file-io.ts\";\n\n/** Mutable progress is retained by the owning lock so failed cleanup can be retried. */\nexport interface DurableFileLockRetirement {\n\tretiredPath: string | undefined;\n\tretirementSynced: boolean;\n\townerRemoved: boolean;\n\tdirectoryRemoved: boolean;\n\tcleanupSynced: boolean;\n}\n\nexport function createDurableFileLockRetirement(): DurableFileLockRetirement {\n\treturn {\n\t\tretiredPath: undefined,\n\t\tretirementSynced: false,\n\t\townerRemoved: false,\n\t\tdirectoryRemoved: false,\n\t\tcleanupSynced: false,\n\t};\n}\n\n/** Atomically retire an active lock and durably clean its private artifact. */\nexport function retireDurableFileLockSync(lockPath: string, retirement: DurableFileLockRetirement): void {\n\tif (retirement.retiredPath === undefined) {\n\t\tconst retiredPath = `${lockPath}.release-${randomUUID()}`;\n\t\trenameSync(lockPath, retiredPath);\n\t\tretirement.retiredPath = retiredPath;\n\t}\n\tif (!retirement.retirementSynced) {\n\t\tfsyncDirectorySync(dirname(lockPath));\n\t\tretirement.retirementSynced = true;\n\t}\n\tconst retiredPath = retirement.retiredPath;\n\tif (!retirement.ownerRemoved) {\n\t\tunlinkSync(`${retiredPath}/owner.json`);\n\t\tretirement.ownerRemoved = true;\n\t}\n\tif (!retirement.directoryRemoved) {\n\t\trmdirSync(retiredPath);\n\t\tretirement.directoryRemoved = true;\n\t}\n\tif (!retirement.cleanupSynced) {\n\t\tfsyncDirectorySync(dirname(lockPath));\n\t\tretirement.cleanupSynced = true;\n\t}\n}\n"]}
@@ -0,0 +1,18 @@
1
+ export interface DurableFileLockOwner {
2
+ readonly pid: number;
3
+ readonly host: string;
4
+ readonly token: string;
5
+ }
6
+ export interface DurableFileLockOwnerSnapshot {
7
+ readonly owner: DurableFileLockOwner;
8
+ readonly dev: string;
9
+ readonly ino: string;
10
+ }
11
+ export declare class DurableFileLockOwnerError extends Error {
12
+ readonly name = "DurableFileLockOwnerError";
13
+ }
14
+ export declare function fileErrorCode(error: unknown): string | undefined;
15
+ /** Read one owner while binding its token to both owner-file and lock-directory inodes. */
16
+ export declare function readDurableFileLockOwnerSync(lockPath: string): DurableFileLockOwnerSnapshot;
17
+ export declare function sameDurableFileLockOwner(left: DurableFileLockOwnerSnapshot, right: DurableFileLockOwnerSnapshot): boolean;
18
+ //# sourceMappingURL=durable-file-lock-owner.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"durable-file-lock-owner.d.ts","sourceRoot":"","sources":["../../src/core/durable-file-lock-owner.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,oBAAoB;IACpC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,4BAA4B;IAC5C,QAAQ,CAAC,KAAK,EAAE,oBAAoB,CAAC;IACrC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;CACrB;AAED,qBAAa,yBAA0B,SAAQ,KAAK;IACnD,SAAkB,IAAI,+BAA+B;CACrD;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAEhE;AA8BD,2FAA2F;AAC3F,wBAAgB,4BAA4B,CAAC,QAAQ,EAAE,MAAM,GAAG,4BAA4B,CAqC3F;AAED,wBAAgB,wBAAwB,CACvC,IAAI,EAAE,4BAA4B,EAClC,KAAK,EAAE,4BAA4B,GACjC,OAAO,CAET","sourcesContent":["import { closeSync, constants, fstatSync, lstatSync, openSync, readFileSync } from \"node:fs\";\nimport { enforcePrivateFileDescriptorModeSync } from \"./durable-file-mode.ts\";\n\nconst MAX_OWNER_BYTES = 1_024;\nconst MAX_OWNER_TEXT = 512;\n\nexport interface DurableFileLockOwner {\n\treadonly pid: number;\n\treadonly host: string;\n\treadonly token: string;\n}\n\nexport interface DurableFileLockOwnerSnapshot {\n\treadonly owner: DurableFileLockOwner;\n\treadonly dev: string;\n\treadonly ino: string;\n}\n\nexport class DurableFileLockOwnerError extends Error {\n\toverride readonly name = \"DurableFileLockOwnerError\";\n}\n\nexport function fileErrorCode(error: unknown): string | undefined {\n\treturn error instanceof Error && \"code\" in error ? String(Reflect.get(error, \"code\")) : undefined;\n}\n\nfunction parseOwner(input: string): DurableFileLockOwner {\n\tlet value: unknown;\n\ttry {\n\t\tvalue = JSON.parse(input);\n\t} catch (error) {\n\t\tthrow new DurableFileLockOwnerError(\"Durable file lock owner record is malformed\", { cause: error });\n\t}\n\tif (typeof value !== \"object\" || value === null || Array.isArray(value)) {\n\t\tthrow new DurableFileLockOwnerError(\"Durable file lock owner record is invalid\");\n\t}\n\tconst pid = Reflect.get(value, \"pid\");\n\tconst host = Reflect.get(value, \"host\");\n\tconst token = Reflect.get(value, \"token\");\n\tif (\n\t\t!Number.isSafeInteger(pid) ||\n\t\tNumber(pid) <= 0 ||\n\t\ttypeof host !== \"string\" ||\n\t\thost.length === 0 ||\n\t\thost.length > MAX_OWNER_TEXT ||\n\t\ttypeof token !== \"string\" ||\n\t\ttoken.length === 0 ||\n\t\ttoken.length > MAX_OWNER_TEXT\n\t) {\n\t\tthrow new DurableFileLockOwnerError(\"Durable file lock owner record is invalid\");\n\t}\n\treturn Object.freeze({ pid: Number(pid), host, token });\n}\n\n/** Read one owner while binding its token to both owner-file and lock-directory inodes. */\nexport function readDurableFileLockOwnerSync(lockPath: string): DurableFileLockOwnerSnapshot {\n\tconst beforeDirectory = lstatSync(lockPath, { bigint: true });\n\tif (!beforeDirectory.isDirectory() || beforeDirectory.isSymbolicLink()) {\n\t\tthrow new DurableFileLockOwnerError(\"Durable file lock is not a real directory\");\n\t}\n\tconst path = `${lockPath}/owner.json`;\n\tconst fd = openSync(path, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0));\n\ttry {\n\t\tenforcePrivateFileDescriptorModeSync(fd, path);\n\t\tconst beforeOwner = fstatSync(fd, { bigint: true });\n\t\tif (!beforeOwner.isFile() || beforeOwner.size <= 0 || beforeOwner.size > MAX_OWNER_BYTES) {\n\t\t\tthrow new DurableFileLockOwnerError(\"Durable file lock owner record is outside its size bound\");\n\t\t}\n\t\tconst bytes: Buffer = readFileSync(fd);\n\t\tconst afterOwner = fstatSync(fd, { bigint: true });\n\t\tconst currentOwner = lstatSync(path, { bigint: true });\n\t\tconst afterDirectory = lstatSync(lockPath, { bigint: true });\n\t\tif (\n\t\t\tbeforeOwner.dev !== afterOwner.dev ||\n\t\t\tbeforeOwner.ino !== afterOwner.ino ||\n\t\t\tbeforeOwner.size !== afterOwner.size ||\n\t\t\tafterOwner.size !== BigInt(bytes.byteLength) ||\n\t\t\tafterOwner.dev !== currentOwner.dev ||\n\t\t\tafterOwner.ino !== currentOwner.ino ||\n\t\t\tbeforeDirectory.dev !== afterDirectory.dev ||\n\t\t\tbeforeDirectory.ino !== afterDirectory.ino\n\t\t) {\n\t\t\tthrow new DurableFileLockOwnerError(\"Durable file lock owner changed during inspection\");\n\t\t}\n\t\treturn Object.freeze({\n\t\t\towner: parseOwner(bytes.toString(\"utf8\")),\n\t\t\tdev: afterDirectory.dev.toString(),\n\t\t\tino: afterDirectory.ino.toString(),\n\t\t});\n\t} finally {\n\t\tcloseSync(fd);\n\t}\n}\n\nexport function sameDurableFileLockOwner(\n\tleft: DurableFileLockOwnerSnapshot,\n\tright: DurableFileLockOwnerSnapshot,\n): boolean {\n\treturn left.dev === right.dev && left.ino === right.ino && left.owner.token === right.owner.token;\n}\n"]}
@@ -0,0 +1,78 @@
1
+ import { closeSync, constants, fstatSync, lstatSync, openSync, readFileSync } from "node:fs";
2
+ import { enforcePrivateFileDescriptorModeSync } from "./durable-file-mode.js";
3
+ const MAX_OWNER_BYTES = 1_024;
4
+ const MAX_OWNER_TEXT = 512;
5
+ export class DurableFileLockOwnerError extends Error {
6
+ name = "DurableFileLockOwnerError";
7
+ }
8
+ export function fileErrorCode(error) {
9
+ return error instanceof Error && "code" in error ? String(Reflect.get(error, "code")) : undefined;
10
+ }
11
+ function parseOwner(input) {
12
+ let value;
13
+ try {
14
+ value = JSON.parse(input);
15
+ }
16
+ catch (error) {
17
+ throw new DurableFileLockOwnerError("Durable file lock owner record is malformed", { cause: error });
18
+ }
19
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
20
+ throw new DurableFileLockOwnerError("Durable file lock owner record is invalid");
21
+ }
22
+ const pid = Reflect.get(value, "pid");
23
+ const host = Reflect.get(value, "host");
24
+ const token = Reflect.get(value, "token");
25
+ if (!Number.isSafeInteger(pid) ||
26
+ Number(pid) <= 0 ||
27
+ typeof host !== "string" ||
28
+ host.length === 0 ||
29
+ host.length > MAX_OWNER_TEXT ||
30
+ typeof token !== "string" ||
31
+ token.length === 0 ||
32
+ token.length > MAX_OWNER_TEXT) {
33
+ throw new DurableFileLockOwnerError("Durable file lock owner record is invalid");
34
+ }
35
+ return Object.freeze({ pid: Number(pid), host, token });
36
+ }
37
+ /** Read one owner while binding its token to both owner-file and lock-directory inodes. */
38
+ export function readDurableFileLockOwnerSync(lockPath) {
39
+ const beforeDirectory = lstatSync(lockPath, { bigint: true });
40
+ if (!beforeDirectory.isDirectory() || beforeDirectory.isSymbolicLink()) {
41
+ throw new DurableFileLockOwnerError("Durable file lock is not a real directory");
42
+ }
43
+ const path = `${lockPath}/owner.json`;
44
+ const fd = openSync(path, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0));
45
+ try {
46
+ enforcePrivateFileDescriptorModeSync(fd, path);
47
+ const beforeOwner = fstatSync(fd, { bigint: true });
48
+ if (!beforeOwner.isFile() || beforeOwner.size <= 0 || beforeOwner.size > MAX_OWNER_BYTES) {
49
+ throw new DurableFileLockOwnerError("Durable file lock owner record is outside its size bound");
50
+ }
51
+ const bytes = readFileSync(fd);
52
+ const afterOwner = fstatSync(fd, { bigint: true });
53
+ const currentOwner = lstatSync(path, { bigint: true });
54
+ const afterDirectory = lstatSync(lockPath, { bigint: true });
55
+ if (beforeOwner.dev !== afterOwner.dev ||
56
+ beforeOwner.ino !== afterOwner.ino ||
57
+ beforeOwner.size !== afterOwner.size ||
58
+ afterOwner.size !== BigInt(bytes.byteLength) ||
59
+ afterOwner.dev !== currentOwner.dev ||
60
+ afterOwner.ino !== currentOwner.ino ||
61
+ beforeDirectory.dev !== afterDirectory.dev ||
62
+ beforeDirectory.ino !== afterDirectory.ino) {
63
+ throw new DurableFileLockOwnerError("Durable file lock owner changed during inspection");
64
+ }
65
+ return Object.freeze({
66
+ owner: parseOwner(bytes.toString("utf8")),
67
+ dev: afterDirectory.dev.toString(),
68
+ ino: afterDirectory.ino.toString(),
69
+ });
70
+ }
71
+ finally {
72
+ closeSync(fd);
73
+ }
74
+ }
75
+ export function sameDurableFileLockOwner(left, right) {
76
+ return left.dev === right.dev && left.ino === right.ino && left.owner.token === right.owner.token;
77
+ }
78
+ //# sourceMappingURL=durable-file-lock-owner.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"durable-file-lock-owner.js","sourceRoot":"","sources":["../../src/core/durable-file-lock-owner.ts"],"names":[],"mappings":"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","sourcesContent":["import { closeSync, constants, fstatSync, lstatSync, openSync, readFileSync } from \"node:fs\";\nimport { enforcePrivateFileDescriptorModeSync } from \"./durable-file-mode.ts\";\n\nconst MAX_OWNER_BYTES = 1_024;\nconst MAX_OWNER_TEXT = 512;\n\nexport interface DurableFileLockOwner {\n\treadonly pid: number;\n\treadonly host: string;\n\treadonly token: string;\n}\n\nexport interface DurableFileLockOwnerSnapshot {\n\treadonly owner: DurableFileLockOwner;\n\treadonly dev: string;\n\treadonly ino: string;\n}\n\nexport class DurableFileLockOwnerError extends Error {\n\toverride readonly name = \"DurableFileLockOwnerError\";\n}\n\nexport function fileErrorCode(error: unknown): string | undefined {\n\treturn error instanceof Error && \"code\" in error ? String(Reflect.get(error, \"code\")) : undefined;\n}\n\nfunction parseOwner(input: string): DurableFileLockOwner {\n\tlet value: unknown;\n\ttry {\n\t\tvalue = JSON.parse(input);\n\t} catch (error) {\n\t\tthrow new DurableFileLockOwnerError(\"Durable file lock owner record is malformed\", { cause: error });\n\t}\n\tif (typeof value !== \"object\" || value === null || Array.isArray(value)) {\n\t\tthrow new DurableFileLockOwnerError(\"Durable file lock owner record is invalid\");\n\t}\n\tconst pid = Reflect.get(value, \"pid\");\n\tconst host = Reflect.get(value, \"host\");\n\tconst token = Reflect.get(value, \"token\");\n\tif (\n\t\t!Number.isSafeInteger(pid) ||\n\t\tNumber(pid) <= 0 ||\n\t\ttypeof host !== \"string\" ||\n\t\thost.length === 0 ||\n\t\thost.length > MAX_OWNER_TEXT ||\n\t\ttypeof token !== \"string\" ||\n\t\ttoken.length === 0 ||\n\t\ttoken.length > MAX_OWNER_TEXT\n\t) {\n\t\tthrow new DurableFileLockOwnerError(\"Durable file lock owner record is invalid\");\n\t}\n\treturn Object.freeze({ pid: Number(pid), host, token });\n}\n\n/** Read one owner while binding its token to both owner-file and lock-directory inodes. */\nexport function readDurableFileLockOwnerSync(lockPath: string): DurableFileLockOwnerSnapshot {\n\tconst beforeDirectory = lstatSync(lockPath, { bigint: true });\n\tif (!beforeDirectory.isDirectory() || beforeDirectory.isSymbolicLink()) {\n\t\tthrow new DurableFileLockOwnerError(\"Durable file lock is not a real directory\");\n\t}\n\tconst path = `${lockPath}/owner.json`;\n\tconst fd = openSync(path, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0));\n\ttry {\n\t\tenforcePrivateFileDescriptorModeSync(fd, path);\n\t\tconst beforeOwner = fstatSync(fd, { bigint: true });\n\t\tif (!beforeOwner.isFile() || beforeOwner.size <= 0 || beforeOwner.size > MAX_OWNER_BYTES) {\n\t\t\tthrow new DurableFileLockOwnerError(\"Durable file lock owner record is outside its size bound\");\n\t\t}\n\t\tconst bytes: Buffer = readFileSync(fd);\n\t\tconst afterOwner = fstatSync(fd, { bigint: true });\n\t\tconst currentOwner = lstatSync(path, { bigint: true });\n\t\tconst afterDirectory = lstatSync(lockPath, { bigint: true });\n\t\tif (\n\t\t\tbeforeOwner.dev !== afterOwner.dev ||\n\t\t\tbeforeOwner.ino !== afterOwner.ino ||\n\t\t\tbeforeOwner.size !== afterOwner.size ||\n\t\t\tafterOwner.size !== BigInt(bytes.byteLength) ||\n\t\t\tafterOwner.dev !== currentOwner.dev ||\n\t\t\tafterOwner.ino !== currentOwner.ino ||\n\t\t\tbeforeDirectory.dev !== afterDirectory.dev ||\n\t\t\tbeforeDirectory.ino !== afterDirectory.ino\n\t\t) {\n\t\t\tthrow new DurableFileLockOwnerError(\"Durable file lock owner changed during inspection\");\n\t\t}\n\t\treturn Object.freeze({\n\t\t\towner: parseOwner(bytes.toString(\"utf8\")),\n\t\t\tdev: afterDirectory.dev.toString(),\n\t\t\tino: afterDirectory.ino.toString(),\n\t\t});\n\t} finally {\n\t\tcloseSync(fd);\n\t}\n}\n\nexport function sameDurableFileLockOwner(\n\tleft: DurableFileLockOwnerSnapshot,\n\tright: DurableFileLockOwnerSnapshot,\n): boolean {\n\treturn left.dev === right.dev && left.ino === right.ino && left.owner.token === right.owner.token;\n}\n"]}
@@ -0,0 +1,35 @@
1
+ import { type DurableFileLockRetirement } from "./durable-file-lock-cleanup.ts";
2
+ import { type DurableFileLockOwnerSnapshot } from "./durable-file-lock-owner.ts";
3
+ export type DurableFileLockPathObservation = {
4
+ readonly status: "absent";
5
+ } | {
6
+ readonly status: "live";
7
+ readonly pid: number;
8
+ readonly host: string;
9
+ } | {
10
+ readonly status: "unknown";
11
+ };
12
+ export interface HeldDurableFileLock {
13
+ readonly key: string;
14
+ readonly path: string;
15
+ readonly token: string;
16
+ readonly expectedOwner: DurableFileLockOwnerSnapshot;
17
+ readonly retirement: DurableFileLockRetirement;
18
+ }
19
+ export declare class DurableFileLockBusyError extends Error {
20
+ readonly name = "DurableFileLockBusyError";
21
+ constructor();
22
+ }
23
+ export declare class DurableFileLockOwnershipError extends Error {
24
+ readonly name = "DurableFileLockOwnershipError";
25
+ constructor();
26
+ }
27
+ export declare function getDurableFileLockRoot(): string;
28
+ export declare function releaseDurableFileLockPathsSync(held: HeldDurableFileLock[], selected: readonly HeldDurableFileLock[]): void;
29
+ export declare function retainRetryableDurableFileLocks(locks: readonly HeldDurableFileLock[]): void;
30
+ export declare function assertDurableFileLockPathsOwnedSync(locks: readonly HeldDurableFileLock[]): void;
31
+ export declare function drainRetryableDurableFileLocksSync(): void;
32
+ export declare function durableFileLockPath(scope: string, key: string): string;
33
+ export declare function acquireDurableFileLockPathSync(key: string, path: string, deadline: number): HeldDurableFileLock;
34
+ export declare function inspectDurableFileLockPathSync(lockPath: string, deadline: number): DurableFileLockPathObservation;
35
+ //# sourceMappingURL=durable-file-lock-storage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"durable-file-lock-storage.d.ts","sourceRoot":"","sources":["../../src/core/durable-file-lock-storage.ts"],"names":[],"mappings":"AAKA,OAAO,EAEN,KAAK,yBAAyB,EAE9B,MAAM,gCAAgC,CAAC;AACxC,OAAO,EACN,KAAK,4BAA4B,EAIjC,MAAM,8BAA8B,CAAC;AAEtC,MAAM,MAAM,8BAA8B,GACvC;IAAE,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAA;CAAE,GAC7B;IAAE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GACxE;IAAE,QAAQ,CAAC,MAAM,EAAE,SAAS,CAAA;CAAE,CAAC;AAElC,MAAM,WAAW,mBAAmB;IACnC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,aAAa,EAAE,4BAA4B,CAAC;IACrD,QAAQ,CAAC,UAAU,EAAE,yBAAyB,CAAC;CAC/C;AAED,qBAAa,wBAAyB,SAAQ,KAAK;IAClD,SAAkB,IAAI,8BAA8B;IACpD,cAEC;CACD;AAED,qBAAa,6BAA8B,SAAQ,KAAK;IACvD,SAAkB,IAAI,mCAAmC;IACzD,cAEC;CACD;AASD,wBAAgB,sBAAsB,IAAI,MAAM,CAmB/C;AA8BD,wBAAgB,+BAA+B,CAC9C,IAAI,EAAE,mBAAmB,EAAE,EAC3B,QAAQ,EAAE,SAAS,mBAAmB,EAAE,GACtC,IAAI,CAMN;AAED,wBAAgB,+BAA+B,CAAC,KAAK,EAAE,SAAS,mBAAmB,EAAE,GAAG,IAAI,CAE3F;AAED,wBAAgB,mCAAmC,CAAC,KAAK,EAAE,SAAS,mBAAmB,EAAE,GAAG,IAAI,CAa/F;AAED,wBAAgB,kCAAkC,IAAI,IAAI,CAEzD;AA2BD,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAGtE;AAED,wBAAgB,8BAA8B,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,mBAAmB,CAkD/G;AA8CD,wBAAgB,8BAA8B,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,8BAA8B,CAQjH","sourcesContent":["import { createHash, randomUUID } from \"node:crypto\";\nimport { chmodSync, lstatSync, mkdirSync, rmdirSync } from \"node:fs\";\nimport { hostname, tmpdir, userInfo } from \"node:os\";\nimport { dirname, join } from \"node:path\";\nimport { ensureDurableDirectorySync, fsyncDirectorySync, writeExclusiveFileDurablySync } from \"./durable-file-io.ts\";\nimport {\n\tcreateDurableFileLockRetirement,\n\ttype DurableFileLockRetirement,\n\tretireDurableFileLockSync,\n} from \"./durable-file-lock-cleanup.ts\";\nimport {\n\ttype DurableFileLockOwnerSnapshot,\n\tfileErrorCode,\n\treadDurableFileLockOwnerSync,\n\tsameDurableFileLockOwner,\n} from \"./durable-file-lock-owner.ts\";\n\nexport type DurableFileLockPathObservation =\n\t| { readonly status: \"absent\" }\n\t| { readonly status: \"live\"; readonly pid: number; readonly host: string }\n\t| { readonly status: \"unknown\" };\n\nexport interface HeldDurableFileLock {\n\treadonly key: string;\n\treadonly path: string;\n\treadonly token: string;\n\treadonly expectedOwner: DurableFileLockOwnerSnapshot;\n\treadonly retirement: DurableFileLockRetirement;\n}\n\nexport class DurableFileLockBusyError extends Error {\n\toverride readonly name = \"DurableFileLockBusyError\";\n\tconstructor() {\n\t\tsuper(\"Durable file lock is held by another live process\");\n\t}\n}\n\nexport class DurableFileLockOwnershipError extends Error {\n\toverride readonly name = \"DurableFileLockOwnershipError\";\n\tconstructor() {\n\t\tsuper(\"Durable file lock ownership changed before retirement\");\n\t}\n}\n\nconst USER = userInfo();\nconst USER_KEY =\n\tUSER.uid >= 0 ? String(USER.uid) : createHash(\"sha256\").update(USER.username).digest(\"hex\").slice(0, 16);\nconst ROOT = join(tmpdir(), `omk-persistence-${USER_KEY}`);\nconst WAITER = new Int32Array(new SharedArrayBuffer(4));\nconst retryableLocks: HeldDurableFileLock[] = [];\n\nexport function getDurableFileLockRoot(): string {\n\tlet value: ReturnType<typeof lstatSync>;\n\ttry {\n\t\tvalue = lstatSync(ROOT);\n\t} catch (error) {\n\t\tif (fileErrorCode(error) !== \"ENOENT\") throw error;\n\t\tensureDurableDirectorySync(ROOT);\n\t\tvalue = lstatSync(ROOT);\n\t}\n\tif (!value.isDirectory() || value.isSymbolicLink()) throw new Error(\"Persistence lock root is not a real directory\");\n\tif (typeof process.getuid === \"function\" && value.uid !== process.getuid()) {\n\t\tthrow new Error(\"Persistence lock root is not owned by the current user\");\n\t}\n\tif ((value.mode & 0o7777) !== 0o700) {\n\t\tchmodSync(ROOT, 0o700);\n\t\tvalue = lstatSync(ROOT);\n\t\tif ((value.mode & 0o7777) !== 0o700) throw new Error(\"Persistence lock root mode is not private\");\n\t}\n\treturn ROOT;\n}\n\nfunction ownerPath(path: string): string {\n\treturn join(path, \"owner.json\");\n}\n\nfunction processIsLive(pid: number): boolean {\n\ttry {\n\t\tprocess.kill(pid, 0);\n\t\treturn true;\n\t} catch (error) {\n\t\tif (!(error instanceof Error)) throw error;\n\t\treturn fileErrorCode(error) !== \"ESRCH\";\n\t}\n}\n\nfunction removeOwnedLock(lock: HeldDurableFileLock): void {\n\tif (lock.retirement.retiredPath === undefined) {\n\t\tlet current: DurableFileLockOwnerSnapshot;\n\t\ttry {\n\t\t\tcurrent = readDurableFileLockOwnerSync(lock.path);\n\t\t} catch (error) {\n\t\t\tif (fileErrorCode(error) === \"ENOENT\") throw new DurableFileLockOwnershipError();\n\t\t\tthrow error;\n\t\t}\n\t\tif (!sameDurableFileLockOwner(current, lock.expectedOwner)) throw new DurableFileLockOwnershipError();\n\t}\n\tretireDurableFileLockSync(lock.path, lock.retirement);\n}\n\nexport function releaseDurableFileLockPathsSync(\n\theld: HeldDurableFileLock[],\n\tselected: readonly HeldDurableFileLock[],\n): void {\n\tfor (const lock of [...selected].reverse()) {\n\t\tremoveOwnedLock(lock);\n\t\tconst index = held.indexOf(lock);\n\t\tif (index >= 0) held.splice(index, 1);\n\t}\n}\n\nexport function retainRetryableDurableFileLocks(locks: readonly HeldDurableFileLock[]): void {\n\tfor (const lock of locks) if (!retryableLocks.includes(lock)) retryableLocks.push(lock);\n}\n\nexport function assertDurableFileLockPathsOwnedSync(locks: readonly HeldDurableFileLock[]): void {\n\tfor (const lock of locks) {\n\t\tif (retryableLocks.includes(lock) || lock.retirement.retiredPath !== undefined)\n\t\t\tthrow new DurableFileLockOwnershipError();\n\t\tlet current: DurableFileLockOwnerSnapshot;\n\t\ttry {\n\t\t\tcurrent = readDurableFileLockOwnerSync(lock.path);\n\t\t} catch (error) {\n\t\t\tif (fileErrorCode(error) === \"ENOENT\") throw new DurableFileLockOwnershipError();\n\t\t\tthrow error;\n\t\t}\n\t\tif (!sameDurableFileLockOwner(current, lock.expectedOwner)) throw new DurableFileLockOwnershipError();\n\t}\n}\n\nexport function drainRetryableDurableFileLocksSync(): void {\n\treleaseDurableFileLockPathsSync(retryableLocks, retryableLocks);\n}\n\nfunction ownerObservation(lockPath: string): DurableFileLockOwnerSnapshot | DurableFileLockPathObservation {\n\ttry {\n\t\treturn readDurableFileLockOwnerSync(lockPath);\n\t} catch (error) {\n\t\tif (!(error instanceof Error)) throw error;\n\t\tif (fileErrorCode(error) === \"ENOENT\") {\n\t\t\ttry {\n\t\t\t\tlstatSync(lockPath);\n\t\t\t} catch (pathError) {\n\t\t\t\tif (!(pathError instanceof Error)) throw pathError;\n\t\t\t\tif (fileErrorCode(pathError) === \"ENOENT\") return { status: \"absent\" };\n\t\t\t}\n\t\t}\n\t\treturn { status: \"unknown\" };\n\t}\n}\n\nfunction reclaimGatePath(lockPath: string, observed: DurableFileLockOwnerSnapshot): string {\n\tconst ownerKey = createHash(\"sha256\")\n\t\t.update(`${observed.owner.token}\\0${observed.dev}\\0${observed.ino}`)\n\t\t.digest(\"hex\")\n\t\t.slice(0, 24);\n\treturn `${lockPath}.reclaim-${ownerKey}.lock`;\n}\n\nexport function durableFileLockPath(scope: string, key: string): string {\n\tif (!/^[a-z-]+$/u.test(scope)) throw new TypeError(\"Durable file lock scope is invalid\");\n\treturn join(getDurableFileLockRoot(), `${scope}-${key}.lock`);\n}\n\nexport function acquireDurableFileLockPathSync(key: string, path: string, deadline: number): HeldDurableFileLock {\n\twhile (true) {\n\t\tconst token = randomUUID();\n\t\ttry {\n\t\t\tmkdirSync(path, { mode: 0o700 });\n\t\t} catch (error) {\n\t\t\tif (fileErrorCode(error) !== \"EEXIST\") throw error;\n\t\t\tif (inspectDurableFileLockPathSync(path, deadline).status === \"absent\") continue;\n\t\t\tif (Date.now() >= deadline) throw new DurableFileLockBusyError();\n\t\t\tAtomics.wait(WAITER, 0, 0, Math.min(10, deadline - Date.now()));\n\t\t\tcontinue;\n\t\t}\n\t\tconst directory = lstatSync(path, { bigint: true });\n\t\tif (!directory.isDirectory() || directory.isSymbolicLink()) throw new DurableFileLockOwnershipError();\n\t\tconst expectedOwner: DurableFileLockOwnerSnapshot = Object.freeze({\n\t\t\towner: Object.freeze({ pid: process.pid, host: hostname(), token }),\n\t\t\tdev: directory.dev.toString(),\n\t\t\tino: directory.ino.toString(),\n\t\t});\n\t\tconst lock: HeldDurableFileLock = {\n\t\t\tkey,\n\t\t\tpath,\n\t\t\ttoken,\n\t\t\texpectedOwner,\n\t\t\tretirement: createDurableFileLockRetirement(),\n\t\t};\n\t\tlet ownerWritten = false;\n\t\ttry {\n\t\t\tfsyncDirectorySync(dirname(path));\n\t\t\twriteExclusiveFileDurablySync(ownerPath(path), Buffer.from(JSON.stringify(expectedOwner.owner), \"utf8\"));\n\t\t\townerWritten = true;\n\t\t\tassertDurableFileLockPathsOwnedSync([lock]);\n\t\t\treturn lock;\n\t\t} catch (error) {\n\t\t\tlet cleanupError: unknown;\n\t\t\ttry {\n\t\t\t\tif (ownerWritten) releaseDurableFileLockPathsSync([lock], [lock]);\n\t\t\t\telse {\n\t\t\t\t\trmdirSync(path);\n\t\t\t\t\tfsyncDirectorySync(dirname(path));\n\t\t\t\t}\n\t\t\t} catch (failure) {\n\t\t\t\tcleanupError = failure instanceof Error ? failure : new Error(String(failure));\n\t\t\t\tif (ownerWritten && !(cleanupError instanceof DurableFileLockOwnershipError))\n\t\t\t\t\tretainRetryableDurableFileLocks([lock]);\n\t\t\t}\n\t\t\tif (cleanupError) throw new AggregateError([error, cleanupError], \"Durable file lock creation cleanup failed\");\n\t\t\tthrow error;\n\t\t}\n\t}\n}\n\nfunction reclaim(lockPath: string, observed: DurableFileLockOwnerSnapshot, deadline: number): boolean {\n\tconst gate = acquireDurableFileLockPathSync(\n\t\t`reclaim:${observed.owner.token}`,\n\t\treclaimGatePath(lockPath, observed),\n\t\tdeadline,\n\t);\n\tconst stale: HeldDurableFileLock = {\n\t\tkey: `stale:${observed.owner.token}`,\n\t\tpath: lockPath,\n\t\ttoken: observed.owner.token,\n\t\texpectedOwner: observed,\n\t\tretirement: createDurableFileLockRetirement(),\n\t};\n\tlet failure: unknown;\n\tlet retired = false;\n\ttry {\n\t\tconst current = readDurableFileLockOwnerSync(lockPath);\n\t\tif (sameDurableFileLockOwner(current, observed)) {\n\t\t\tremoveOwnedLock(stale);\n\t\t\tretired = true;\n\t\t}\n\t} catch (error) {\n\t\tif (fileErrorCode(error) === \"ENOENT\") {\n\t\t\ttry {\n\t\t\t\tlstatSync(lockPath);\n\t\t\t} catch (pathError) {\n\t\t\t\tif (fileErrorCode(pathError) === \"ENOENT\") retired = true;\n\t\t\t\telse failure = pathError instanceof Error ? pathError : new Error(String(pathError));\n\t\t\t}\n\t\t}\n\t\tif (!retired && failure === undefined) failure = error instanceof Error ? error : new Error(String(error));\n\t\tif (stale.retirement.retiredPath !== undefined) retainRetryableDurableFileLocks([stale]);\n\t}\n\ttry {\n\t\treleaseDurableFileLockPathsSync([gate], [gate]);\n\t} catch (error) {\n\t\tretainRetryableDurableFileLocks([gate]);\n\t\tconst cleanupError = error instanceof Error ? error : new Error(String(error));\n\t\tfailure = failure ? new AggregateError([failure, cleanupError], \"Durable lock reclaim failed\") : cleanupError;\n\t}\n\tif (failure) throw failure;\n\treturn retired;\n}\n\nexport function inspectDurableFileLockPathSync(lockPath: string, deadline: number): DurableFileLockPathObservation {\n\tconst observed = ownerObservation(lockPath);\n\tif (\"status\" in observed) return observed;\n\tif (observed.owner.host !== hostname()) return { status: \"unknown\" };\n\tif (processIsLive(observed.owner.pid)) {\n\t\treturn { status: \"live\", pid: observed.owner.pid, host: observed.owner.host };\n\t}\n\treturn reclaim(lockPath, observed, deadline) ? { status: \"absent\" } : { status: \"unknown\" };\n}\n"]}