@openclaw/fs-safe 0.9.0 → 0.11.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 (326) hide show
  1. package/CHANGELOG.md +134 -0
  2. package/LICENSE +1 -0
  3. package/README.md +52 -4
  4. package/dist/absolute-path.d.ts.map +1 -1
  5. package/dist/absolute-path.js +3 -2
  6. package/dist/advanced.d.ts +5 -0
  7. package/dist/advanced.d.ts.map +1 -1
  8. package/dist/advanced.js +5 -0
  9. package/dist/archive-crc32.d.ts.map +1 -1
  10. package/dist/archive-crc32.js +6 -1
  11. package/dist/archive-deadline.d.ts.map +1 -1
  12. package/dist/archive-deadline.js +3 -4
  13. package/dist/archive-durability.d.ts +6 -6
  14. package/dist/archive-durability.d.ts.map +1 -1
  15. package/dist/archive-durability.js +1 -1
  16. package/dist/archive-entry.d.ts.map +1 -1
  17. package/dist/archive-entry.js +4 -5
  18. package/dist/archive-gzip-tail.d.ts +3 -0
  19. package/dist/archive-gzip-tail.d.ts.map +1 -1
  20. package/dist/archive-gzip-tail.js +23 -3
  21. package/dist/archive-input.d.ts.map +1 -1
  22. package/dist/archive-input.js +4 -2
  23. package/dist/archive-merge.d.ts +5 -1
  24. package/dist/archive-merge.d.ts.map +1 -1
  25. package/dist/archive-merge.js +16 -13
  26. package/dist/archive-native.d.ts.map +1 -1
  27. package/dist/archive-native.js +7 -6
  28. package/dist/archive-parser.wasm +0 -0
  29. package/dist/archive-read.d.ts.map +1 -1
  30. package/dist/archive-read.js +83 -74
  31. package/dist/archive-staging.d.ts +6 -3
  32. package/dist/archive-staging.d.ts.map +1 -1
  33. package/dist/archive-staging.js +42 -22
  34. package/dist/archive-tar-stream.d.ts +11 -4
  35. package/dist/archive-tar-stream.d.ts.map +1 -1
  36. package/dist/archive-tar-stream.js +23 -10
  37. package/dist/archive-tar-wasm.d.ts.map +1 -1
  38. package/dist/archive-tar-wasm.js +18 -14
  39. package/dist/archive-zip-admission.d.ts +1 -1
  40. package/dist/archive-zip-admission.d.ts.map +1 -1
  41. package/dist/archive-zip-admission.js +48 -12
  42. package/dist/archive-zip-loader.d.ts +6 -0
  43. package/dist/archive-zip-loader.d.ts.map +1 -0
  44. package/dist/archive-zip-loader.js +38 -0
  45. package/dist/archive-zip-names.d.ts.map +1 -1
  46. package/dist/archive-zip-names.js +13 -8
  47. package/dist/archive-zip-preflight.d.ts +2 -3
  48. package/dist/archive-zip-preflight.d.ts.map +1 -1
  49. package/dist/archive-zip-preflight.js +2 -34
  50. package/dist/archive.d.ts.map +1 -1
  51. package/dist/archive.js +12 -10
  52. package/dist/bounded-read.d.ts +12 -0
  53. package/dist/bounded-read.d.ts.map +1 -1
  54. package/dist/bounded-read.js +82 -45
  55. package/dist/clone-metadata.d.ts +19 -0
  56. package/dist/clone-metadata.d.ts.map +1 -0
  57. package/dist/clone-metadata.js +32 -0
  58. package/dist/copy-file-input.d.ts +22 -0
  59. package/dist/copy-file-input.d.ts.map +1 -0
  60. package/dist/copy-file-input.js +69 -0
  61. package/dist/copy-policy.d.ts +3 -0
  62. package/dist/copy-policy.d.ts.map +1 -0
  63. package/dist/copy-policy.js +8 -0
  64. package/dist/copy-publication.d.ts +10 -1
  65. package/dist/copy-publication.d.ts.map +1 -1
  66. package/dist/copy-publication.js +27 -0
  67. package/dist/copy-tree-portable.d.ts +9 -0
  68. package/dist/copy-tree-portable.d.ts.map +1 -0
  69. package/dist/copy-tree-portable.js +222 -0
  70. package/dist/copy.d.ts +16 -0
  71. package/dist/copy.d.ts.map +1 -0
  72. package/dist/copy.js +125 -0
  73. package/dist/directory-durability.d.ts.map +1 -1
  74. package/dist/directory-durability.js +5 -4
  75. package/dist/directory-guard.d.ts +11 -1
  76. package/dist/directory-guard.d.ts.map +1 -1
  77. package/dist/directory-guard.js +53 -11
  78. package/dist/durability.d.ts +1 -1
  79. package/dist/durability.d.ts.map +1 -1
  80. package/dist/durability.js +1 -1
  81. package/dist/error-detail.d.ts.map +1 -1
  82. package/dist/error-detail.js +4 -1
  83. package/dist/file-handle-transfer.d.ts +14 -0
  84. package/dist/file-handle-transfer.d.ts.map +1 -0
  85. package/dist/file-handle-transfer.js +64 -0
  86. package/dist/file-hash.d.ts +9 -2
  87. package/dist/file-hash.d.ts.map +1 -1
  88. package/dist/file-hash.js +135 -39
  89. package/dist/file-lock-sync.d.ts.map +1 -1
  90. package/dist/file-lock-sync.js +8 -4
  91. package/dist/file-store-boundary.d.ts.map +1 -1
  92. package/dist/file-store-boundary.js +7 -5
  93. package/dist/file-store-path.d.ts +3 -0
  94. package/dist/file-store-path.d.ts.map +1 -0
  95. package/dist/file-store-path.js +27 -0
  96. package/dist/file-store-prune.d.ts.map +1 -1
  97. package/dist/file-store-prune.js +6 -4
  98. package/dist/file-store-sync-write.d.ts.map +1 -1
  99. package/dist/file-store-sync-write.js +56 -44
  100. package/dist/file-store.d.ts.map +1 -1
  101. package/dist/file-store.js +2 -18
  102. package/dist/filename.d.ts.map +1 -1
  103. package/dist/filename.js +4 -13
  104. package/dist/guarded-mkdir.d.ts +1 -0
  105. package/dist/guarded-mkdir.d.ts.map +1 -1
  106. package/dist/guarded-mkdir.js +4 -2
  107. package/dist/guarded-mutation.d.ts +2 -0
  108. package/dist/guarded-mutation.d.ts.map +1 -1
  109. package/dist/guarded-mutation.js +8 -4
  110. package/dist/guest-dispatch-python.d.ts +2 -0
  111. package/dist/guest-dispatch-python.d.ts.map +1 -0
  112. package/dist/guest-dispatch-python.js +117 -0
  113. package/dist/guest-native-python.d.ts +4 -0
  114. package/dist/guest-native-python.d.ts.map +1 -0
  115. package/dist/guest-native-python.js +135 -0
  116. package/dist/guest.d.ts +9 -0
  117. package/dist/guest.d.ts.map +1 -0
  118. package/dist/guest.js +413 -0
  119. package/dist/home-dir.d.ts.map +1 -1
  120. package/dist/home-dir.js +9 -7
  121. package/dist/index.d.ts +1 -1
  122. package/dist/index.d.ts.map +1 -1
  123. package/dist/install-path.d.ts.map +1 -1
  124. package/dist/install-path.js +5 -8
  125. package/dist/json-durable-queue-directory.js +3 -3
  126. package/dist/json-durable-queue.d.ts.map +1 -1
  127. package/dist/json-durable-queue.js +19 -18
  128. package/dist/json.d.ts.map +1 -1
  129. package/dist/json.js +2 -1
  130. package/dist/local-roots.d.ts.map +1 -1
  131. package/dist/local-roots.js +24 -25
  132. package/dist/move-path-stage.d.ts +8 -0
  133. package/dist/move-path-stage.d.ts.map +1 -0
  134. package/dist/move-path-stage.js +56 -0
  135. package/dist/move-path.d.ts.map +1 -1
  136. package/dist/move-path.js +30 -34
  137. package/dist/mutation-authority.d.ts +9 -0
  138. package/dist/mutation-authority.d.ts.map +1 -0
  139. package/dist/mutation-authority.js +36 -0
  140. package/dist/native-binding.d.ts +29 -1
  141. package/dist/native-binding.d.ts.map +1 -1
  142. package/dist/native-operations.d.ts +1 -1
  143. package/dist/native-operations.d.ts.map +1 -1
  144. package/dist/native-operations.js +11 -5
  145. package/dist/native-pinned-write-windows.d.ts.map +1 -1
  146. package/dist/native-pinned-write-windows.js +19 -7
  147. package/dist/native-pinned-write.d.ts.map +1 -1
  148. package/dist/native-pinned-write.js +3 -1
  149. package/dist/native-staged-file.d.ts +3 -3
  150. package/dist/native-staged-file.d.ts.map +1 -1
  151. package/dist/native-staged-file.js +35 -8
  152. package/dist/opened-realpath.d.ts.map +1 -1
  153. package/dist/opened-realpath.js +5 -4
  154. package/dist/output.d.ts +2 -0
  155. package/dist/output.d.ts.map +1 -1
  156. package/dist/output.js +2 -0
  157. package/dist/overwrite-file-handle.d.ts +8 -0
  158. package/dist/overwrite-file-handle.d.ts.map +1 -0
  159. package/dist/overwrite-file-handle.js +42 -0
  160. package/dist/path-case.d.ts +7 -0
  161. package/dist/path-case.d.ts.map +1 -0
  162. package/dist/path-case.js +136 -0
  163. package/dist/path.d.ts.map +1 -1
  164. package/dist/path.js +5 -2
  165. package/dist/permissions-windows.d.ts +1 -1
  166. package/dist/permissions-windows.d.ts.map +1 -1
  167. package/dist/permissions-windows.js +85 -53
  168. package/dist/pinned-open.d.ts.map +1 -1
  169. package/dist/pinned-open.js +3 -1
  170. package/dist/pinned-operation.js +1 -1
  171. package/dist/pinned-write.d.ts +7 -3
  172. package/dist/pinned-write.d.ts.map +1 -1
  173. package/dist/pinned-write.js +51 -30
  174. package/dist/positional-read.d.ts +9 -0
  175. package/dist/positional-read.d.ts.map +1 -0
  176. package/dist/positional-read.js +36 -0
  177. package/dist/private-temp-workspace.d.ts.map +1 -1
  178. package/dist/private-temp-workspace.js +6 -4
  179. package/dist/publish-copy-stage.d.ts +13 -0
  180. package/dist/publish-copy-stage.d.ts.map +1 -0
  181. package/dist/publish-copy-stage.js +47 -0
  182. package/dist/publish-file.d.ts.map +1 -1
  183. package/dist/publish-file.js +6 -5
  184. package/dist/realpath.d.ts +4 -0
  185. package/dist/realpath.d.ts.map +1 -0
  186. package/dist/realpath.js +43 -0
  187. package/dist/recursive-mkdir-path.d.ts +3 -0
  188. package/dist/recursive-mkdir-path.d.ts.map +1 -0
  189. package/dist/recursive-mkdir-path.js +8 -0
  190. package/dist/replace-directory.d.ts.map +1 -1
  191. package/dist/replace-directory.js +2 -1
  192. package/dist/replace-file-copy-fallback.d.ts.map +1 -1
  193. package/dist/replace-file-copy-fallback.js +23 -31
  194. package/dist/replace-file-copy-source.d.ts.map +1 -1
  195. package/dist/replace-file-copy-source.js +7 -12
  196. package/dist/replace-file-mode.d.ts +3 -0
  197. package/dist/replace-file-mode.d.ts.map +1 -0
  198. package/dist/replace-file-mode.js +10 -0
  199. package/dist/replace-file-temp-owner.d.ts.map +1 -1
  200. package/dist/replace-file-temp-owner.js +6 -2
  201. package/dist/replace-file.d.ts +1 -0
  202. package/dist/replace-file.d.ts.map +1 -1
  203. package/dist/replace-file.js +14 -8
  204. package/dist/root-context.d.ts +3 -0
  205. package/dist/root-context.d.ts.map +1 -1
  206. package/dist/root-context.js +53 -28
  207. package/dist/root-create-input.d.ts +10 -0
  208. package/dist/root-create-input.d.ts.map +1 -0
  209. package/dist/root-create-input.js +80 -0
  210. package/dist/root-directory-list.d.ts +26 -0
  211. package/dist/root-directory-list.d.ts.map +1 -0
  212. package/dist/root-directory-list.js +219 -0
  213. package/dist/root-entries.d.ts +11 -0
  214. package/dist/root-entries.d.ts.map +1 -0
  215. package/dist/root-entries.js +61 -0
  216. package/dist/root-errors.d.ts +6 -5
  217. package/dist/root-errors.d.ts.map +1 -1
  218. package/dist/root-errors.js +16 -12
  219. package/dist/root-file.d.ts +2 -0
  220. package/dist/root-file.d.ts.map +1 -1
  221. package/dist/root-file.js +12 -4
  222. package/dist/root-impl.d.ts +17 -51
  223. package/dist/root-impl.d.ts.map +1 -1
  224. package/dist/root-impl.js +358 -234
  225. package/dist/root-options.d.ts +77 -0
  226. package/dist/root-options.d.ts.map +1 -0
  227. package/dist/root-options.js +18 -0
  228. package/dist/root-path-existing.d.ts +5 -0
  229. package/dist/root-path-existing.d.ts.map +1 -1
  230. package/dist/root-path-existing.js +59 -3
  231. package/dist/root-path-symlink.d.ts.map +1 -1
  232. package/dist/root-path-symlink.js +3 -2
  233. package/dist/root-path.d.ts +1 -0
  234. package/dist/root-path.d.ts.map +1 -1
  235. package/dist/root-path.js +74 -162
  236. package/dist/root-paths.d.ts.map +1 -1
  237. package/dist/root-paths.js +13 -9
  238. package/dist/root-remove.d.ts +5 -0
  239. package/dist/root-remove.d.ts.map +1 -0
  240. package/dist/root-remove.js +286 -0
  241. package/dist/root-symlink-policy.d.ts +14 -0
  242. package/dist/root-symlink-policy.d.ts.map +1 -0
  243. package/dist/root-symlink-policy.js +34 -0
  244. package/dist/root-walk.d.ts +5 -4
  245. package/dist/root-walk.d.ts.map +1 -1
  246. package/dist/root-walk.js +99 -50
  247. package/dist/root-write-mode.d.ts.map +1 -1
  248. package/dist/root-write-mode.js +3 -5
  249. package/dist/root.d.ts +4 -1
  250. package/dist/root.d.ts.map +1 -1
  251. package/dist/secret-file.d.ts.map +1 -1
  252. package/dist/secret-file.js +2 -1
  253. package/dist/secret-read-async.d.ts.map +1 -1
  254. package/dist/secret-read-async.js +2 -1
  255. package/dist/secure-file.d.ts.map +1 -1
  256. package/dist/secure-file.js +25 -8
  257. package/dist/secure-temp-dir.d.ts.map +1 -1
  258. package/dist/secure-temp-dir.js +2 -1
  259. package/dist/sibling-staged-file.d.ts +1 -0
  260. package/dist/sibling-staged-file.d.ts.map +1 -1
  261. package/dist/sibling-staged-file.js +42 -8
  262. package/dist/sibling-temp.d.ts +2 -0
  263. package/dist/sibling-temp.d.ts.map +1 -1
  264. package/dist/sibling-temp.js +6 -4
  265. package/dist/sidecar-lock-acquire.d.ts.map +1 -1
  266. package/dist/sidecar-lock-acquire.js +17 -5
  267. package/dist/sidecar-lock-policy.d.ts +2 -0
  268. package/dist/sidecar-lock-policy.d.ts.map +1 -1
  269. package/dist/sidecar-lock-policy.js +20 -1
  270. package/dist/staged-directory.d.ts.map +1 -1
  271. package/dist/staged-directory.js +4 -3
  272. package/dist/temp-cleanup.d.ts.map +1 -1
  273. package/dist/temp-cleanup.js +3 -2
  274. package/dist/temp-target.d.ts +14 -12
  275. package/dist/temp-target.d.ts.map +1 -1
  276. package/dist/temp-target.js +12 -6
  277. package/dist/timing.d.ts +1 -0
  278. package/dist/timing.d.ts.map +1 -1
  279. package/dist/timing.js +25 -6
  280. package/dist/trash.d.ts.map +1 -1
  281. package/dist/trash.js +9 -7
  282. package/dist/unicode-path.d.ts +3 -0
  283. package/dist/unicode-path.d.ts.map +1 -0
  284. package/dist/unicode-path.js +13 -0
  285. package/dist/walk.d.ts.map +1 -1
  286. package/dist/walk.js +9 -28
  287. package/dist/windows-owner.d.ts +9 -12
  288. package/dist/windows-owner.d.ts.map +1 -1
  289. package/dist/windows-owner.js +24 -58
  290. package/dist/write-file-handle.d.ts +7 -0
  291. package/dist/write-file-handle.d.ts.map +1 -0
  292. package/dist/write-file-handle.js +26 -0
  293. package/docs/advanced.md +22 -4
  294. package/docs/archive.md +44 -9
  295. package/docs/atomic.md +24 -1
  296. package/docs/config.md +1 -0
  297. package/docs/contributing.md +36 -1
  298. package/docs/copy.md +155 -0
  299. package/docs/directory-identity.md +85 -0
  300. package/docs/durability.md +53 -3
  301. package/docs/entries.md +109 -0
  302. package/docs/errors.md +4 -4
  303. package/docs/file-store.md +15 -0
  304. package/docs/guest.md +141 -0
  305. package/docs/in-place-write.md +81 -0
  306. package/docs/index.md +2 -0
  307. package/docs/install.md +31 -0
  308. package/docs/local-roots.md +8 -1
  309. package/docs/native-helper.md +10 -3
  310. package/docs/native.md +18 -1
  311. package/docs/output.md +32 -6
  312. package/docs/path-case.md +64 -0
  313. package/docs/path-scope.md +1 -1
  314. package/docs/permissions.md +29 -10
  315. package/docs/positional-read.md +63 -0
  316. package/docs/public-api.md +31 -2
  317. package/docs/root.md +196 -6
  318. package/docs/secure-file.md +2 -0
  319. package/docs/security-model.md +14 -0
  320. package/docs/sidecar-lock.md +12 -3
  321. package/docs/temp.md +35 -6
  322. package/docs/timing.md +2 -0
  323. package/docs/types.md +19 -12
  324. package/docs/walk.md +54 -5
  325. package/docs/writing.md +173 -5
  326. package/package.json +19 -8
package/dist/copy.js ADDED
@@ -0,0 +1,125 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { assertAbsolutePathInput } from "./absolute-path.js";
4
+ import { resolveCopyCloneMode } from "./copy-policy.js";
5
+ import { copyOwnedTree } from "./copy-tree-portable.js";
6
+ import { FsSafeError } from "./errors.js";
7
+ import { getNativeBinding, requireNativeBinding } from "./native.js";
8
+ import { assertStagedDirectoryCurrent, openStagedDirectory } from "./staged-directory.js";
9
+ export { readCloneFileMetadata } from "./clone-metadata.js";
10
+ /** Inspect an existing real directory without creating probe files. */
11
+ export function probeTreeClone(parentPath) {
12
+ const native = getNativeBinding();
13
+ if (!native)
14
+ return undefined;
15
+ const parent = openStagedDirectory(assertAbsolutePathInput(parentPath));
16
+ try {
17
+ return native.probeTreeClone(parent.fd) ?? undefined;
18
+ }
19
+ finally {
20
+ fs.closeSync(parent.fd);
21
+ }
22
+ }
23
+ /** Create an empty clone source: a Btrfs subvolume or an ordinary supported directory. */
24
+ export async function createCloneSource(destination, options = {}) {
25
+ await materializeTree(undefined, destination, options, "always");
26
+ }
27
+ /** Copy an immutable, caller-owned tree, preferring native cloning unless configured otherwise. */
28
+ export async function copyTree(source, destination, options = {}) {
29
+ options.signal?.throwIfAborted();
30
+ const policy = resolveCopyCloneMode(options.clone, "auto");
31
+ await materializeTree(source, destination, options, policy);
32
+ }
33
+ function cloneUnavailable(error) {
34
+ if (error instanceof FsSafeError && error.code === "unsupported-platform")
35
+ return true;
36
+ if (!(error instanceof Error) || !("code" in error))
37
+ return false;
38
+ // ENOTSUP also describes unsupported contents (for example named streams).
39
+ // Only capability failures may select a byte copy, never lossy source errors.
40
+ return error.code === "CLONE_UNAVAILABLE" || error.code === "EXDEV" || error.code === "ENOSYS";
41
+ }
42
+ async function materializeTree(source, destination, options, policy) {
43
+ options.signal?.throwIfAborted();
44
+ const concurrency = options.concurrency ?? 16;
45
+ if (!Number.isInteger(concurrency) || concurrency < 1 || concurrency > 32) {
46
+ throw new FsSafeError("invalid-path", "clone concurrency must be an integer between 1 and 32");
47
+ }
48
+ const native = policy === "always"
49
+ ? requireNativeBinding()
50
+ : policy === "auto"
51
+ ? getNativeBinding()
52
+ : undefined;
53
+ const target = assertAbsolutePathInput(destination);
54
+ const name = path.basename(target);
55
+ if (!name)
56
+ throw new FsSafeError("invalid-path", "clone destination must name a child directory");
57
+ const parent = openStagedDirectory(path.dirname(target));
58
+ let original;
59
+ try {
60
+ const supported = native?.probeTreeClone(parent.fd);
61
+ if (!supported && policy === "always") {
62
+ throw new FsSafeError("unsupported-platform", "destination filesystem does not support tree cloning");
63
+ }
64
+ if (source !== undefined) {
65
+ original = openStagedDirectory(assertAbsolutePathInput(source));
66
+ const relative = path.relative(original.receipt.realPath, path.join(parent.receipt.realPath, name));
67
+ if (relative === "" ||
68
+ (!path.isAbsolute(relative) && relative !== ".." && !relative.startsWith(`..${path.sep}`))) {
69
+ throw new FsSafeError("invalid-path", "clone destination must be outside the source tree");
70
+ }
71
+ assertStagedDirectoryCurrent(original.receipt);
72
+ }
73
+ assertStagedDirectoryCurrent(parent.receipt);
74
+ options.signal?.throwIfAborted();
75
+ let cloned = false;
76
+ // napi-rs uses the supplied signal's onabort property. Give it a private
77
+ // signal so caller handlers and other admitted native operations stay intact.
78
+ const cancellation = options.signal && supported ? new AbortController() : undefined;
79
+ const abort = () => cancellation?.abort();
80
+ options.signal?.addEventListener("abort", abort, { once: true });
81
+ try {
82
+ if (native && supported) {
83
+ await native.cloneTree(original?.fd ?? null, parent.fd, name, concurrency, cancellation?.signal);
84
+ cloned = true;
85
+ }
86
+ }
87
+ catch (error) {
88
+ options.signal?.throwIfAborted();
89
+ if (policy !== "auto" || !cloneUnavailable(error)) {
90
+ if (error instanceof Error && "code" in error && error.code === "CLONE_UNAVAILABLE") {
91
+ throw new FsSafeError("unsupported-platform", error.message, { cause: error });
92
+ }
93
+ throw error;
94
+ }
95
+ }
96
+ finally {
97
+ options.signal?.removeEventListener("abort", abort);
98
+ }
99
+ options.signal?.throwIfAborted();
100
+ assertStagedDirectoryCurrent(parent.receipt);
101
+ if (original)
102
+ assertStagedDirectoryCurrent(original.receipt);
103
+ if (!cloned && original) {
104
+ await copyOwnedTree(original, target, {
105
+ signal: options.signal,
106
+ concurrency: options.concurrency ?? (process.platform === "win32" ? 4 : 1),
107
+ copyFileContents: process.platform === "win32" || process.platform === "linux"
108
+ ? native?.copyFileContents
109
+ : undefined,
110
+ });
111
+ options.signal?.throwIfAborted();
112
+ assertStagedDirectoryCurrent(parent.receipt);
113
+ assertStagedDirectoryCurrent(original.receipt);
114
+ }
115
+ }
116
+ finally {
117
+ try {
118
+ if (original)
119
+ fs.closeSync(original.fd);
120
+ }
121
+ finally {
122
+ fs.closeSync(parent.fd);
123
+ }
124
+ }
125
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"directory-durability.d.ts","sourceRoot":"","sources":["../src/directory-durability.ts"],"names":[],"mappings":"AAAA,OAAe,EAAE,KAAK,KAAK,EAAE,MAAM,SAAS,CAAC;AAM7C,OAAO,EAAoB,KAAK,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAE7E,MAAM,MAAM,oBAAoB,GAC5B;IAAE,MAAM,EAAE,QAAQ,CAAA;CAAE,GACpB;IAAE,MAAM,EAAE,aAAa,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAE7C,MAAM,MAAM,gBAAgB,GAAG;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,KAAK,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,uBAAuB,GAAG,gBAAgB,GAAG;IACvD,UAAU,EAAE,oBAAoB,GAAG;QAAE,MAAM,EAAE,YAAY,CAAA;KAAE,CAAC;CAC7D,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B,QAAQ,CAAC,OAAO,EAAE,gBAAgB,CAAC;IACnC,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/B,IAAI,IAAI,OAAO,CAAC,oBAAoB,CAAC,CAAC;IACtC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB,CAAC;AAEF,MAAM,MAAM,6BAA6B,GAAG;IAC1C,aAAa,EAAE,MAAM,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,wBAAwB,CAAC,EAAE,gBAAgB,CAAC;IAC5C,MAAM,CAAC,EAAE,CAAC,aAAa,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACnD,CAAC;AAgKF,wBAAsB,YAAY,CAChC,SAAS,EAAE,MAAM,GAAG,gBAAgB,EACpC,OAAO,GAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAA;CAAO,GAC/B,OAAO,CAAC,eAAe,CAAC,CAa1B;AAED,wBAAsB,aAAa,CACjC,SAAS,EAAE,MAAM,GAAG,gBAAgB,EACpC,OAAO,GAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAA;CAAO,GAC/B,OAAO,CAAC,oBAAoB,CAAC,CAmB/B;AAED,wBAAgB,iBAAiB,CAC/B,SAAS,EAAE,MAAM,GAAG,gBAAgB,EACpC,OAAO,GAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAA;CAAO,GAC/B,oBAAoB,CAuCtB;AAED,wBAAsB,uBAAuB,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAElF;AAED,wBAAgB,2BAA2B,CAAC,aAAa,EAAE,MAAM,GAAG,IAAI,CAMvE;AAuBD,wBAAsB,sBAAsB,CAC1C,OAAO,EAAE,6BAA6B,GACrC,OAAO,CAAC,uBAAuB,CAAC,CA8ElC"}
1
+ {"version":3,"file":"directory-durability.d.ts","sourceRoot":"","sources":["../src/directory-durability.ts"],"names":[],"mappings":"AAAA,OAAe,EAAE,KAAK,KAAK,EAAE,MAAM,SAAS,CAAC;AAM7C,OAAO,EAAoB,KAAK,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAG7E,MAAM,MAAM,oBAAoB,GAC5B;IAAE,MAAM,EAAE,QAAQ,CAAA;CAAE,GACpB;IAAE,MAAM,EAAE,aAAa,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAE7C,MAAM,MAAM,gBAAgB,GAAG;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,KAAK,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,uBAAuB,GAAG,gBAAgB,GAAG;IACvD,UAAU,EAAE,oBAAoB,GAAG;QAAE,MAAM,EAAE,YAAY,CAAA;KAAE,CAAC;CAC7D,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B,QAAQ,CAAC,OAAO,EAAE,gBAAgB,CAAC;IACnC,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/B,IAAI,IAAI,OAAO,CAAC,oBAAoB,CAAC,CAAC;IACtC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB,CAAC;AAEF,MAAM,MAAM,6BAA6B,GAAG;IAC1C,aAAa,EAAE,MAAM,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,wBAAwB,CAAC,EAAE,gBAAgB,CAAC;IAC5C,MAAM,CAAC,EAAE,CAAC,aAAa,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACnD,CAAC;AAgKF,wBAAsB,YAAY,CAChC,SAAS,EAAE,MAAM,GAAG,gBAAgB,EACpC,OAAO,GAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAA;CAAO,GAC/B,OAAO,CAAC,eAAe,CAAC,CAa1B;AAED,wBAAsB,aAAa,CACjC,SAAS,EAAE,MAAM,GAAG,gBAAgB,EACpC,OAAO,GAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAA;CAAO,GAC/B,OAAO,CAAC,oBAAoB,CAAC,CAmB/B;AAED,wBAAgB,iBAAiB,CAC/B,SAAS,EAAE,MAAM,GAAG,gBAAgB,EACpC,OAAO,GAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAA;CAAO,GAC/B,oBAAoB,CAuCtB;AAED,wBAAsB,uBAAuB,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAElF;AAED,wBAAgB,2BAA2B,CAAC,aAAa,EAAE,MAAM,GAAG,IAAI,CAMvE;AAuBD,wBAAsB,sBAAsB,CAC1C,OAAO,EAAE,6BAA6B,GACrC,OAAO,CAAC,uBAAuB,CAAC,CA8ElC"}
@@ -4,6 +4,7 @@ import path from "node:path";
4
4
  import { ensureAbsoluteDirectory } from "./absolute-path.js";
5
5
  import { FsSafeError } from "./errors.js";
6
6
  import { sameFileIdentity } from "./file-identity.js";
7
+ import { realpathSync } from "./realpath.js";
7
8
  function directoryOpenFlags() {
8
9
  if (process.platform === "win32") {
9
10
  return "r";
@@ -47,7 +48,7 @@ async function createDirectoryReceipt(directoryPath, label) {
47
48
  assertDirectory(identity, resolvedPath, label);
48
49
  return {
49
50
  path: resolvedPath,
50
- realPath: fsSync.realpathSync.native(resolvedPath),
51
+ realPath: realpathSync.native(resolvedPath),
51
52
  identity,
52
53
  };
53
54
  }
@@ -57,7 +58,7 @@ function createDirectoryReceiptSync(directoryPath, label) {
57
58
  assertDirectory(identity, resolvedPath, label);
58
59
  return {
59
60
  path: resolvedPath,
60
- realPath: fsSync.realpathSync(resolvedPath),
61
+ realPath: realpathSync(resolvedPath),
61
62
  identity,
62
63
  };
63
64
  }
@@ -65,7 +66,7 @@ async function assertDirectoryReceiptCurrent(receipt, label) {
65
66
  const currentIdentity = fsSync.lstatSync(receipt.path);
66
67
  assertDirectory(currentIdentity, receipt.path, label);
67
68
  if (!sameFileIdentity(receipt.identity, currentIdentity) ||
68
- (fsSync.realpathSync.native(receipt.path)) !== receipt.realPath) {
69
+ (realpathSync.native(receipt.path)) !== receipt.realPath) {
69
70
  throw new FsSafeError("path-mismatch", `${label} changed during durable directory operation: ${receipt.path}`);
70
71
  }
71
72
  }
@@ -73,7 +74,7 @@ function assertDirectoryReceiptCurrentSync(receipt, label) {
73
74
  const currentIdentity = fsSync.lstatSync(receipt.path);
74
75
  assertDirectory(currentIdentity, receipt.path, label);
75
76
  if (!sameFileIdentity(receipt.identity, currentIdentity) ||
76
- fsSync.realpathSync(receipt.path) !== receipt.realPath) {
77
+ realpathSync(receipt.path) !== receipt.realPath) {
77
78
  throw new FsSafeError("path-mismatch", `${label} changed during durable directory operation: ${receipt.path}`);
78
79
  }
79
80
  }
@@ -10,6 +10,15 @@ export type SyncDirectoryGuard = {
10
10
  realPath: string;
11
11
  stat: Stats;
12
12
  };
13
+ export type DirectoryIdentity = Readonly<{
14
+ dev: bigint;
15
+ ino: bigint;
16
+ realPath: string;
17
+ }>;
18
+ export declare function readDirectoryIdentity(dir: string): Promise<DirectoryIdentity>;
19
+ export declare function assertDirectoryIdentitySync(observedPath: string, expected: Pick<DirectoryIdentity, "dev" | "ino"> & {
20
+ realPath?: string;
21
+ }): void;
13
22
  export declare function createAsyncDirectoryGuard(dir: string, options: {
14
23
  bigint: true;
15
24
  }): Promise<AsyncDirectoryGuard<BigIntStats>>;
@@ -21,11 +30,12 @@ export declare function createAsyncDirectoryGuard(dir: string, options: {
21
30
  }): Promise<AnyAsyncDirectoryGuard>;
22
31
  export declare function assertAsyncDirectoryGuard(guard: AnyAsyncDirectoryGuard): Promise<void>;
23
32
  export declare function createSyncDirectoryGuard(dir: string): SyncDirectoryGuard;
24
- export declare function assertSyncDirectoryGuard(guard: SyncDirectoryGuard): void;
33
+ export declare function assertSyncDirectoryGuard(guard: SyncDirectoryGuard | AnyAsyncDirectoryGuard): void;
25
34
  export declare function createNearestExistingDirectoryGuard(rootReal: string, targetPath: string): Promise<AsyncDirectoryGuard>;
26
35
  export declare function createNearestExistingDirectoryGuard(rootReal: string, targetPath: string, options: {
27
36
  bigint: boolean;
28
37
  }): Promise<AnyAsyncDirectoryGuard>;
29
38
  export declare function createNearestExistingSyncDirectoryGuard(rootReal: string, targetPath: string): SyncDirectoryGuard;
30
39
  export declare function inspectDirectoryIdentity(dir: string, expected?: Pick<BigIntStats, "dev" | "ino">): Promise<BigIntStats>;
40
+ export declare function inspectDirectoryIdentitySync(dir: string, expected?: Pick<BigIntStats, "dev" | "ino">): BigIntStats;
31
41
  //# sourceMappingURL=directory-guard.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"directory-guard.d.ts","sourceRoot":"","sources":["../src/directory-guard.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AASlD,MAAM,MAAM,mBAAmB,CAAC,CAAC,SAAS,KAAK,GAAG,WAAW,GAAG,KAAK,IAAI;IACvE,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,CAAC,CAAC;CACT,CAAC;AAEF,MAAM,MAAM,sBAAsB,GAAG,mBAAmB,CAAC,KAAK,GAAG,WAAW,CAAC,CAAC;AAE9E,MAAM,MAAM,kBAAkB,GAAG;IAC/B,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,KAAK,CAAC;CACb,CAAC;AAEF,wBAAgB,yBAAyB,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE;IAAE,MAAM,EAAE,IAAI,CAAA;CAAE,GAAG,OAAO,CAAC,mBAAmB,CAAC,WAAW,CAAC,CAAC,CAAC;AAC7H,wBAAgB,yBAAyB,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;IAAE,MAAM,CAAC,EAAE,KAAK,CAAA;CAAE,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAAC;AACnH,wBAAgB,yBAAyB,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE;IAAE,MAAM,EAAE,OAAO,CAAA;CAAE,GAAG,OAAO,CAAC,sBAAsB,CAAC,CAAC;AAStH,wBAAsB,yBAAyB,CAAC,KAAK,EAAE,sBAAsB,GAAG,OAAO,CAAC,IAAI,CAAC,CAU5F;AAED,wBAAgB,wBAAwB,CAAC,GAAG,EAAE,MAAM,GAAG,kBAAkB,CAMxE;AAED,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,kBAAkB,GAAG,IAAI,CAQxE;AAED,wBAAgB,mCAAmC,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAAC;AACxH,wBAAgB,mCAAmC,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE;IAAE,MAAM,EAAE,OAAO,CAAA;CAAE,GAAG,OAAO,CAAC,sBAAsB,CAAC,CAAC;AAqBzJ,wBAAgB,uCAAuC,CACrD,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,GACjB,kBAAkB,CAcpB;AAGD,wBAAsB,wBAAwB,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,IAAI,CAAC,WAAW,EAAE,KAAK,GAAG,KAAK,CAAC,GAAG,OAAO,CAAC,WAAW,CAAC,CAM7H"}
1
+ {"version":3,"file":"directory-guard.d.ts","sourceRoot":"","sources":["../src/directory-guard.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAUlD,MAAM,MAAM,mBAAmB,CAAC,CAAC,SAAS,KAAK,GAAG,WAAW,GAAG,KAAK,IAAI;IACvE,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,CAAC,CAAC;CACT,CAAC;AAEF,MAAM,MAAM,sBAAsB,GAAG,mBAAmB,CAAC,KAAK,GAAG,WAAW,CAAC,CAAC;AAE9E,MAAM,MAAM,kBAAkB,GAAG;IAC/B,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,KAAK,CAAC;CACb,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG,QAAQ,CAAC;IACvC,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC,CAAC;AAEH,wBAAsB,qBAAqB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAGnF;AAED,wBAAgB,2BAA2B,CACzC,YAAY,EAAE,MAAM,EACpB,QAAQ,EAAE,IAAI,CAAC,iBAAiB,EAAE,KAAK,GAAG,KAAK,CAAC,GAAG;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,GACvE,IAAI,CAKN;AAED,wBAAgB,yBAAyB,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE;IAAE,MAAM,EAAE,IAAI,CAAA;CAAE,GAAG,OAAO,CAAC,mBAAmB,CAAC,WAAW,CAAC,CAAC,CAAC;AAC7H,wBAAgB,yBAAyB,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;IAAE,MAAM,CAAC,EAAE,KAAK,CAAA;CAAE,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAAC;AACnH,wBAAgB,yBAAyB,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE;IAAE,MAAM,EAAE,OAAO,CAAA;CAAE,GAAG,OAAO,CAAC,sBAAsB,CAAC,CAAC;AAStH,wBAAsB,yBAAyB,CAAC,KAAK,EAAE,sBAAsB,GAAG,OAAO,CAAC,IAAI,CAAC,CAY5F;AAED,wBAAgB,wBAAwB,CAAC,GAAG,EAAE,MAAM,GAAG,kBAAkB,CAMxE;AAED,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,kBAAkB,GAAG,sBAAsB,GAAG,IAAI,CAajG;AAED,wBAAgB,mCAAmC,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAAC;AACxH,wBAAgB,mCAAmC,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE;IAAE,MAAM,EAAE,OAAO,CAAA;CAAE,GAAG,OAAO,CAAC,sBAAsB,CAAC,CAAC;AAqBzJ,wBAAgB,uCAAuC,CACrD,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,GACjB,kBAAkB,CAcpB;AAGD,wBAAsB,wBAAwB,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,IAAI,CAAC,WAAW,EAAE,KAAK,GAAG,KAAK,CAAC,GAAG,OAAO,CAAC,WAAW,CAAC,CAE7H;AAmBD,wBAAgB,4BAA4B,CAC1C,GAAG,EAAE,MAAM,EACX,QAAQ,CAAC,EAAE,IAAI,CAAC,WAAW,EAAE,KAAK,GAAG,KAAK,CAAC,GAC1C,WAAW,CAQb"}
@@ -2,24 +2,37 @@ import fsSync from "node:fs";
2
2
  import path from "node:path";
3
3
  import { FsSafeError } from "./errors.js";
4
4
  import { sameFileIdentity } from "./file-identity.js";
5
- import { inspectFileIdentity } from "./strict-file-identity.js";
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 { realpathSync } from "./realpath.js";
9
+ export async function readDirectoryIdentity(dir) {
10
+ const guard = await createAsyncDirectoryGuard(dir, { bigint: true });
11
+ return Object.freeze({ dev: guard.stat.dev, ino: guard.stat.ino, realPath: guard.realPath });
12
+ }
13
+ export function assertDirectoryIdentitySync(observedPath, expected) {
14
+ inspectDirectoryIdentitySync(observedPath, expected);
15
+ if (expected.realPath !== undefined && realpathSync.native(observedPath) !== expected.realPath) {
16
+ throw new FsSafeError("path-mismatch", "directory changed during operation");
17
+ }
18
+ }
8
19
  export async function createAsyncDirectoryGuard(dir, options) {
9
- const stat = options?.bigint ? await inspectDirectoryIdentity(dir) : fsSync.lstatSync(dir);
20
+ const stat = options?.bigint ? inspectDirectoryIdentitySync(dir) : fsSync.lstatSync(dir);
10
21
  if (stat.isSymbolicLink() || !stat.isDirectory()) {
11
22
  throw directoryComponentNotDirectoryError();
12
23
  }
13
- return { dir, realPath: fsSync.realpathSync.native(dir), stat };
24
+ return { dir, realPath: realpathSync.native(dir), stat };
14
25
  }
15
26
  export async function assertAsyncDirectoryGuard(guard) {
16
- const stat = typeof guard.stat.dev === "bigint" && typeof guard.stat.ino === "bigint"
17
- ? await inspectDirectoryIdentity(guard.dir, { dev: guard.stat.dev, ino: guard.stat.ino })
18
- : fsSync.lstatSync(guard.dir);
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);
19
32
  if (stat.isSymbolicLink() || !stat.isDirectory()) {
20
33
  throw directoryComponentNotDirectoryError();
21
34
  }
22
- if (!sameFileIdentity(stat, guard.stat) || fsSync.realpathSync.native(guard.dir) !== guard.realPath) {
35
+ if (!sameFileIdentity(stat, guard.stat) || realpathSync.native(guard.dir) !== guard.realPath) {
23
36
  throw new FsSafeError("path-mismatch", "directory changed during operation");
24
37
  }
25
38
  }
@@ -28,14 +41,19 @@ export function createSyncDirectoryGuard(dir) {
28
41
  if (stat.isSymbolicLink() || !stat.isDirectory()) {
29
42
  throw directoryComponentNotDirectoryError();
30
43
  }
31
- return { dir, realPath: fsSync.realpathSync(dir), stat };
44
+ return { dir, realPath: realpathSync(dir), stat };
32
45
  }
33
46
  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
+ }
34
51
  const stat = fsSync.lstatSync(guard.dir);
35
52
  if (stat.isSymbolicLink() || !stat.isDirectory()) {
36
53
  throw directoryComponentNotDirectoryError();
37
54
  }
38
- if (!sameFileIdentity(stat, guard.stat) || fsSync.realpathSync(guard.dir) !== guard.realPath) {
55
+ const realPath = realpathSync(guard.dir);
56
+ if (!sameFileIdentity(stat, guard.stat) || realPath !== guard.realPath) {
39
57
  throw new FsSafeError("path-mismatch", "directory changed during operation");
40
58
  }
41
59
  }
@@ -73,8 +91,32 @@ export function createNearestExistingSyncDirectoryGuard(rootReal, targetPath) {
73
91
  }
74
92
  // Recovery receipts must retain every identity bit, including on Windows.
75
93
  export async function inspectDirectoryIdentity(dir, expected) {
76
- return await inspectFileIdentity(async () => {
77
- const stat = fsSync.lstatSync(dir, { bigint: true });
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);
118
+ return inspectFileIdentitySync(() => {
119
+ const stat = fsSync.lstatSync(entryPath, { bigint: true });
78
120
  if (stat.isSymbolicLink() || !stat.isDirectory())
79
121
  throw directoryComponentNotDirectoryError();
80
122
  return stat;
@@ -1,4 +1,4 @@
1
1
  export { ensureDurableDirectory, pinDirectory, syncDirectory, syncDirectoryBestEffort, syncDirectoryBestEffortSync, syncDirectorySync, type DirectoryReceipt, type DirectorySyncOutcome, type DurableDirectoryReceipt, type EnsureDurableDirectoryOptions, type PinnedDirectory, } from "./directory-durability.js";
2
2
  export { isHardlinkFallbackError, publishFileExclusive, type PublishFileExclusiveResult, type PublishFileExclusiveStrategy, type PublishFileExclusiveCleanup, type PublishFileExclusiveDirectorySyncFailure, type PublishFileExclusiveFailureDetails, type PublishFileExclusiveFailurePhase, type PublishFileExclusiveSyncFailurePolicy, } from "./publish-file.js";
3
- export { sha256File, type Sha256FileInput, type Sha256FileResult, } from "./file-hash.js";
3
+ export { sha256File, sha256FileSync, type Sha256FileInput, type Sha256FileSyncInput, type Sha256FileOptions, type Sha256FileResult, } from "./file-hash.js";
4
4
  //# sourceMappingURL=durability.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"durability.d.ts","sourceRoot":"","sources":["../src/durability.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,sBAAsB,EACtB,YAAY,EACZ,aAAa,EACb,uBAAuB,EACvB,2BAA2B,EAC3B,iBAAiB,EACjB,KAAK,gBAAgB,EACrB,KAAK,oBAAoB,EACzB,KAAK,uBAAuB,EAC5B,KAAK,6BAA6B,EAClC,KAAK,eAAe,GACrB,MAAM,2BAA2B,CAAC;AACnC,OAAO,EACL,uBAAuB,EACvB,oBAAoB,EACpB,KAAK,0BAA0B,EAC/B,KAAK,4BAA4B,EACjC,KAAK,2BAA2B,EAChC,KAAK,wCAAwC,EAC7C,KAAK,kCAAkC,EACvC,KAAK,gCAAgC,EACrC,KAAK,qCAAqC,GAC3C,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACL,UAAU,EACV,KAAK,eAAe,EACpB,KAAK,gBAAgB,GACtB,MAAM,gBAAgB,CAAC"}
1
+ {"version":3,"file":"durability.d.ts","sourceRoot":"","sources":["../src/durability.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,sBAAsB,EACtB,YAAY,EACZ,aAAa,EACb,uBAAuB,EACvB,2BAA2B,EAC3B,iBAAiB,EACjB,KAAK,gBAAgB,EACrB,KAAK,oBAAoB,EACzB,KAAK,uBAAuB,EAC5B,KAAK,6BAA6B,EAClC,KAAK,eAAe,GACrB,MAAM,2BAA2B,CAAC;AACnC,OAAO,EACL,uBAAuB,EACvB,oBAAoB,EACpB,KAAK,0BAA0B,EAC/B,KAAK,4BAA4B,EACjC,KAAK,2BAA2B,EAChC,KAAK,wCAAwC,EAC7C,KAAK,kCAAkC,EACvC,KAAK,gCAAgC,EACrC,KAAK,qCAAqC,GAC3C,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACL,UAAU,EACV,cAAc,EACd,KAAK,eAAe,EACpB,KAAK,mBAAmB,EACxB,KAAK,iBAAiB,EACtB,KAAK,gBAAgB,GACtB,MAAM,gBAAgB,CAAC"}
@@ -1,3 +1,3 @@
1
1
  export { ensureDurableDirectory, pinDirectory, syncDirectory, syncDirectoryBestEffort, syncDirectoryBestEffortSync, syncDirectorySync, } from "./directory-durability.js";
2
2
  export { isHardlinkFallbackError, publishFileExclusive, } from "./publish-file.js";
3
- export { sha256File, } from "./file-hash.js";
3
+ export { sha256File, sha256FileSync, } from "./file-hash.js";
@@ -1 +1 @@
1
- {"version":3,"file":"error-detail.d.ts","sourceRoot":"","sources":["../src/error-detail.ts"],"names":[],"mappings":"AAKA,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAIvD;AAED,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAI/C"}
1
+ {"version":3,"file":"error-detail.d.ts","sourceRoot":"","sources":["../src/error-detail.ts"],"names":[],"mappings":"AAMA,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAIvD;AAED,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAM/C"}
@@ -1,10 +1,13 @@
1
1
  import os from "node:os";
2
+ import path from "node:path";
2
3
  const UNSAFE_ERROR_DETAIL_CHARACTERS = /[\u0000-\u001f\u007f-\u009f\u2028\u2029]/gu;
3
4
  export function formatErrorDetail(value) {
4
5
  return value.replace(UNSAFE_ERROR_DETAIL_CHARACTERS, (character) => `\\u${character.charCodeAt(0).toString(16).padStart(4, "0")}`);
5
6
  }
6
7
  export function shortPath(value) {
7
8
  const home = os.homedir();
8
- const shortened = value.startsWith(home) ? `~${value.slice(home.length)}` : value;
9
+ const prefix = home.endsWith(path.sep) ? home : `${home}${path.sep}`;
10
+ const shortened = value === home ? "~"
11
+ : value.startsWith(prefix) ? `~${path.sep}${value.slice(prefix.length)}` : value;
9
12
  return formatErrorDetail(shortened);
10
13
  }
@@ -0,0 +1,14 @@
1
+ import type { FileHandle } from "node:fs/promises";
2
+ export type CopyFileHandleOptions = {
3
+ maxBytes?: number;
4
+ signal?: AbortSignal;
5
+ onChunk?: (chunk: Uint8Array) => void;
6
+ assertBeforeMutation?: () => void;
7
+ };
8
+ /** Copies caller-owned regular files from offset zero without moving either cursor. */
9
+ export declare function copyFileHandle(source: FileHandle, target: FileHandle, options?: CopyFileHandleOptions): Promise<number>;
10
+ export declare function transferFileHandle(source: FileHandle, target: FileHandle | number, options: CopyFileHandleOptions & {
11
+ sizeHint: number;
12
+ targetPosition?: number;
13
+ }): Promise<number>;
14
+ //# sourceMappingURL=file-handle-transfer.d.ts.map
@@ -0,0 +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"}
@@ -0,0 +1,64 @@
1
+ import { normalizeMaxBytes } from "./byte-budget.js";
2
+ import { FsSafeError } from "./errors.js";
3
+ import { assertSynchronousCallbackResult, composeMutationAssertions, rethrowMutationAuthorityError, } from "./mutation-authority.js";
4
+ import { writeAllToFile } from "./write-file-handle.js";
5
+ import { inspectFileIdentity } from "./strict-file-identity.js";
6
+ /** Copies caller-owned regular files from offset zero without moving either cursor. */
7
+ export async function copyFileHandle(source, target, options = {}) {
8
+ options.signal?.throwIfAborted();
9
+ const maxBytes = normalizeMaxBytes(options.maxBytes);
10
+ const sourceStat = await inspectFileIdentity(() => {
11
+ options.signal?.throwIfAborted();
12
+ return source.stat({ bigint: true });
13
+ });
14
+ options.signal?.throwIfAborted();
15
+ if (!sourceStat.isFile())
16
+ throw new FsSafeError("not-file", "copy source handle must be a regular file");
17
+ if (maxBytes !== undefined && Number.isFinite(maxBytes) && sourceStat.size > BigInt(maxBytes)) {
18
+ throw new FsSafeError("too-large", `file exceeds limit of ${maxBytes} bytes`);
19
+ }
20
+ const targetStat = await inspectFileIdentity(() => {
21
+ options.signal?.throwIfAborted();
22
+ return target.stat({ bigint: true });
23
+ });
24
+ options.signal?.throwIfAborted();
25
+ if (!targetStat.isFile())
26
+ throw new FsSafeError("not-file", "copy target handle must be a regular file");
27
+ if (sourceStat.dev === targetStat.dev && sourceStat.ino === targetStat.ino) {
28
+ throw new FsSafeError("path-alias", "copy source and target handles refer to the same file");
29
+ }
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);
37
+ }
38
+ // 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));
42
+ let position = 0;
43
+ 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`);
49
+ }
50
+ if (bytesRead === 0)
51
+ return position;
52
+ const chunk = buffer.subarray(0, bytesRead);
53
+ if (options.onChunk)
54
+ assertSynchronousCallbackResult(options.onChunk(chunk), "onChunk");
55
+ await writeAllToFile(target, chunk, {
56
+ position: options.targetPosition === undefined ? undefined : options.targetPosition + position,
57
+ assertBeforeMutation: () => {
58
+ options.signal?.throwIfAborted();
59
+ options.assertBeforeMutation?.();
60
+ },
61
+ });
62
+ position += bytesRead;
63
+ }
64
+ }
@@ -1,10 +1,17 @@
1
1
  import type { FileHandle } from "node:fs/promises";
2
2
  import { type NativeBinding } from "./native.js";
3
3
  export type Sha256FileInput = string | FileHandle;
4
+ export type Sha256FileSyncInput = string | number;
5
+ export type Sha256FileOptions = {
6
+ maxBytes?: number;
7
+ signal?: AbortSignal;
8
+ };
4
9
  export type Sha256FileResult = {
5
10
  bytes: number;
6
11
  digest: string;
7
12
  };
8
- export declare function hashFileHandle(handle: FileHandle, native?: NativeBinding | undefined): Promise<Sha256FileResult>;
9
- export declare function sha256File(input: Sha256FileInput): Promise<Sha256FileResult>;
13
+ export declare function hashFileHandle(handle: FileHandle, native?: NativeBinding | undefined, { maxBytes, signal }?: Sha256FileOptions): Promise<Sha256FileResult>;
14
+ export declare function sha256File(input: Sha256FileInput, options?: Sha256FileOptions): Promise<Sha256FileResult>;
15
+ /** Hash synchronously from offset zero, retaining ownership and position of a borrowed fd. */
16
+ export declare function sha256FileSync(input: Sha256FileSyncInput, options?: Sha256FileOptions): Sha256FileResult;
10
17
  //# sourceMappingURL=file-hash.d.ts.map
@@ -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;AAGnD,OAAO,EAAoB,KAAK,aAAa,EAAE,MAAM,aAAa,CAAC;AAInE,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,UAAU,CAAC;AAElD,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,GACrD,OAAO,CAAC,gBAAgB,CAAC,CAoB3B;AA+CD,wBAAsB,UAAU,CAAC,KAAK,EAAE,eAAe,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAElF"}
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"}