@openclaw/fs-safe 0.11.0 → 0.13.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 (416) hide show
  1. package/CHANGELOG.md +119 -0
  2. package/README.md +15 -8
  3. package/dist/absolute-path.d.ts.map +1 -1
  4. package/dist/absolute-path.js +32 -11
  5. package/dist/advanced.d.ts +3 -1
  6. package/dist/advanced.d.ts.map +1 -1
  7. package/dist/advanced.js +3 -1
  8. package/dist/archive-entry.d.ts.map +1 -1
  9. package/dist/archive-entry.js +4 -3
  10. package/dist/archive-gzip-tail.d.ts.map +1 -1
  11. package/dist/archive-gzip-tail.js +13 -9
  12. package/dist/archive-input.d.ts.map +1 -1
  13. package/dist/archive-input.js +6 -2
  14. package/dist/archive-merge.d.ts.map +1 -1
  15. package/dist/archive-merge.js +15 -2
  16. package/dist/archive-native.d.ts.map +1 -1
  17. package/dist/archive-native.js +7 -19
  18. package/dist/archive-plan.js +1 -1
  19. package/dist/archive-read.d.ts.map +1 -1
  20. package/dist/archive-read.js +55 -46
  21. package/dist/archive-staging.d.ts.map +1 -1
  22. package/dist/archive-staging.js +43 -10
  23. package/dist/archive-tar-inspect.d.ts.map +1 -1
  24. package/dist/archive-tar-inspect.js +4 -1
  25. package/dist/archive-zip-admission.d.ts +1 -1
  26. package/dist/archive-zip-admission.d.ts.map +1 -1
  27. package/dist/archive-zip-admission.js +2 -2
  28. package/dist/archive-zip-directory.d.ts +3 -0
  29. package/dist/archive-zip-directory.d.ts.map +1 -1
  30. package/dist/archive-zip-directory.js +13 -2
  31. package/dist/archive-zip-loader.d.ts +3 -2
  32. package/dist/archive-zip-loader.d.ts.map +1 -1
  33. package/dist/archive-zip-loader.js +30 -4
  34. package/dist/archive-zip-manifest.d.ts +5 -0
  35. package/dist/archive-zip-manifest.d.ts.map +1 -0
  36. package/dist/archive-zip-manifest.js +22 -0
  37. package/dist/archive-zip-names.d.ts +6 -1
  38. package/dist/archive-zip-names.d.ts.map +1 -1
  39. package/dist/archive-zip-names.js +50 -17
  40. package/dist/archive-zip-preflight.d.ts.map +1 -1
  41. package/dist/archive-zip-preflight.js +3 -2
  42. package/dist/archive.d.ts.map +1 -1
  43. package/dist/archive.js +9 -4
  44. package/dist/bounded-read.js +2 -2
  45. package/dist/darwin-acl.d.ts +4 -0
  46. package/dist/darwin-acl.d.ts.map +1 -0
  47. package/dist/darwin-acl.js +24 -0
  48. package/dist/deny-mutations.d.ts.map +1 -1
  49. package/dist/deny-mutations.js +8 -2
  50. package/dist/device-path.d.ts.map +1 -1
  51. package/dist/device-path.js +5 -3
  52. package/dist/directory-durability.d.ts.map +1 -1
  53. package/dist/directory-durability.js +67 -23
  54. package/dist/directory-entry-path.d.ts +3 -0
  55. package/dist/directory-entry-path.d.ts.map +1 -0
  56. package/dist/directory-entry-path.js +21 -0
  57. package/dist/directory-guard.d.ts +17 -1
  58. package/dist/directory-guard.d.ts.map +1 -1
  59. package/dist/directory-guard.js +134 -48
  60. package/dist/directory-mode-node.d.ts +12 -0
  61. package/dist/directory-mode-node.d.ts.map +1 -1
  62. package/dist/directory-mode-node.js +102 -4
  63. package/dist/effective-uid.d.ts +2 -0
  64. package/dist/effective-uid.d.ts.map +1 -0
  65. package/dist/effective-uid.js +25 -0
  66. package/dist/file-handle-transfer.d.ts.map +1 -1
  67. package/dist/file-handle-transfer.js +98 -27
  68. package/dist/file-hash.d.ts.map +1 -1
  69. package/dist/file-hash.js +12 -2
  70. package/dist/file-lock-sync.d.ts.map +1 -1
  71. package/dist/file-lock-sync.js +36 -6
  72. package/dist/file-lock.d.ts.map +1 -1
  73. package/dist/file-lock.js +29 -9
  74. package/dist/file-observation.d.ts +1 -1
  75. package/dist/file-observation.d.ts.map +1 -1
  76. package/dist/file-store-boundary.d.ts +6 -2
  77. package/dist/file-store-boundary.d.ts.map +1 -1
  78. package/dist/file-store-boundary.js +20 -65
  79. package/dist/file-store-copy-source.d.ts +5 -0
  80. package/dist/file-store-copy-source.d.ts.map +1 -0
  81. package/dist/file-store-copy-source.js +31 -0
  82. package/dist/file-store-path.d.ts.map +1 -1
  83. package/dist/file-store-path.js +4 -1
  84. package/dist/file-store-prune.d.ts.map +1 -1
  85. package/dist/file-store-prune.js +9 -1
  86. package/dist/file-store-sync-directory.d.ts +16 -0
  87. package/dist/file-store-sync-directory.d.ts.map +1 -0
  88. package/dist/file-store-sync-directory.js +349 -0
  89. package/dist/file-store.d.ts.map +1 -1
  90. package/dist/file-store.js +11 -28
  91. package/dist/filename.d.ts +1 -0
  92. package/dist/filename.d.ts.map +1 -1
  93. package/dist/filename.js +85 -23
  94. package/dist/fs.d.ts.map +1 -1
  95. package/dist/fs.js +3 -2
  96. package/dist/guarded-mkdir.d.ts +10 -0
  97. package/dist/guarded-mkdir.d.ts.map +1 -1
  98. package/dist/guarded-mkdir.js +217 -33
  99. package/dist/guest.d.ts.map +1 -1
  100. package/dist/guest.js +16 -5
  101. package/dist/home-dir.d.ts.map +1 -1
  102. package/dist/home-dir.js +73 -10
  103. package/dist/install-path.d.ts +6 -0
  104. package/dist/install-path.d.ts.map +1 -1
  105. package/dist/install-path.js +67 -19
  106. package/dist/json-durable-queue-ownership.d.ts +8 -0
  107. package/dist/json-durable-queue-ownership.d.ts.map +1 -1
  108. package/dist/json-durable-queue-ownership.js +134 -43
  109. package/dist/json-durable-queue-paths.d.ts +11 -0
  110. package/dist/json-durable-queue-paths.d.ts.map +1 -0
  111. package/dist/json-durable-queue-paths.js +42 -0
  112. package/dist/json-durable-queue-read.d.ts +6 -0
  113. package/dist/json-durable-queue-read.d.ts.map +1 -0
  114. package/dist/json-durable-queue-read.js +61 -0
  115. package/dist/json-durable-queue.d.ts +1 -1
  116. package/dist/json-durable-queue.d.ts.map +1 -1
  117. package/dist/json-durable-queue.js +83 -134
  118. package/dist/json-store.d.ts.map +1 -1
  119. package/dist/json-store.js +5 -1
  120. package/dist/json.d.ts.map +1 -1
  121. package/dist/json.js +54 -22
  122. package/dist/local-file-access.d.ts.map +1 -1
  123. package/dist/local-file-access.js +4 -0
  124. package/dist/local-file-descriptor.d.ts +17 -0
  125. package/dist/local-file-descriptor.d.ts.map +1 -0
  126. package/dist/local-file-descriptor.js +84 -0
  127. package/dist/local-roots.d.ts.map +1 -1
  128. package/dist/local-roots.js +35 -7
  129. package/dist/move-path-cleanup.d.ts +2 -0
  130. package/dist/move-path-cleanup.d.ts.map +1 -1
  131. package/dist/move-path-cleanup.js +44 -18
  132. package/dist/move-path.d.ts.map +1 -1
  133. package/dist/move-path.js +31 -6
  134. package/dist/native-binding.d.ts +21 -0
  135. package/dist/native-binding.d.ts.map +1 -1
  136. package/dist/native-directory-observation.d.ts +17 -0
  137. package/dist/native-directory-observation.d.ts.map +1 -0
  138. package/dist/native-directory-observation.js +37 -0
  139. package/dist/native-parent-admission.d.ts +31 -0
  140. package/dist/native-parent-admission.d.ts.map +1 -0
  141. package/dist/native-parent-admission.js +124 -0
  142. package/dist/native-pinned-write-windows.d.ts +0 -1
  143. package/dist/native-pinned-write-windows.d.ts.map +1 -1
  144. package/dist/native-pinned-write-windows.js +0 -3
  145. package/dist/native-pinned-write.d.ts.map +1 -1
  146. package/dist/native-pinned-write.js +309 -40
  147. package/dist/output.d.ts.map +1 -1
  148. package/dist/output.js +15 -5
  149. package/dist/overwrite-file-handle.d.ts.map +1 -1
  150. package/dist/overwrite-file-handle.js +5 -1
  151. package/dist/owner-dacl.d.ts.map +1 -1
  152. package/dist/owner-dacl.js +2 -0
  153. package/dist/path-policy.d.ts.map +1 -1
  154. package/dist/path-policy.js +7 -2
  155. package/dist/path-prefix.d.ts +7 -0
  156. package/dist/path-prefix.d.ts.map +1 -0
  157. package/dist/path-prefix.js +82 -0
  158. package/dist/path-scope-lexical.d.ts +14 -0
  159. package/dist/path-scope-lexical.d.ts.map +1 -0
  160. package/dist/path-scope-lexical.js +37 -0
  161. package/dist/path-segment-route.d.ts +7 -0
  162. package/dist/path-segment-route.d.ts.map +1 -0
  163. package/dist/path-segment-route.js +24 -0
  164. package/dist/path-suffix-aliases.d.ts +10 -0
  165. package/dist/path-suffix-aliases.d.ts.map +1 -0
  166. package/dist/path-suffix-aliases.js +386 -0
  167. package/dist/path.d.ts.map +1 -1
  168. package/dist/path.js +35 -6
  169. package/dist/permissions-windows.d.ts.map +1 -1
  170. package/dist/permissions-windows.js +14 -3
  171. package/dist/permissions.d.ts.map +1 -1
  172. package/dist/permissions.js +37 -8
  173. package/dist/pinned-mutation-admission.d.ts +25 -0
  174. package/dist/pinned-mutation-admission.d.ts.map +1 -0
  175. package/dist/pinned-mutation-admission.js +425 -0
  176. package/dist/pinned-mutation-observation.d.ts +34 -0
  177. package/dist/pinned-mutation-observation.d.ts.map +1 -0
  178. package/dist/pinned-mutation-observation.js +142 -0
  179. package/dist/pinned-mutation-shared-route.d.ts +24 -0
  180. package/dist/pinned-mutation-shared-route.d.ts.map +1 -0
  181. package/dist/pinned-mutation-shared-route.js +70 -0
  182. package/dist/pinned-open.d.ts +6 -0
  183. package/dist/pinned-open.d.ts.map +1 -1
  184. package/dist/pinned-open.js +28 -10
  185. package/dist/pinned-write-types.d.ts +75 -0
  186. package/dist/pinned-write-types.d.ts.map +1 -0
  187. package/dist/pinned-write-types.js +1 -0
  188. package/dist/pinned-write.d.ts +7 -33
  189. package/dist/pinned-write.d.ts.map +1 -1
  190. package/dist/pinned-write.js +152 -17
  191. package/dist/private-directory.d.ts.map +1 -1
  192. package/dist/private-directory.js +2 -0
  193. package/dist/private-producer-handoff.d.ts +16 -0
  194. package/dist/private-producer-handoff.d.ts.map +1 -0
  195. package/dist/private-producer-handoff.js +272 -0
  196. package/dist/private-temp-workspace.d.ts +2 -39
  197. package/dist/private-temp-workspace.d.ts.map +1 -1
  198. package/dist/private-temp-workspace.js +183 -77
  199. package/dist/publish-file.d.ts.map +1 -1
  200. package/dist/publish-file.js +33 -29
  201. package/dist/regular-file.d.ts.map +1 -1
  202. package/dist/regular-file.js +56 -45
  203. package/dist/replace-directory.d.ts.map +1 -1
  204. package/dist/replace-directory.js +12 -5
  205. package/dist/replace-file-temp-owner.d.ts +1 -0
  206. package/dist/replace-file-temp-owner.d.ts.map +1 -1
  207. package/dist/replace-file-temp-owner.js +12 -1
  208. package/dist/replace-file.d.ts.map +1 -1
  209. package/dist/replace-file.js +24 -24
  210. package/dist/root-boundary.d.ts +29 -0
  211. package/dist/root-boundary.d.ts.map +1 -0
  212. package/dist/root-boundary.js +182 -0
  213. package/dist/root-context.d.ts +12 -1
  214. package/dist/root-context.d.ts.map +1 -1
  215. package/dist/root-context.js +89 -16
  216. package/dist/root-directory-creation.d.ts +13 -0
  217. package/dist/root-directory-creation.d.ts.map +1 -0
  218. package/dist/root-directory-creation.js +212 -0
  219. package/dist/root-directory-list.d.ts +16 -4
  220. package/dist/root-directory-list.d.ts.map +1 -1
  221. package/dist/root-directory-list.js +182 -33
  222. package/dist/root-directory.d.ts +23 -0
  223. package/dist/root-directory.d.ts.map +1 -0
  224. package/dist/root-directory.js +141 -0
  225. package/dist/root-errors.d.ts.map +1 -1
  226. package/dist/root-errors.js +12 -1
  227. package/dist/root-file-final-admission.d.ts +21 -0
  228. package/dist/root-file-final-admission.d.ts.map +1 -0
  229. package/dist/root-file-final-admission.js +83 -0
  230. package/dist/root-file.d.ts.map +1 -1
  231. package/dist/root-file.js +103 -24
  232. package/dist/root-impl.d.ts +1 -0
  233. package/dist/root-impl.d.ts.map +1 -1
  234. package/dist/root-impl.js +316 -325
  235. package/dist/root-move-noreplace.d.ts +14 -0
  236. package/dist/root-move-noreplace.d.ts.map +1 -0
  237. package/dist/root-move-noreplace.js +202 -0
  238. package/dist/root-move-preflight.d.ts +8 -0
  239. package/dist/root-move-preflight.d.ts.map +1 -0
  240. package/dist/root-move-preflight.js +16 -0
  241. package/dist/root-observed-path.d.ts +9 -0
  242. package/dist/root-observed-path.d.ts.map +1 -0
  243. package/dist/root-observed-path.js +95 -0
  244. package/dist/root-path-errors.d.ts +12 -0
  245. package/dist/root-path-errors.d.ts.map +1 -0
  246. package/dist/root-path-errors.js +13 -0
  247. package/dist/root-path-existing.d.ts +2 -0
  248. package/dist/root-path-existing.d.ts.map +1 -1
  249. package/dist/root-path-existing.js +63 -22
  250. package/dist/root-path-observation.d.ts +63 -0
  251. package/dist/root-path-observation.d.ts.map +1 -0
  252. package/dist/root-path-observation.js +180 -0
  253. package/dist/root-path-stat.d.ts +5 -0
  254. package/dist/root-path-stat.d.ts.map +1 -0
  255. package/dist/root-path-stat.js +101 -0
  256. package/dist/root-path-symlink.d.ts.map +1 -1
  257. package/dist/root-path-symlink.js +23 -4
  258. package/dist/root-path.d.ts +13 -0
  259. package/dist/root-path.d.ts.map +1 -1
  260. package/dist/root-path.js +263 -73
  261. package/dist/root-paths-lexical.d.ts +9 -0
  262. package/dist/root-paths-lexical.d.ts.map +1 -0
  263. package/dist/root-paths-lexical.js +22 -0
  264. package/dist/root-paths.d.ts +3 -29
  265. package/dist/root-paths.d.ts.map +1 -1
  266. package/dist/root-paths.js +89 -178
  267. package/dist/root-read-admission.d.ts +26 -0
  268. package/dist/root-read-admission.d.ts.map +1 -0
  269. package/dist/root-read-admission.js +94 -0
  270. package/dist/root-remove-identity.d.ts +15 -0
  271. package/dist/root-remove-identity.d.ts.map +1 -0
  272. package/dist/root-remove-identity.js +89 -0
  273. package/dist/root-remove-receipt.d.ts +17 -0
  274. package/dist/root-remove-receipt.d.ts.map +1 -0
  275. package/dist/root-remove-receipt.js +37 -0
  276. package/dist/root-remove.d.ts +2 -1
  277. package/dist/root-remove.d.ts.map +1 -1
  278. package/dist/root-remove.js +172 -10
  279. package/dist/root-walk.d.ts.map +1 -1
  280. package/dist/root-walk.js +2 -1
  281. package/dist/root-write-admission.d.ts +68 -0
  282. package/dist/root-write-admission.d.ts.map +1 -0
  283. package/dist/root-write-admission.js +322 -0
  284. package/dist/root-write-compatibility.d.ts +13 -0
  285. package/dist/root-write-compatibility.d.ts.map +1 -0
  286. package/dist/root-write-compatibility.js +74 -0
  287. package/dist/root-write-complete-parent.d.ts +42 -0
  288. package/dist/root-write-complete-parent.d.ts.map +1 -0
  289. package/dist/root-write-complete-parent.js +195 -0
  290. package/dist/root-write-mode.d.ts +2 -0
  291. package/dist/root-write-mode.d.ts.map +1 -1
  292. package/dist/root-write-mode.js +20 -7
  293. package/dist/root-write-publication.d.ts +24 -0
  294. package/dist/root-write-publication.d.ts.map +1 -0
  295. package/dist/root-write-publication.js +85 -0
  296. package/dist/root-write-verification.d.ts.map +1 -1
  297. package/dist/root-write-verification.js +18 -7
  298. package/dist/safe-path-segment.d.ts.map +1 -1
  299. package/dist/safe-path-segment.js +3 -1
  300. package/dist/secret-file.d.ts.map +1 -1
  301. package/dist/secret-file.js +62 -24
  302. package/dist/secret-read-async.d.ts.map +1 -1
  303. package/dist/secret-read-async.js +18 -13
  304. package/dist/secret-read-policy.d.ts.map +1 -1
  305. package/dist/secret-read-policy.js +5 -1
  306. package/dist/secure-file-windows.d.ts +10 -0
  307. package/dist/secure-file-windows.d.ts.map +1 -0
  308. package/dist/secure-file-windows.js +186 -0
  309. package/dist/secure-file.d.ts.map +1 -1
  310. package/dist/secure-file.js +98 -10
  311. package/dist/secure-temp-dir.d.ts +6 -3
  312. package/dist/secure-temp-dir.d.ts.map +1 -1
  313. package/dist/secure-temp-dir.js +121 -101
  314. package/dist/secure-temp-repair.d.ts +35 -0
  315. package/dist/secure-temp-repair.d.ts.map +1 -0
  316. package/dist/secure-temp-repair.js +106 -0
  317. package/dist/sibling-staged-file.d.ts +3 -1
  318. package/dist/sibling-staged-file.d.ts.map +1 -1
  319. package/dist/sibling-staged-file.js +181 -50
  320. package/dist/sibling-temp.d.ts.map +1 -1
  321. package/dist/sibling-temp.js +27 -12
  322. package/dist/sidecar-lock-acquire.d.ts.map +1 -1
  323. package/dist/sidecar-lock-acquire.js +45 -15
  324. package/dist/sidecar-lock-root.d.ts.map +1 -1
  325. package/dist/sidecar-lock-root.js +4 -2
  326. package/dist/sidecar-lock.d.ts.map +1 -1
  327. package/dist/sidecar-lock.js +2 -3
  328. package/dist/staged-directory.d.ts +14 -5
  329. package/dist/staged-directory.d.ts.map +1 -1
  330. package/dist/staged-directory.js +54 -3
  331. package/dist/standalone-publication-path.d.ts +2 -0
  332. package/dist/standalone-publication-path.d.ts.map +1 -0
  333. package/dist/standalone-publication-path.js +6 -0
  334. package/dist/stat-observation.d.ts +11 -0
  335. package/dist/stat-observation.d.ts.map +1 -0
  336. package/dist/stat-observation.js +64 -0
  337. package/dist/strict-file-identity.d.ts.map +1 -1
  338. package/dist/strict-file-identity.js +39 -2
  339. package/dist/symlink-parents.d.ts.map +1 -1
  340. package/dist/symlink-parents.js +7 -2
  341. package/dist/temp-target.d.ts +2 -0
  342. package/dist/temp-target.d.ts.map +1 -1
  343. package/dist/temp-target.js +133 -4
  344. package/dist/temp-workspace-admission.d.ts +22 -0
  345. package/dist/temp-workspace-admission.d.ts.map +1 -0
  346. package/dist/temp-workspace-admission.js +374 -0
  347. package/dist/temp-workspace-child-admission.d.ts +13 -0
  348. package/dist/temp-workspace-child-admission.d.ts.map +1 -0
  349. package/dist/temp-workspace-child-admission.js +95 -0
  350. package/dist/temp-workspace-descriptor.d.ts +48 -0
  351. package/dist/temp-workspace-descriptor.d.ts.map +1 -0
  352. package/dist/temp-workspace-descriptor.js +363 -0
  353. package/dist/temp-workspace-identity.d.ts +15 -0
  354. package/dist/temp-workspace-identity.d.ts.map +1 -0
  355. package/dist/temp-workspace-identity.js +41 -0
  356. package/dist/temp-workspace-owner.d.ts +7 -6
  357. package/dist/temp-workspace-owner.d.ts.map +1 -1
  358. package/dist/temp-workspace-owner.js +121 -61
  359. package/dist/temp-workspace-permissions.d.ts +4 -0
  360. package/dist/temp-workspace-permissions.d.ts.map +1 -0
  361. package/dist/temp-workspace-permissions.js +32 -0
  362. package/dist/temp-workspace-types.d.ts +40 -0
  363. package/dist/temp-workspace-types.d.ts.map +1 -0
  364. package/dist/temp-workspace-types.js +1 -0
  365. package/dist/temp.d.ts +1 -1
  366. package/dist/temp.d.ts.map +1 -1
  367. package/dist/temp.js +1 -1
  368. package/dist/test-hooks.d.ts +7 -0
  369. package/dist/test-hooks.d.ts.map +1 -1
  370. package/dist/text-atomic.d.ts.map +1 -1
  371. package/dist/text-atomic.js +3 -1
  372. package/dist/trash.d.ts.map +1 -1
  373. package/dist/trash.js +54 -8
  374. package/dist/walk.d.ts.map +1 -1
  375. package/dist/walk.js +20 -16
  376. package/dist/windows-owner.d.ts.map +1 -1
  377. package/dist/windows-owner.js +5 -0
  378. package/dist/windows-path-alias.d.ts +39 -0
  379. package/dist/windows-path-alias.d.ts.map +1 -0
  380. package/dist/windows-path-alias.js +153 -0
  381. package/docs/advanced.md +28 -1
  382. package/docs/archive.md +29 -2
  383. package/docs/atomic.md +27 -3
  384. package/docs/contributing.md +4 -1
  385. package/docs/copy.md +4 -1
  386. package/docs/durability.md +18 -2
  387. package/docs/errors.md +14 -6
  388. package/docs/file-store.md +30 -3
  389. package/docs/filename.md +21 -7
  390. package/docs/guest.md +5 -0
  391. package/docs/install-path.md +61 -15
  392. package/docs/install.md +12 -8
  393. package/docs/json-store.md +5 -1
  394. package/docs/json.md +11 -0
  395. package/docs/mutation-policy-proof.md +65 -0
  396. package/docs/native-helper.md +28 -9
  397. package/docs/native.md +46 -10
  398. package/docs/output.md +33 -11
  399. package/docs/path-prefix.md +64 -0
  400. package/docs/path-suffix-aliases.md +159 -0
  401. package/docs/path.md +12 -1
  402. package/docs/permissions.md +26 -1
  403. package/docs/private-file-store.md +14 -0
  404. package/docs/public-api.md +14 -0
  405. package/docs/quickstart.md +1 -1
  406. package/docs/reading.md +8 -1
  407. package/docs/root.md +21 -4
  408. package/docs/secret-file.md +3 -0
  409. package/docs/secure-file.md +21 -16
  410. package/docs/security-model.md +76 -9
  411. package/docs/sidecar-lock.md +28 -1
  412. package/docs/store.md +27 -2
  413. package/docs/temp.md +262 -34
  414. package/docs/test-hooks.md +14 -0
  415. package/docs/writing.md +38 -8
  416. package/package.json +10 -10
@@ -5,61 +5,102 @@ import { sameFileIdentity } from "./file-identity.js";
5
5
  import { inspectFileIdentitySync } from "./strict-file-identity.js";
6
6
  import { isNotFoundPathError } from "./path.js";
7
7
  import { directoryComponentNotDirectoryError } from "./root-errors.js";
8
+ import { assertNoWindowsPathAlias, pathForWindowsFilesystem, resolvePathPreservingWindowsRoot, } from "./windows-path-alias.js";
8
9
  import { realpathSync } from "./realpath.js";
10
+ import { directoryEntryPath } from "./directory-entry-path.js";
11
+ import { assertStatObservationSync, inspectStatObservationSync, } from "./stat-observation.js";
9
12
  export async function readDirectoryIdentity(dir) {
10
13
  const guard = await createAsyncDirectoryGuard(dir, { bigint: true });
11
14
  return Object.freeze({ dev: guard.stat.dev, ino: guard.stat.ino, realPath: guard.realPath });
12
15
  }
13
16
  export function assertDirectoryIdentitySync(observedPath, expected) {
14
- inspectDirectoryIdentitySync(observedPath, expected);
15
- if (expected.realPath !== undefined && realpathSync.native(observedPath) !== expected.realPath) {
17
+ const operationPath = directoryOperationPath(observedPath);
18
+ const expectedDev = expected.dev;
19
+ const expectedIno = expected.ino;
20
+ const expectedRealPath = expected.realPath;
21
+ if (expectedRealPath !== undefined)
22
+ assertNoWindowsPathAlias(expectedRealPath, "filesystem");
23
+ inspectDirectoryIdentityAtPathSync(operationPath, { dev: expectedDev, ino: expectedIno });
24
+ if (expectedRealPath === undefined)
25
+ return;
26
+ const realPath = realpathSync.native(operationPath);
27
+ assertNoWindowsPathAlias(realPath, "filesystem");
28
+ if (realPath !== expectedRealPath) {
16
29
  throw new FsSafeError("path-mismatch", "directory changed during operation");
17
30
  }
18
31
  }
19
32
  export async function createAsyncDirectoryGuard(dir, options) {
20
- const stat = options?.bigint ? inspectDirectoryIdentitySync(dir) : fsSync.lstatSync(dir);
33
+ const operationPath = directoryOperationPath(dir);
34
+ const stat = options?.bigint
35
+ ? inspectDirectoryIdentityAtPathSync(operationPath, undefined, options.initial)
36
+ : fsSync.lstatSync(operationPath);
21
37
  if (stat.isSymbolicLink() || !stat.isDirectory()) {
22
38
  throw directoryComponentNotDirectoryError();
23
39
  }
24
- return { dir, realPath: realpathSync.native(dir), stat };
40
+ const realPath = realpathSync.native(operationPath);
41
+ assertNoWindowsPathAlias(realPath, "filesystem");
42
+ return { dir, realPath, stat };
25
43
  }
26
44
  export async function assertAsyncDirectoryGuard(guard) {
27
- if (typeof guard.stat.dev === "bigint" && typeof guard.stat.ino === "bigint") {
28
- assertDirectoryIdentitySync(guard.dir, { dev: guard.stat.dev, ino: guard.stat.ino, realPath: guard.realPath });
29
- return;
30
- }
31
- const stat = fsSync.lstatSync(guard.dir);
45
+ const dir = guard.dir;
46
+ const operationPath = directoryOperationPath(dir);
47
+ const expectedRealPath = guard.realPath;
48
+ assertNoWindowsPathAlias(expectedRealPath, "filesystem");
49
+ const expectedStat = guard.stat;
50
+ const expectedIdentity = { dev: expectedStat.dev, ino: expectedStat.ino };
51
+ const stat = typeof expectedIdentity.dev === "bigint" && typeof expectedIdentity.ino === "bigint"
52
+ ? inspectDirectoryIdentityAtPathSync(operationPath, {
53
+ dev: expectedIdentity.dev,
54
+ ino: expectedIdentity.ino,
55
+ })
56
+ : fsSync.lstatSync(operationPath);
32
57
  if (stat.isSymbolicLink() || !stat.isDirectory()) {
33
58
  throw directoryComponentNotDirectoryError();
34
59
  }
35
- if (!sameFileIdentity(stat, guard.stat) || realpathSync.native(guard.dir) !== guard.realPath) {
60
+ if (!sameFileIdentity(stat, expectedIdentity)) {
61
+ throw new FsSafeError("path-mismatch", "directory changed during operation");
62
+ }
63
+ const realPath = realpathSync.native(operationPath);
64
+ assertNoWindowsPathAlias(realPath, "filesystem");
65
+ if (realPath !== expectedRealPath) {
36
66
  throw new FsSafeError("path-mismatch", "directory changed during operation");
37
67
  }
38
68
  }
39
69
  export function createSyncDirectoryGuard(dir) {
40
- const stat = fsSync.lstatSync(dir);
70
+ const operationPath = directoryOperationPath(dir);
71
+ const stat = fsSync.lstatSync(operationPath);
41
72
  if (stat.isSymbolicLink() || !stat.isDirectory()) {
42
73
  throw directoryComponentNotDirectoryError();
43
74
  }
44
- return { dir, realPath: realpathSync(dir), stat };
75
+ const realPath = realpathSync(operationPath);
76
+ assertNoWindowsPathAlias(realPath, "filesystem");
77
+ return { dir, realPath, stat };
45
78
  }
46
79
  export function assertSyncDirectoryGuard(guard) {
47
- if (typeof guard.stat.dev === "bigint" && typeof guard.stat.ino === "bigint") {
48
- assertDirectoryIdentitySync(guard.dir, { dev: guard.stat.dev, ino: guard.stat.ino, realPath: guard.realPath });
49
- return;
50
- }
51
- const stat = fsSync.lstatSync(guard.dir);
80
+ const dir = guard.dir;
81
+ const operationPath = directoryOperationPath(dir);
82
+ const expectedRealPath = guard.realPath;
83
+ assertNoWindowsPathAlias(expectedRealPath, "filesystem");
84
+ const expectedStat = guard.stat;
85
+ const expectedIdentity = { dev: expectedStat.dev, ino: expectedStat.ino };
86
+ const stat = typeof expectedIdentity.dev === "bigint" && typeof expectedIdentity.ino === "bigint"
87
+ ? inspectDirectoryIdentityAtPathSync(operationPath, { dev: expectedIdentity.dev, ino: expectedIdentity.ino })
88
+ : fsSync.lstatSync(operationPath);
52
89
  if (stat.isSymbolicLink() || !stat.isDirectory()) {
53
90
  throw directoryComponentNotDirectoryError();
54
91
  }
55
- const realPath = realpathSync(guard.dir);
56
- if (!sameFileIdentity(stat, guard.stat) || realPath !== guard.realPath) {
92
+ const realPath = typeof expectedIdentity.ino === "bigint"
93
+ ? realpathSync.native(operationPath) : realpathSync(operationPath);
94
+ assertNoWindowsPathAlias(realPath, "filesystem");
95
+ if (!sameFileIdentity(stat, expectedIdentity) || realPath !== expectedRealPath) {
57
96
  throw new FsSafeError("path-mismatch", "directory changed during operation");
58
97
  }
59
98
  }
60
99
  export async function createNearestExistingDirectoryGuard(rootReal, targetPath, options = { bigint: false }) {
61
- let current = path.resolve(targetPath);
62
- const root = path.resolve(rootReal);
100
+ assertNoWindowsPathAlias(rootReal, "filesystem");
101
+ assertNoWindowsPathAlias(targetPath, "filesystem");
102
+ let current = resolvePathPreservingWindowsRoot(targetPath);
103
+ const root = resolvePathPreservingWindowsRoot(rootReal);
63
104
  while (current !== root) {
64
105
  try {
65
106
  return await createAsyncDirectoryGuard(current, options);
@@ -74,8 +115,10 @@ export async function createNearestExistingDirectoryGuard(rootReal, targetPath,
74
115
  return await createAsyncDirectoryGuard(root, options);
75
116
  }
76
117
  export function createNearestExistingSyncDirectoryGuard(rootReal, targetPath) {
77
- let current = path.resolve(targetPath);
78
- const root = path.resolve(rootReal);
118
+ assertNoWindowsPathAlias(rootReal, "filesystem");
119
+ assertNoWindowsPathAlias(targetPath, "filesystem");
120
+ let current = resolvePathPreservingWindowsRoot(targetPath);
121
+ const root = resolvePathPreservingWindowsRoot(rootReal);
79
122
  while (current !== root) {
80
123
  try {
81
124
  return createSyncDirectoryGuard(current);
@@ -91,34 +134,77 @@ export function createNearestExistingSyncDirectoryGuard(rootReal, targetPath) {
91
134
  }
92
135
  // Recovery receipts must retain every identity bit, including on Windows.
93
136
  export async function inspectDirectoryIdentity(dir, expected) {
94
- return inspectDirectoryIdentitySync(dir, expected);
95
- }
96
- function directoryEntryPath(dir) {
97
- const windows = process.platform === "win32";
98
- if (typeof dir === "string") {
99
- const last = dir[dir.length - 1];
100
- if (last !== "/" && (!windows || last !== "\\"))
101
- return dir;
102
- }
103
- let rootLength = (windows ? path.win32 : path.posix).parse(dir).root.length;
104
- if (windows && /^[\\/]{2}[?.][\\/]UNC[\\/]/i.test(dir)) {
105
- // Node parses the namespace prefix as the root; retain the complete UNC share instead.
106
- const uncRoot = path.win32.parse(`\\\\${dir.slice(8)}`).root;
107
- if (uncRoot.length > 1)
108
- rootLength = Math.max(rootLength, uncRoot.length + 6);
109
- }
110
- let end = dir.length;
111
- while (end > rootLength && (dir[end - 1] === "/" || (windows && dir[end - 1] === "\\")))
112
- end--;
113
- return end === rootLength || end === dir.length ? dir : dir.slice(0, end);
114
- }
115
- export function inspectDirectoryIdentitySync(dir, expected) {
116
- // A trailing separator makes lstat follow a final directory symlink.
117
- const entryPath = directoryEntryPath(dir);
137
+ const operationPath = directoryOperationPath(dir);
138
+ const expectedIdentity = expected === undefined
139
+ ? undefined
140
+ : { dev: expected.dev, ino: expected.ino };
141
+ return inspectDirectoryIdentityAtPathSync(operationPath, expectedIdentity);
142
+ }
143
+ function directoryOperationPath(dir) {
144
+ assertNoWindowsPathAlias(dir, "filesystem");
145
+ return pathForWindowsFilesystem(directoryEntryPath(dir));
146
+ }
147
+ export function observeDirectoryIdentitySync(dir, options) {
148
+ const entryPath = directoryOperationPath(dir);
149
+ const stat = options?.bigint
150
+ ? fsSync.lstatSync(entryPath, { bigint: true })
151
+ : fsSync.lstatSync(entryPath);
152
+ if (stat.isSymbolicLink() || !stat.isDirectory())
153
+ throw directoryComponentNotDirectoryError();
154
+ return stat;
155
+ }
156
+ export function inspectDirectoryIdentitySync(dir, expected, initial, platform = process.platform) {
157
+ const operationPath = directoryOperationPath(dir);
158
+ const expectedIdentity = expected === undefined
159
+ ? undefined
160
+ : { dev: expected.dev, ino: expected.ino };
161
+ return inspectDirectoryIdentityAtPathSync(operationPath, expectedIdentity, initial, platform);
162
+ }
163
+ function inspectDirectoryIdentityAtPathSync(operationPath, expected, initial, platform = process.platform) {
118
164
  return inspectFileIdentitySync(() => {
119
- const stat = fsSync.lstatSync(entryPath, { bigint: true });
165
+ // Traversal can supply the first exact observation. A bounded retry still
166
+ // uses the admitted operation path and retains every known identity bit.
167
+ const stat = initial ?? fsSync.lstatSync(operationPath, { bigint: true });
168
+ initial = undefined;
169
+ if (stat.isSymbolicLink() || !stat.isDirectory())
170
+ throw directoryComponentNotDirectoryError();
171
+ return stat;
172
+ }, expected, platform);
173
+ }
174
+ export function extendDirectoryObservationGuard(observation, dir, realPath) {
175
+ const guard = observation;
176
+ guard.dir = dir;
177
+ guard.realPath = realPath;
178
+ return guard;
179
+ }
180
+ // Only stat/list receipts use this metadata fast path. Recovery and publication
181
+ // callers retain the BigIntStats contract of inspectDirectoryIdentitySync.
182
+ export function inspectDirectoryObservationSync(dir, expected) {
183
+ const entryPath = directoryOperationPath(dir);
184
+ return inspectStatObservationSync(bigint => {
185
+ const stat = bigint ? fsSync.lstatSync(entryPath, { bigint: true }) : fsSync.lstatSync(entryPath);
120
186
  if (stat.isSymbolicLink() || !stat.isDirectory())
121
187
  throw directoryComponentNotDirectoryError();
122
188
  return stat;
123
189
  }, expected);
124
190
  }
191
+ export function assertDirectoryObservationSync(dir, expected) {
192
+ const entryPath = directoryOperationPath(dir);
193
+ return assertStatObservationSync(bigint => {
194
+ const stat = bigint ? fsSync.lstatSync(entryPath, { bigint: true }) : fsSync.lstatSync(entryPath);
195
+ if (stat.isSymbolicLink() || !stat.isDirectory())
196
+ throw directoryComponentNotDirectoryError();
197
+ return stat;
198
+ }, expected);
199
+ }
200
+ export function assertDirectoryObservationGuardSync(guard) {
201
+ const dir = guard.dir;
202
+ const expectedRealPath = guard.realPath;
203
+ assertNoWindowsPathAlias(expectedRealPath, "filesystem");
204
+ assertDirectoryObservationSync(dir, guard.identity);
205
+ const realPath = realpathSync.native(directoryOperationPath(dir));
206
+ assertNoWindowsPathAlias(realPath, "filesystem");
207
+ if (realPath !== expectedRealPath) {
208
+ throw new FsSafeError("path-mismatch", "directory changed during operation");
209
+ }
210
+ }
@@ -1,8 +1,20 @@
1
1
  import { type BigIntStats } from "node:fs";
2
2
  import { type DirectoryModeOwner } from "./directory-mode-owner.js";
3
+ export declare function nodeDirectorySearchOnlyFlags(): {
4
+ flags: number;
5
+ proc: boolean;
6
+ } | undefined;
3
7
  /** Real Node only: injected filesystem adapters must retain descriptor-chmod semantics. */
4
8
  export declare function pinNodeDirectoryForMode(dirPath: string, options?: {
5
9
  expectedIdentity?: BigIntStats;
6
10
  ownerUid?: number;
7
11
  }): Promise<DirectoryModeOwner>;
12
+ /** Synchronous counterpart with the same no-follow, identity, and owner checks. */
13
+ export declare function pinNodeDirectoryForModeSync(dirPath: string, options?: {
14
+ expectedIdentity?: BigIntStats;
15
+ ownerUid?: number;
16
+ }): {
17
+ apply(mode: number, check?: () => void): void;
18
+ close(): void;
19
+ };
8
20
  //# sourceMappingURL=directory-mode-node.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"directory-mode-node.d.ts","sourceRoot":"","sources":["../src/directory-mode-node.ts"],"names":[],"mappings":"AAAA,OAAe,EAAa,KAAK,WAAW,EAAE,MAAM,SAAS,CAAC;AAK9D,OAAO,EAA0C,KAAK,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAW5G,2FAA2F;AAC3F,wBAAsB,uBAAuB,CAC3C,OAAO,EAAE,MAAM,EACf,OAAO,GAAE;IAAE,gBAAgB,CAAC,EAAE,WAAW,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAO,GAClE,OAAO,CAAC,kBAAkB,CAAC,CAsE7B"}
1
+ {"version":3,"file":"directory-mode-node.d.ts","sourceRoot":"","sources":["../src/directory-mode-node.ts"],"names":[],"mappings":"AAAA,OAAe,EAAa,KAAK,WAAW,EAAE,MAAM,SAAS,CAAC;AAK9D,OAAO,EAA0C,KAAK,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAE5G,wBAAgB,4BAA4B,IAAI;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,OAAO,CAAA;CAAE,GAAG,SAAS,CAO3F;AAED,2FAA2F;AAC3F,wBAAsB,uBAAuB,CAC3C,OAAO,EAAE,MAAM,EACf,OAAO,GAAE;IAAE,gBAAgB,CAAC,EAAE,WAAW,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAO,GAClE,OAAO,CAAC,kBAAkB,CAAC,CAsE7B;AAED,mFAAmF;AACnF,wBAAgB,2BAA2B,CACzC,OAAO,EAAE,MAAM,EACf,OAAO,GAAE;IAAE,gBAAgB,CAAC,EAAE,WAAW,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAO,GAClE;IAAE,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,IAAI,GAAG,IAAI,CAAC;IAAC,KAAK,IAAI,IAAI,CAAA;CAAE,CAsFlE"}
@@ -1,10 +1,10 @@
1
1
  import fsSync, { constants } from "node:fs";
2
2
  import fs from "node:fs/promises";
3
3
  import { FsSafeError } from "./errors.js";
4
- import { inspectDirectoryIdentity } from "./directory-guard.js";
5
- import { inspectFileIdentity } from "./strict-file-identity.js";
4
+ import { inspectDirectoryIdentity, inspectDirectoryIdentitySync } from "./directory-guard.js";
5
+ import { inspectFileIdentity, inspectFileIdentitySync } from "./strict-file-identity.js";
6
6
  import { assertOwnedDirectory, ownDirectoryMode } from "./directory-mode-owner.js";
7
- function searchOnlyFlags() {
7
+ export function nodeDirectorySearchOnlyFlags() {
8
8
  if (process.arch !== "x64" && process.arch !== "arm64")
9
9
  return undefined;
10
10
  // Darwin SDK O_SEARCH = O_EXEC (0x40000000) | O_DIRECTORY, on x64/arm64.
@@ -41,7 +41,7 @@ export async function pinNodeDirectoryForMode(dirPath, options = {}) {
41
41
  const handle = await fs.open(dirPath, constants.O_RDONLY | flags).catch(async (error) => {
42
42
  if (error.code !== "EACCES")
43
43
  throw error;
44
- const route = searchOnlyFlags();
44
+ const route = nodeDirectorySearchOnlyFlags();
45
45
  if (!route)
46
46
  throw error;
47
47
  proc = route.proc;
@@ -91,3 +91,101 @@ export async function pinNodeDirectoryForMode(dirPath, options = {}) {
91
91
  throw error;
92
92
  }
93
93
  }
94
+ /** Synchronous counterpart with the same no-follow, identity, and owner checks. */
95
+ export function pinNodeDirectoryForModeSync(dirPath, options = {}) {
96
+ const expected = inspectDirectoryIdentitySync(dirPath, options.expectedIdentity);
97
+ const assertOwner = (stat) => {
98
+ if (options.ownerUid !== undefined && stat.uid !== BigInt(options.ownerUid)) {
99
+ throw new FsSafeError("not-owned", "directory mode target must retain its expected owner");
100
+ }
101
+ };
102
+ assertOwner(expected);
103
+ if (process.platform === "win32") {
104
+ return {
105
+ apply: (_mode, check) => {
106
+ check?.();
107
+ assertOwner(inspectDirectoryIdentitySync(dirPath, expected));
108
+ check?.();
109
+ },
110
+ close: () => undefined,
111
+ };
112
+ }
113
+ if ([constants.O_DIRECTORY, constants.O_NOFOLLOW, constants.O_NONBLOCK, constants.O_RDONLY]
114
+ .some((flag) => typeof flag !== "number")) {
115
+ throw new FsSafeError("helper-unavailable", "no-follow directory mode descriptors are unavailable");
116
+ }
117
+ const flags = constants.O_DIRECTORY | constants.O_NOFOLLOW | constants.O_NONBLOCK;
118
+ let fd;
119
+ let proc = false;
120
+ try {
121
+ fd = fsSync.openSync(dirPath, constants.O_RDONLY | flags);
122
+ }
123
+ catch (error) {
124
+ if (error.code !== "EACCES")
125
+ throw error;
126
+ const route = nodeDirectorySearchOnlyFlags();
127
+ if (!route)
128
+ throw error;
129
+ proc = route.proc;
130
+ fd = fsSync.openSync(dirPath, route.flags | flags);
131
+ }
132
+ let closed = false;
133
+ const inspect = () => {
134
+ if (closed)
135
+ throw new FsSafeError("path-mismatch", "directory mode descriptor is closed");
136
+ const stat = inspectFileIdentitySync(() => fsSync.fstatSync(fd, { bigint: true }), expected);
137
+ assertOwnedDirectory(expected, stat);
138
+ assertOwner(stat);
139
+ inspectDirectoryIdentitySync(dirPath, expected);
140
+ return Number(stat.mode & 4095n);
141
+ };
142
+ const procPath = `/proc/self/fd/${fd}`;
143
+ const assertProcAuthority = () => {
144
+ if (fsSync.statfsSync("/proc/self/fd", { bigint: true }).type !== 0x9fa0n) {
145
+ throw new FsSafeError("path-mismatch", "directory mode requires a trusted procfs fd namespace");
146
+ }
147
+ const opened = inspectFileIdentitySync(() => fsSync.fstatSync(fd, { bigint: true }), expected);
148
+ const followed = inspectFileIdentitySync(() => fsSync.statSync(procPath, { bigint: true }), expected);
149
+ assertOwnedDirectory(opened, followed);
150
+ assertOwner(opened);
151
+ assertOwner(followed);
152
+ };
153
+ try {
154
+ inspect();
155
+ return {
156
+ apply: (mode, check) => {
157
+ mode &= 0o7777;
158
+ check?.();
159
+ const currentMode = inspect();
160
+ check?.();
161
+ if (currentMode !== mode) {
162
+ if (proc)
163
+ assertProcAuthority();
164
+ check?.();
165
+ inspect();
166
+ if (proc)
167
+ fsSync.chmodSync(procPath, mode);
168
+ else
169
+ fsSync.fchmodSync(fd, mode);
170
+ if (proc)
171
+ assertProcAuthority();
172
+ }
173
+ const finalMode = inspect();
174
+ check?.();
175
+ if (finalMode !== mode) {
176
+ throw new FsSafeError("path-mismatch", "directory final mode could not be verified");
177
+ }
178
+ },
179
+ close: () => {
180
+ if (closed)
181
+ return;
182
+ closed = true;
183
+ fsSync.closeSync(fd);
184
+ },
185
+ };
186
+ }
187
+ catch (error) {
188
+ fsSync.closeSync(fd);
189
+ throw error;
190
+ }
191
+ }
@@ -0,0 +1,2 @@
1
+ export declare function resolveEffectiveUid(): number;
2
+ //# sourceMappingURL=effective-uid.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"effective-uid.d.ts","sourceRoot":"","sources":["../src/effective-uid.ts"],"names":[],"mappings":"AAUA,wBAAgB,mBAAmB,IAAI,MAAM,CAe5C"}
@@ -0,0 +1,25 @@
1
+ function isValidUid(value) {
2
+ return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
3
+ }
4
+ function unavailable(cause) {
5
+ return new Error("Effective user identity is unavailable.", {
6
+ ...(cause === undefined ? {} : { cause }),
7
+ });
8
+ }
9
+ export function resolveEffectiveUid() {
10
+ const provider = process.geteuid;
11
+ if (typeof provider !== "function") {
12
+ throw unavailable();
13
+ }
14
+ let uid;
15
+ try {
16
+ uid = provider.call(process);
17
+ }
18
+ catch (cause) {
19
+ throw unavailable(cause);
20
+ }
21
+ if (!isValidUid(uid)) {
22
+ throw unavailable();
23
+ }
24
+ return uid;
25
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"file-handle-transfer.d.ts","sourceRoot":"","sources":["../src/file-handle-transfer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAWnD,MAAM,MAAM,qBAAqB,GAAG;IAClC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI,CAAC;IACtC,oBAAoB,CAAC,EAAE,MAAM,IAAI,CAAC;CACnC,CAAC;AAEF,uFAAuF;AACvF,wBAAsB,cAAc,CAClC,MAAM,EAAE,UAAU,EAClB,MAAM,EAAE,UAAU,EAClB,OAAO,GAAE,qBAA0B,GAClC,OAAO,CAAC,MAAM,CAAC,CA4BjB;AAGD,wBAAsB,kBAAkB,CACtC,MAAM,EAAE,UAAU,EAClB,MAAM,EAAE,UAAU,GAAG,MAAM,EAC3B,OAAO,EAAE,qBAAqB,GAAG;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,cAAc,CAAC,EAAE,MAAM,CAAA;CAAE,GAC7E,OAAO,CAAC,MAAM,CAAC,CAyBjB"}
1
+ {"version":3,"file":"file-handle-transfer.d.ts","sourceRoot":"","sources":["../src/file-handle-transfer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAWnD,MAAM,MAAM,qBAAqB,GAAG;IAClC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI,CAAC;IACtC,oBAAoB,CAAC,EAAE,MAAM,IAAI,CAAC;CACnC,CAAC;AAkDF,uFAAuF;AACvF,wBAAsB,cAAc,CAClC,MAAM,EAAE,UAAU,EAClB,MAAM,EAAE,UAAU,EAClB,OAAO,GAAE,qBAA0B,GAClC,OAAO,CAAC,MAAM,CAAC,CA0CjB;AAGD,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,UAAU,EAClB,MAAM,EAAE,UAAU,GAAG,MAAM,EAC3B,OAAO,EAAE,qBAAqB,GAAG;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,cAAc,CAAC,EAAE,MAAM,CAAA;CAAE,GAC7E,OAAO,CAAC,MAAM,CAAC,CAwBjB"}
@@ -3,61 +3,132 @@ import { FsSafeError } from "./errors.js";
3
3
  import { assertSynchronousCallbackResult, composeMutationAssertions, rethrowMutationAuthorityError, } from "./mutation-authority.js";
4
4
  import { writeAllToFile } from "./write-file-handle.js";
5
5
  import { inspectFileIdentity } from "./strict-file-identity.js";
6
+ const NO_CALLBACK_ARGUMENTS = Object.freeze([]);
7
+ function defineReceiverValue(receiver, key, value) {
8
+ Object.defineProperty(receiver, key, {
9
+ value, enumerable: true, writable: true, configurable: true,
10
+ });
11
+ }
12
+ function copyTransferReceiver(options, maxBytes, sizeHint, signal, onChunk, assertBeforeMutation) {
13
+ const receiver = {};
14
+ const spreadSource = Object(options);
15
+ // Preserve the former late object-spread boundary, including proxy traps and
16
+ // unrelated getter failures. Authority-bearing values stay on their early
17
+ // snapshots, while descriptors still run in original own-key order.
18
+ for (const key of Reflect.ownKeys(spreadSource)) {
19
+ if (!Object.getOwnPropertyDescriptor(spreadSource, key)?.enumerable)
20
+ continue;
21
+ let value;
22
+ switch (key) {
23
+ case "maxBytes":
24
+ value = maxBytes;
25
+ break;
26
+ case "signal":
27
+ value = signal;
28
+ break;
29
+ case "onChunk":
30
+ value = onChunk;
31
+ break;
32
+ case "assertBeforeMutation":
33
+ value = assertBeforeMutation;
34
+ break;
35
+ default: value = Reflect.get(spreadSource, key);
36
+ }
37
+ // Create a data property so an enumerable __proto__ key cannot alter the
38
+ // ordinary receiver prototype, matching object spread.
39
+ defineReceiverValue(receiver, key, value);
40
+ }
41
+ // Match the explicit fields that followed the former spread. Updating an
42
+ // existing key retains its original position; absent keys append in order.
43
+ defineReceiverValue(receiver, "maxBytes", maxBytes);
44
+ defineReceiverValue(receiver, "sizeHint", sizeHint);
45
+ defineReceiverValue(receiver, "targetPosition", 0);
46
+ defineReceiverValue(receiver, "assertBeforeMutation", assertBeforeMutation);
47
+ return receiver;
48
+ }
6
49
  /** Copies caller-owned regular files from offset zero without moving either cursor. */
7
50
  export async function copyFileHandle(source, target, options = {}) {
8
- options.signal?.throwIfAborted();
51
+ // Snapshot caller-owned options before the first await. This preserves
52
+ // inherited/non-enumerable callbacks and prevents accessor mutation from
53
+ // replacing cancellation or mutation authority mid-transfer.
54
+ const signal = options.signal;
55
+ signal?.throwIfAborted();
9
56
  const maxBytes = normalizeMaxBytes(options.maxBytes);
57
+ const onChunk = options.onChunk;
58
+ const assertBeforeMutation = composeMutationAssertions(undefined, options.assertBeforeMutation);
10
59
  const sourceStat = await inspectFileIdentity(() => {
11
- options.signal?.throwIfAborted();
60
+ signal?.throwIfAborted();
12
61
  return source.stat({ bigint: true });
13
62
  });
14
- options.signal?.throwIfAborted();
63
+ signal?.throwIfAborted();
15
64
  if (!sourceStat.isFile())
16
65
  throw new FsSafeError("not-file", "copy source handle must be a regular file");
17
66
  if (maxBytes !== undefined && Number.isFinite(maxBytes) && sourceStat.size > BigInt(maxBytes)) {
18
67
  throw new FsSafeError("too-large", `file exceeds limit of ${maxBytes} bytes`);
19
68
  }
20
69
  const targetStat = await inspectFileIdentity(() => {
21
- options.signal?.throwIfAborted();
70
+ signal?.throwIfAborted();
22
71
  return target.stat({ bigint: true });
23
72
  });
24
- options.signal?.throwIfAborted();
73
+ signal?.throwIfAborted();
25
74
  if (!targetStat.isFile())
26
75
  throw new FsSafeError("not-file", "copy target handle must be a regular file");
27
76
  if (sourceStat.dev === targetStat.dev && sourceStat.ino === targetStat.ino) {
28
77
  throw new FsSafeError("path-alias", "copy source and target handles refer to the same file");
29
78
  }
30
- return await transferFileHandle(source, target, {
31
- ...options,
32
- maxBytes,
33
- sizeHint: Number(sourceStat.size),
34
- targetPosition: 0,
35
- assertBeforeMutation: composeMutationAssertions(undefined, options.assertBeforeMutation),
36
- }).catch(rethrowMutationAuthorityError);
79
+ const sizeHint = Number(sourceStat.size);
80
+ const callbackThis = copyTransferReceiver(options, maxBytes, sizeHint, signal, onChunk, assertBeforeMutation);
81
+ const beforeWrite = () => {
82
+ signal?.throwIfAborted();
83
+ assertBeforeMutation?.();
84
+ };
85
+ return await transferFileHandleCore(source, target, sizeHint, maxBytes, 0, signal, onChunk, beforeWrite, callbackThis).catch(rethrowMutationAuthorityError);
37
86
  }
38
87
  // Root.copyIn supplies already-admitted identities and retains its owned target cursor.
39
- export async function transferFileHandle(source, target, options) {
40
- const maxBytes = options.maxBytes ?? Infinity;
41
- const buffer = Buffer.allocUnsafe(Math.min(512 * 1024, Math.max(64 * 1024, options.sizeHint), maxBytes + 1));
88
+ export function transferFileHandle(source, target, options) {
89
+ try {
90
+ const maxBytes = options.maxBytes;
91
+ const sizeHint = options.sizeHint;
92
+ const targetPosition = options.targetPosition;
93
+ const signal = options.signal;
94
+ const onChunk = options.onChunk;
95
+ const assertBeforeMutation = options.assertBeforeMutation;
96
+ const beforeWrite = signal === undefined && assertBeforeMutation === undefined
97
+ ? undefined
98
+ : () => {
99
+ signal?.throwIfAborted();
100
+ if (assertBeforeMutation) {
101
+ Reflect.apply(assertBeforeMutation, options, NO_CALLBACK_ARGUMENTS);
102
+ }
103
+ };
104
+ return transferFileHandleCore(source, target, sizeHint, maxBytes, targetPosition, signal, onChunk, beforeWrite, options);
105
+ }
106
+ catch (error) {
107
+ // Keep the former async-function contract for synchronous option getters.
108
+ return Promise.reject(error);
109
+ }
110
+ }
111
+ async function transferFileHandleCore(source, target, sizeHint, maxBytes, targetPosition, signal, onChunk, beforeWrite, callbackThis) {
112
+ const limit = maxBytes ?? Infinity;
113
+ const buffer = Buffer.allocUnsafe(Math.min(512 * 1024, Math.max(64 * 1024, sizeHint), limit + 1));
42
114
  let position = 0;
43
115
  while (true) {
44
- options.signal?.throwIfAborted();
45
- const { bytesRead } = await source.read(buffer, 0, Math.min(buffer.length, maxBytes - position + 1), position);
46
- options.signal?.throwIfAborted();
47
- if (bytesRead > maxBytes - position) {
48
- throw new FsSafeError("too-large", `file exceeds limit of ${maxBytes} bytes`);
116
+ signal?.throwIfAborted();
117
+ const { bytesRead } = await source.read(buffer, 0, Math.min(buffer.length, limit - position + 1), position);
118
+ signal?.throwIfAborted();
119
+ if (bytesRead > limit - position) {
120
+ throw new FsSafeError("too-large", `file exceeds limit of ${limit} bytes`);
49
121
  }
50
122
  if (bytesRead === 0)
51
123
  return position;
52
124
  const chunk = buffer.subarray(0, bytesRead);
53
- if (options.onChunk)
54
- assertSynchronousCallbackResult(options.onChunk(chunk), "onChunk");
125
+ if (onChunk) {
126
+ const returned = Function.prototype.call.call(onChunk, callbackThis, chunk);
127
+ assertSynchronousCallbackResult(returned, "onChunk");
128
+ }
55
129
  await writeAllToFile(target, chunk, {
56
- position: options.targetPosition === undefined ? undefined : options.targetPosition + position,
57
- assertBeforeMutation: () => {
58
- options.signal?.throwIfAborted();
59
- options.assertBeforeMutation?.();
60
- },
130
+ position: targetPosition === undefined ? undefined : targetPosition + position,
131
+ assertBeforeMutation: beforeWrite,
61
132
  });
62
133
  position += bytesRead;
63
134
  }
@@ -1 +1 @@
1
- {"version":3,"file":"file-hash.d.ts","sourceRoot":"","sources":["../src/file-hash.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAInD,OAAO,EAAoB,KAAK,aAAa,EAAE,MAAM,aAAa,CAAC;AAInE,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,UAAU,CAAC;AAClD,MAAM,MAAM,mBAAmB,GAAG,MAAM,GAAG,MAAM,CAAC;AAElD,MAAM,MAAM,iBAAiB,GAAG;IAC9B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,wBAAsB,cAAc,CAClC,MAAM,EAAE,UAAU,EAClB,MAAM,GAAE,aAAa,GAAG,SAA8B,EACtD,EAAE,QAAmB,EAAE,MAAM,EAAE,GAAE,iBAAsB,GACtD,OAAO,CAAC,gBAAgB,CAAC,CAoD3B;AAyDD,wBAAsB,UAAU,CAC9B,KAAK,EAAE,eAAe,EACtB,OAAO,GAAE,iBAAsB,GAC9B,OAAO,CAAC,gBAAgB,CAAC,CAM3B;AAoDD,8FAA8F;AAC9F,wBAAgB,cAAc,CAC5B,KAAK,EAAE,mBAAmB,EAC1B,OAAO,GAAE,iBAAsB,GAC9B,gBAAgB,CAMlB"}
1
+ {"version":3,"file":"file-hash.d.ts","sourceRoot":"","sources":["../src/file-hash.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAInD,OAAO,EAAoB,KAAK,aAAa,EAAE,MAAM,aAAa,CAAC;AAKnE,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,UAAU,CAAC;AAClD,MAAM,MAAM,mBAAmB,GAAG,MAAM,GAAG,MAAM,CAAC;AAElD,MAAM,MAAM,iBAAiB,GAAG;IAC9B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,wBAAsB,cAAc,CAClC,MAAM,EAAE,UAAU,EAClB,MAAM,GAAE,aAAa,GAAG,SAA8B,EACtD,EAAE,QAAmB,EAAE,MAAM,EAAE,GAAE,iBAAsB,GACtD,OAAO,CAAC,gBAAgB,CAAC,CAwD3B;AA0DD,wBAAsB,UAAU,CAC9B,KAAK,EAAE,eAAe,EACtB,OAAO,GAAE,iBAAsB,GAC9B,OAAO,CAAC,gBAAgB,CAAC,CAM3B;AAwDD,8FAA8F;AAC9F,wBAAgB,cAAc,CAC5B,KAAK,EAAE,mBAAmB,EAC1B,OAAO,GAAE,iBAAsB,GAC9B,gBAAgB,CAMlB"}