@openclaw/feishu 2026.9.8 → 2026.10.1-beta.1

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 (225) hide show
  1. package/dist/.setup/{channel-DV4hM2Uf.mjs → channel-CX62T71S.mjs} +146 -245
  2. package/dist/.setup/{channel.runtime-CXQcH5zZ.mjs → channel.runtime-COiaq-Rv.mjs} +6 -12
  3. package/dist/.setup/{chat-D8MlFAZi.mjs → chat-BdyvImsY.mjs} +6 -9
  4. package/dist/.setup/{client-BYG-_IZl.mjs → client-BsBVWJzN.mjs} +1 -1
  5. package/dist/.setup/{doctor-contract-3XC0vbIw.mjs → doctor-contract-B1B9ogdd.mjs} +212 -27
  6. package/dist/.setup/{monitor-D_rsHBZ6.mjs → monitor-7DvwUs4Y.mjs} +5 -6
  7. package/dist/.setup/{monitor.account-DvvY7i6k.mjs → monitor.account-7xVu7XWY.mjs} +190 -393
  8. package/dist/.setup/{probe-_ViqSi0j.mjs → probe-B9o7iey6.mjs} +13 -39
  9. package/dist/.setup/{reply-delivery-result-COAjaukc.mjs → reply-delivery-result-Bt-BWBEA.mjs} +151 -297
  10. package/dist/.setup/{setup-api-C4S5I3ac.mjs → setup-api-DmzX9S01.mjs} +1 -1
  11. package/dist/.setup/{subagent-hooks-BaFoMu8W.mjs → subagent-hooks-CUXg0pC-.mjs} +5 -11
  12. package/dist/.setup/{thread-bindings-EdLXPXzw.mjs → thread-bindings-jW2x5VuN.mjs} +23 -41
  13. package/dist/api.js +234 -297
  14. package/dist/channel-plugin-api.js +1 -1
  15. package/dist/doctor-contract-api.js +2 -2
  16. package/dist/session-binding-contract-api.js +1 -1
  17. package/dist/setup-api.js +1 -1
  18. package/dist/setup-entry.js +1 -1
  19. package/dist/subagent-hooks-api.js +1 -1
  20. package/node_modules/@openclaw/fs-safe/CHANGELOG.md +95 -0
  21. package/node_modules/@openclaw/fs-safe/README.md +70 -379
  22. package/node_modules/@openclaw/fs-safe/dist/absolute-path.js +35 -51
  23. package/node_modules/@openclaw/fs-safe/dist/advanced.d.ts +2 -0
  24. package/node_modules/@openclaw/fs-safe/dist/advanced.js +1 -0
  25. package/node_modules/@openclaw/fs-safe/dist/archive-input.js +2 -0
  26. package/node_modules/@openclaw/fs-safe/dist/archive-parser.wasm +0 -0
  27. package/node_modules/@openclaw/fs-safe/dist/archive-staging.d.ts +1 -1
  28. package/node_modules/@openclaw/fs-safe/dist/archive-staging.js +9 -10
  29. package/node_modules/@openclaw/fs-safe/dist/archive.js +2 -5
  30. package/node_modules/@openclaw/fs-safe/dist/atomic-io.d.ts +51 -0
  31. package/node_modules/@openclaw/fs-safe/dist/atomic-io.js +242 -0
  32. package/node_modules/@openclaw/fs-safe/dist/config.d.ts +1 -1
  33. package/node_modules/@openclaw/fs-safe/dist/config.js +1 -1
  34. package/node_modules/@openclaw/fs-safe/dist/copy-file-input.d.ts +1 -1
  35. package/node_modules/@openclaw/fs-safe/dist/copy-file-input.js +2 -2
  36. package/node_modules/@openclaw/fs-safe/dist/copy-tree-portable.js +2 -0
  37. package/node_modules/@openclaw/fs-safe/dist/create.js +12 -5
  38. package/node_modules/@openclaw/fs-safe/dist/directory-guard.d.ts +11 -0
  39. package/node_modules/@openclaw/fs-safe/dist/directory-guard.js +4 -7
  40. package/node_modules/@openclaw/fs-safe/dist/entry-publication-types.d.ts +55 -0
  41. package/node_modules/@openclaw/fs-safe/dist/entry-publication-types.js +1 -0
  42. package/node_modules/@openclaw/fs-safe/dist/entry-publication.d.ts +8 -0
  43. package/node_modules/@openclaw/fs-safe/dist/entry-publication.js +262 -0
  44. package/node_modules/@openclaw/fs-safe/dist/exclusive-create.d.ts +3 -0
  45. package/node_modules/@openclaw/fs-safe/dist/exclusive-create.js +22 -0
  46. package/node_modules/@openclaw/fs-safe/dist/file-cleanup.d.ts +1 -6
  47. package/node_modules/@openclaw/fs-safe/dist/file-cleanup.js +3 -6
  48. package/node_modules/@openclaw/fs-safe/dist/file-identity.js +3 -6
  49. package/node_modules/@openclaw/fs-safe/dist/file-lock-sync-root-io.js +2 -2
  50. package/node_modules/@openclaw/fs-safe/dist/file-lock-sync-root-mutation.js +9 -0
  51. package/node_modules/@openclaw/fs-safe/dist/file-lock-sync-root.js +4 -6
  52. package/node_modules/@openclaw/fs-safe/dist/file-lock-sync-stale-admission.js +1 -1
  53. package/node_modules/@openclaw/fs-safe/dist/file-lock-sync.js +8 -0
  54. package/node_modules/@openclaw/fs-safe/dist/file-store-boundary.js +2 -0
  55. package/node_modules/@openclaw/fs-safe/dist/file-store-sync-write.js +20 -18
  56. package/node_modules/@openclaw/fs-safe/dist/file-store.js +4 -10
  57. package/node_modules/@openclaw/fs-safe/dist/fs.d.ts +2 -10
  58. package/node_modules/@openclaw/fs-safe/dist/fs.js +2 -10
  59. package/node_modules/@openclaw/fs-safe/dist/guarded-mkdir.d.ts +3 -4
  60. package/node_modules/@openclaw/fs-safe/dist/guarded-mkdir.js +6 -18
  61. package/node_modules/@openclaw/fs-safe/dist/guest-dispatch-python.js +1 -1
  62. package/node_modules/@openclaw/fs-safe/dist/guest-native-python.js +0 -2
  63. package/node_modules/@openclaw/fs-safe/dist/guest.js +3 -9
  64. package/node_modules/@openclaw/fs-safe/dist/index.d.ts +1 -1
  65. package/node_modules/@openclaw/fs-safe/dist/index.js +1 -1
  66. package/node_modules/@openclaw/fs-safe/dist/json-durable-queue-directory.js +1 -5
  67. package/node_modules/@openclaw/fs-safe/dist/json.js +19 -37
  68. package/node_modules/@openclaw/fs-safe/dist/move-path.js +2 -0
  69. package/node_modules/@openclaw/fs-safe/dist/mutation-authority.d.ts +1 -0
  70. package/node_modules/@openclaw/fs-safe/dist/mutation-authority.js +9 -1
  71. package/node_modules/@openclaw/fs-safe/dist/native-binding.d.ts +47 -2
  72. package/node_modules/@openclaw/fs-safe/dist/native-config.d.ts +1 -9
  73. package/node_modules/@openclaw/fs-safe/dist/native-config.js +6 -40
  74. package/node_modules/@openclaw/fs-safe/dist/native-parent-admission.d.ts +2 -0
  75. package/node_modules/@openclaw/fs-safe/dist/native-parent-admission.js +5 -2
  76. package/node_modules/@openclaw/fs-safe/dist/native-pinned-write.js +11 -316
  77. package/node_modules/@openclaw/fs-safe/dist/native-policy-parent-windows.d.ts +7 -7
  78. package/node_modules/@openclaw/fs-safe/dist/native-policy-parent-windows.js +28 -175
  79. package/node_modules/@openclaw/fs-safe/dist/native-policy-parent.d.ts +15 -0
  80. package/node_modules/@openclaw/fs-safe/dist/native-policy-parent.js +418 -0
  81. package/node_modules/@openclaw/fs-safe/dist/native-rename-outcome.js +9 -4
  82. package/node_modules/@openclaw/fs-safe/dist/native-staged-file.js +17 -13
  83. package/node_modules/@openclaw/fs-safe/dist/native-staged-symlink.js +6 -24
  84. package/node_modules/@openclaw/fs-safe/dist/native.js +5 -1
  85. package/node_modules/@openclaw/fs-safe/dist/path-case.js +10 -9
  86. package/node_modules/@openclaw/fs-safe/dist/path-scope-lexical.js +4 -1
  87. package/node_modules/@openclaw/fs-safe/dist/path-segment-route.d.ts +1 -0
  88. package/node_modules/@openclaw/fs-safe/dist/path-segment-route.js +3 -0
  89. package/node_modules/@openclaw/fs-safe/dist/path.js +4 -1
  90. package/node_modules/@openclaw/fs-safe/dist/permissions-windows.d.ts +0 -1
  91. package/node_modules/@openclaw/fs-safe/dist/pinned-mutation-admission.js +1 -3
  92. package/node_modules/@openclaw/fs-safe/dist/pinned-write-staged.js +2 -0
  93. package/node_modules/@openclaw/fs-safe/dist/pinned-write.js +9 -10
  94. package/node_modules/@openclaw/fs-safe/dist/private-producer-handoff.js +3 -6
  95. package/node_modules/@openclaw/fs-safe/dist/publish-copy-stage.js +2 -2
  96. package/node_modules/@openclaw/fs-safe/dist/publish-file.js +4 -3
  97. package/node_modules/@openclaw/fs-safe/dist/replace-file-buffer.d.ts +3 -4
  98. package/node_modules/@openclaw/fs-safe/dist/replace-file-buffer.js +18 -32
  99. package/node_modules/@openclaw/fs-safe/dist/replace-file-copy-fallback.d.ts +5 -17
  100. package/node_modules/@openclaw/fs-safe/dist/replace-file-copy-fallback.js +92 -230
  101. package/node_modules/@openclaw/fs-safe/dist/replace-file-copy-source.d.ts +4 -15
  102. package/node_modules/@openclaw/fs-safe/dist/replace-file-copy-source.js +22 -63
  103. package/node_modules/@openclaw/fs-safe/dist/replace-file-descriptor.d.ts +9 -34
  104. package/node_modules/@openclaw/fs-safe/dist/replace-file-descriptor.js +78 -83
  105. package/node_modules/@openclaw/fs-safe/dist/replace-file-destination.d.ts +18 -20
  106. package/node_modules/@openclaw/fs-safe/dist/replace-file-destination.js +64 -98
  107. package/node_modules/@openclaw/fs-safe/dist/replace-file-temp-owner.d.ts +16 -33
  108. package/node_modules/@openclaw/fs-safe/dist/replace-file-temp-owner.js +61 -200
  109. package/node_modules/@openclaw/fs-safe/dist/replace-file-types.d.ts +1 -6
  110. package/node_modules/@openclaw/fs-safe/dist/replace-file.js +76 -216
  111. package/node_modules/@openclaw/fs-safe/dist/root-boundary.js +14 -24
  112. package/node_modules/@openclaw/fs-safe/dist/root-context.d.ts +1 -4
  113. package/node_modules/@openclaw/fs-safe/dist/root-context.js +4 -19
  114. package/node_modules/@openclaw/fs-safe/dist/root-create-native.d.ts +52 -0
  115. package/node_modules/@openclaw/fs-safe/dist/root-create-native.js +350 -0
  116. package/node_modules/@openclaw/fs-safe/dist/root-directory-list-types.d.ts +35 -0
  117. package/node_modules/@openclaw/fs-safe/dist/root-directory-list-types.js +1 -0
  118. package/node_modules/@openclaw/fs-safe/dist/root-directory-list.d.ts +2 -27
  119. package/node_modules/@openclaw/fs-safe/dist/root-directory-list.js +31 -28
  120. package/node_modules/@openclaw/fs-safe/dist/root-directory.js +1 -4
  121. package/node_modules/@openclaw/fs-safe/dist/root-errors.js +34 -0
  122. package/node_modules/@openclaw/fs-safe/dist/root-file.js +9 -21
  123. package/node_modules/@openclaw/fs-safe/dist/root-impl.js +105 -37
  124. package/node_modules/@openclaw/fs-safe/dist/root-move-noreplace.d.ts +7 -2
  125. package/node_modules/@openclaw/fs-safe/dist/root-move-noreplace.js +67 -38
  126. package/node_modules/@openclaw/fs-safe/dist/root-observed-path.d.ts +1 -1
  127. package/node_modules/@openclaw/fs-safe/dist/root-observed-path.js +0 -2
  128. package/node_modules/@openclaw/fs-safe/dist/root-options.d.ts +2 -3
  129. package/node_modules/@openclaw/fs-safe/dist/root-path-existing.js +3 -1
  130. package/node_modules/@openclaw/fs-safe/dist/root-path-stat.js +68 -85
  131. package/node_modules/@openclaw/fs-safe/dist/root-path.js +6 -1
  132. package/node_modules/@openclaw/fs-safe/dist/root-paths-lexical.d.ts +4 -0
  133. package/node_modules/@openclaw/fs-safe/dist/root-paths-lexical.js +1 -1
  134. package/node_modules/@openclaw/fs-safe/dist/root-paths.js +4 -9
  135. package/node_modules/@openclaw/fs-safe/dist/root-remove-native.d.ts +4 -0
  136. package/node_modules/@openclaw/fs-safe/dist/root-remove-native.js +311 -0
  137. package/node_modules/@openclaw/fs-safe/dist/root-remove.d.ts +11 -0
  138. package/node_modules/@openclaw/fs-safe/dist/root-remove.js +3 -1
  139. package/node_modules/@openclaw/fs-safe/dist/root-walk.d.ts +2 -2
  140. package/node_modules/@openclaw/fs-safe/dist/root-walk.js +1 -3
  141. package/node_modules/@openclaw/fs-safe/dist/root-write-admission.d.ts +3 -3
  142. package/node_modules/@openclaw/fs-safe/dist/root-write-admission.js +8 -10
  143. package/node_modules/@openclaw/fs-safe/dist/safe-path-segment.d.ts +0 -1
  144. package/node_modules/@openclaw/fs-safe/dist/safe-path-segment.js +0 -4
  145. package/node_modules/@openclaw/fs-safe/dist/secret-file.js +12 -9
  146. package/node_modules/@openclaw/fs-safe/dist/secure-temp-dir.d.ts +0 -2
  147. package/node_modules/@openclaw/fs-safe/dist/sibling-staged-file.js +2 -0
  148. package/node_modules/@openclaw/fs-safe/dist/sibling-temp.js +7 -4
  149. package/node_modules/@openclaw/fs-safe/dist/sidecar-lock-acquire.js +13 -6
  150. package/node_modules/@openclaw/fs-safe/dist/sidecar-lock-reclaim.d.ts +0 -3
  151. package/node_modules/@openclaw/fs-safe/dist/sidecar-lock-reclaim.js +5 -23
  152. package/node_modules/@openclaw/fs-safe/dist/staged-file-settlement.d.ts +2 -1
  153. package/node_modules/@openclaw/fs-safe/dist/staged-file-settlement.js +11 -6
  154. package/node_modules/@openclaw/fs-safe/dist/temp-cleanup.js +1 -8
  155. package/node_modules/@openclaw/fs-safe/dist/temp-workspace-child-admission.d.ts +1 -1
  156. package/node_modules/@openclaw/fs-safe/dist/temp-workspace-child-admission.js +2 -9
  157. package/node_modules/@openclaw/fs-safe/dist/text-atomic.d.ts +1 -6
  158. package/node_modules/@openclaw/fs-safe/dist/trash.js +6 -2
  159. package/node_modules/@openclaw/fs-safe/dist/walk.js +100 -46
  160. package/node_modules/@openclaw/fs-safe/dist/watch-hints.d.ts +7 -0
  161. package/node_modules/@openclaw/fs-safe/dist/watch-hints.js +235 -19
  162. package/node_modules/@openclaw/fs-safe/dist/watch-native.d.ts +21 -2
  163. package/node_modules/@openclaw/fs-safe/dist/watch-native.js +21 -3
  164. package/node_modules/@openclaw/fs-safe/dist/watch-rescan.d.ts +6 -0
  165. package/node_modules/@openclaw/fs-safe/dist/watch-rescan.js +111 -0
  166. package/node_modules/@openclaw/fs-safe/dist/watch-scan.d.ts +13 -0
  167. package/node_modules/@openclaw/fs-safe/dist/watch-scan.js +27 -4
  168. package/node_modules/@openclaw/fs-safe/dist/watch-stream.js +2 -0
  169. package/node_modules/@openclaw/fs-safe/dist/watch.js +131 -40
  170. package/node_modules/@openclaw/fs-safe/dist/windows-path-alias.d.ts +8 -0
  171. package/node_modules/@openclaw/fs-safe/dist/windows-path-alias.js +24 -6
  172. package/node_modules/@openclaw/fs-safe/dist/windows-path-syntax.d.ts +13 -0
  173. package/node_modules/@openclaw/fs-safe/dist/windows-path-syntax.js +51 -0
  174. package/node_modules/@openclaw/fs-safe/docs/advanced.md +4 -0
  175. package/node_modules/@openclaw/fs-safe/docs/atomic.md +4 -1
  176. package/node_modules/@openclaw/fs-safe/docs/config.md +6 -14
  177. package/node_modules/@openclaw/fs-safe/docs/contributing.md +4 -14
  178. package/node_modules/@openclaw/fs-safe/docs/durability.md +6 -29
  179. package/node_modules/@openclaw/fs-safe/docs/entry-publication.md +146 -0
  180. package/node_modules/@openclaw/fs-safe/docs/errors.md +16 -0
  181. package/node_modules/@openclaw/fs-safe/docs/file-store.md +28 -5
  182. package/node_modules/@openclaw/fs-safe/docs/index.md +2 -31
  183. package/node_modules/@openclaw/fs-safe/docs/install.md +10 -31
  184. package/node_modules/@openclaw/fs-safe/docs/json.md +1 -1
  185. package/node_modules/@openclaw/fs-safe/docs/local-roots.md +0 -1
  186. package/node_modules/@openclaw/fs-safe/docs/migrating-to-0.5.md +12 -12
  187. package/node_modules/@openclaw/fs-safe/docs/native-helper.md +35 -96
  188. package/node_modules/@openclaw/fs-safe/docs/native.md +22 -20
  189. package/node_modules/@openclaw/fs-safe/docs/path.md +1 -1
  190. package/node_modules/@openclaw/fs-safe/docs/permissions.md +1 -2
  191. package/node_modules/@openclaw/fs-safe/docs/public-api.md +1 -2
  192. package/node_modules/@openclaw/fs-safe/docs/reading.md +1 -2
  193. package/node_modules/@openclaw/fs-safe/docs/root.md +26 -184
  194. package/node_modules/@openclaw/fs-safe/docs/secret-file.md +1 -1
  195. package/node_modules/@openclaw/fs-safe/docs/security-model.md +115 -3
  196. package/node_modules/@openclaw/fs-safe/docs/store.md +2 -2
  197. package/node_modules/@openclaw/fs-safe/docs/temp.md +29 -65
  198. package/node_modules/@openclaw/fs-safe/docs/testing.md +90 -58
  199. package/node_modules/@openclaw/fs-safe/docs/types.md +3 -16
  200. package/node_modules/@openclaw/fs-safe/docs/walk.md +33 -5
  201. package/node_modules/@openclaw/fs-safe/docs/watch.md +96 -14
  202. package/node_modules/@openclaw/fs-safe/docs/writing.md +172 -16
  203. package/node_modules/@openclaw/fs-safe/package.json +10 -10
  204. package/node_modules/@openclaw/fs-safe-darwin-arm64/fs-safe-native.node +0 -0
  205. package/node_modules/@openclaw/fs-safe-darwin-arm64/package.json +1 -1
  206. package/node_modules/@openclaw/fs-safe-darwin-x64/fs-safe-native.node +0 -0
  207. package/node_modules/@openclaw/fs-safe-darwin-x64/package.json +1 -1
  208. package/node_modules/@openclaw/fs-safe-linux-arm64-gnu/fs-safe-native.node +0 -0
  209. package/node_modules/@openclaw/fs-safe-linux-arm64-gnu/package.json +1 -1
  210. package/node_modules/@openclaw/fs-safe-linux-arm64-musl/fs-safe-native.node +0 -0
  211. package/node_modules/@openclaw/fs-safe-linux-arm64-musl/package.json +1 -1
  212. package/node_modules/@openclaw/fs-safe-linux-x64-gnu/fs-safe-native.node +0 -0
  213. package/node_modules/@openclaw/fs-safe-linux-x64-gnu/package.json +1 -1
  214. package/node_modules/@openclaw/fs-safe-linux-x64-musl/fs-safe-native.node +0 -0
  215. package/node_modules/@openclaw/fs-safe-linux-x64-musl/package.json +1 -1
  216. package/node_modules/@openclaw/fs-safe-win32-x64-msvc/fs-safe-native.node +0 -0
  217. package/node_modules/@openclaw/fs-safe-win32-x64-msvc/package.json +1 -1
  218. package/package.json +5 -5
  219. package/skills/feishu-wiki/SKILL.md +1 -1
  220. package/dist/.setup/accounts-wRqItHug.mjs +0 -206
  221. package/node_modules/@openclaw/fs-safe/dist/watch-alias.d.ts +0 -6
  222. package/node_modules/@openclaw/fs-safe/dist/watch-alias.js +0 -88
  223. package/node_modules/@openclaw/fs-safe/docs/mutation-policy-proof.md +0 -69
  224. package/node_modules/@openclaw/fs-safe/docs/private-file-store.md +0 -68
  225. package/node_modules/@openclaw/fs-safe/docs/test-hooks.md +0 -110
@@ -1,28 +1,19 @@
1
1
  import syncFs, {} from "node:fs";
2
- import fs, {} from "node:fs/promises";
2
+ import fs from "node:fs/promises";
3
+ import { inspectAtomicIdentity, runAsync, runSync, wait } from "./atomic-io.js";
3
4
  import { readBoundedAsync, readBoundedSync } from "./bounded-read.js";
4
5
  import { FsSafeError } from "./errors.js";
5
- import { inspectFileIdentity, inspectFileIdentitySync } from "./strict-file-identity.js";
6
- import { readOwnedCopySource, readOwnedCopySourceSync } from "./replace-file-copy-source.js";
6
+ import { readOwnedCopySource } from "./replace-file-copy-source.js";
7
7
  import { resolveReadOpenFlags } from "./read-open-flags.js";
8
8
  import { hasErrorCode } from "./file-cleanup.js";
9
- import { writeAtomicDestination, writeAtomicDestinationSync } from "./replace-file-buffer.js";
9
+ import { writeAtomicDestination } from "./replace-file-buffer.js";
10
10
  import { AtomicMutation } from "./replace-file-mutation.js";
11
- import { captureAtomicDestination, captureAtomicDestinationSync } from "./replace-file-destination.js";
11
+ import { captureAtomicDestination } from "./replace-file-destination.js";
12
12
  const SUPPORTS_NOFOLLOW = process.platform !== "win32" && "O_NOFOLLOW" in syncFs.constants;
13
13
  const NOFOLLOW = SUPPORTS_NOFOLLOW ? syncFs.constants.O_NOFOLLOW : 0;
14
14
  const OPEN_READ_FLAGS = resolveReadOpenFlags();
15
15
  const OPEN_READ_WRITE_FLAGS = syncFs.constants.O_RDWR | NOFOLLOW;
16
16
  const OPEN_WRITE_EXCLUSIVE_FLAGS = syncFs.constants.O_WRONLY | syncFs.constants.O_CREAT | syncFs.constants.O_EXCL | NOFOLLOW;
17
- function closeSyncAfterAdmissionFailure(fsModule, fd, error) {
18
- try {
19
- fsModule.closeSync(fd);
20
- }
21
- catch {
22
- // Preserve the already-selected admission failure.
23
- }
24
- throw error;
25
- }
26
17
  function admitDestinationKind(pathname, opened, dest, admission) {
27
18
  if (admission === "hardlinks") {
28
19
  if (pathname.isSymbolicLink() || !pathname.isFile()) {
@@ -37,27 +28,17 @@ function admitDestinationKind(pathname, opened, dest, admission) {
37
28
  }
38
29
  return pathname;
39
30
  }
40
- function assertDestinationLinks(opened, dest, admission, hardlinks) {
41
- if ((admission === "hardlinks" || hardlinks === "reject") && opened.nlink > 1n) {
42
- throw new FsSafeError("hardlink", `Hardlinked ${admission === "hardlinks" ? "atomic replace" : "copy fallback"} destination not allowed: ${dest}`);
43
- }
44
- }
45
- function inspectPinnedDestinationSync(fsModule, fd, dest, admission, hardlinks) {
46
- const opened = inspectFileIdentitySync(() => fsModule.fstatSync(fd, { bigint: true }));
47
- inspectFileIdentitySync(() => admitDestinationKind(fsModule.lstatSync(dest, { bigint: true }), opened, dest, admission), opened);
48
- assertDestinationLinks(opened, dest, admission, hardlinks);
49
- }
50
- async function openPinnedDestination(fsModule, dest, admission, hardlinks, mutation) {
31
+ function* openPinnedDestination(io, dest, admission, hardlinks, mutation) {
51
32
  let preview;
52
33
  try {
53
- preview = fsModule === fs ? syncFs.lstatSync(dest) : await fsModule.lstat(dest);
34
+ preview = yield* io.lstat(dest);
54
35
  }
55
36
  catch (error) {
56
37
  if (hasErrorCode(error, "ENOENT"))
57
38
  return null;
58
39
  throw error;
59
40
  }
60
- if (!preview)
41
+ if (io.asynchronous && !preview)
61
42
  return null;
62
43
  if (admission === "hardlinks" && (preview.isSymbolicLink() || !preview.isFile()))
63
44
  return null;
@@ -65,20 +46,24 @@ async function openPinnedDestination(fsModule, dest, admission, hardlinks, mutat
65
46
  throw new FsSafeError("symlink", `Refusing copy fallback through symlink destination: ${dest}`);
66
47
  }
67
48
  mutation?.assert();
68
- const handle = await fsModule.open(dest, admission === "restore" ? OPEN_READ_WRITE_FLAGS : OPEN_READ_FLAGS);
49
+ const file = yield* io.open(dest, admission === "restore" ? OPEN_READ_WRITE_FLAGS : OPEN_READ_FLAGS);
69
50
  try {
70
- if (fsModule === fs)
71
- inspectPinnedDestinationSync(syncFs, handle.fd, dest, admission, hardlinks);
72
- else {
73
- const opened = await inspectFileIdentity(() => handle.stat({ bigint: true }));
74
- await inspectFileIdentity(async () => admitDestinationKind(await fsModule.lstat(dest, { bigint: true }), opened, dest, admission), opened);
75
- assertDestinationLinks(opened, dest, admission, hardlinks);
51
+ const synchronous = io.asyncFs === fs;
52
+ const openedInspection = inspectAtomicIdentity(io, () => file.statExact(), undefined, synchronous);
53
+ const opened = (io.asynchronous && !synchronous ? (yield openedInspection) : openedInspection);
54
+ const currentInspection = inspectAtomicIdentity(io, () => io.lstatExact(dest), opened, synchronous, stat => admitDestinationKind(stat, opened, dest, admission));
55
+ if (io.asynchronous && !synchronous)
56
+ yield currentInspection;
57
+ if ((admission === "hardlinks" || hardlinks === "reject") && opened.nlink > 1n) {
58
+ throw new FsSafeError("hardlink", `Hardlinked ${admission === "hardlinks" ? "atomic replace" : "copy fallback"} destination not allowed: ${dest}`);
76
59
  }
77
- return handle;
60
+ return file;
78
61
  }
79
62
  catch (error) {
80
63
  try {
81
- await handle.close();
64
+ const closing = file.close();
65
+ if (io.asynchronous)
66
+ yield closing;
82
67
  }
83
68
  catch {
84
69
  // Preserve the already-selected admission failure.
@@ -86,72 +71,40 @@ async function openPinnedDestination(fsModule, dest, admission, hardlinks, mutat
86
71
  throw error;
87
72
  }
88
73
  }
89
- function openPinnedDestinationSync(fsModule, dest, admission, hardlinks, mutation) {
90
- let preview;
91
- try {
92
- preview = fsModule.lstatSync(dest);
93
- }
94
- catch (error) {
95
- if (hasErrorCode(error, "ENOENT"))
96
- return null;
97
- throw error;
98
- }
99
- if (admission === "hardlinks" && (preview.isSymbolicLink() || !preview.isFile()))
100
- return null;
101
- if (admission === "restore" && preview.isSymbolicLink()) {
102
- throw new FsSafeError("symlink", `Refusing copy fallback through symlink destination: ${dest}`);
103
- }
104
- mutation?.assert();
105
- const fd = fsModule.openSync(dest, admission === "restore" ? OPEN_READ_WRITE_FLAGS : OPEN_READ_FLAGS);
106
- try {
107
- inspectPinnedDestinationSync(fsModule, fd, dest, admission, hardlinks);
108
- return fd;
109
- }
110
- catch (error) {
111
- closeSyncAfterAdmissionFailure(fsModule, fd, error);
112
- }
113
- }
114
- export async function assertDestinationHardlinkPolicy(fsModule, dest, policy) {
74
+ export function* assertDestinationHardlinkPolicy(io, dest, policy) {
115
75
  if (policy !== "reject")
116
76
  return;
117
- const handle = await openPinnedDestination(fsModule, dest, "hardlinks");
118
- if (!handle)
77
+ const file = yield* openPinnedDestination(io, dest, "hardlinks");
78
+ if (!file)
119
79
  return;
120
80
  try {
121
- await handle.close();
81
+ const closing = file.close();
82
+ if (io.asynchronous)
83
+ yield closing;
122
84
  }
123
- catch {
124
- // The asynchronous admission pin has best-effort close semantics.
85
+ catch (error) {
86
+ // Only asynchronous admission pins have best-effort successful close.
87
+ if (!io.asynchronous)
88
+ throw error;
125
89
  }
126
90
  }
127
- export function assertDestinationHardlinkPolicySync(fsModule, dest, policy) {
128
- if (policy !== "reject")
129
- return;
130
- const fd = openPinnedDestinationSync(fsModule, dest, "hardlinks");
131
- if (fd !== null)
132
- fsModule.closeSync(fd);
133
- }
134
- function restoreReadOptions(stat, maxBytes) {
135
- return {
136
- initialSize: Number.isSafeInteger(stat.size) && stat.size >= 0 ? stat.size : undefined,
137
- createLimitError: () => new FsSafeError("too-large", `Atomic replace restore snapshot exceeds maxRestoreBytes (${maxBytes})`),
138
- };
139
- }
140
- async function readRestoreSnapshot(handle, maxBytes, stat) {
141
- let position = 0;
142
- return await readBoundedAsync(maxBytes, async (buffer, length) => {
143
- const { bytesRead } = await handle.read(buffer, 0, length, position);
144
- position += bytesRead;
145
- return bytesRead;
146
- }, restoreReadOptions(stat, maxBytes));
147
- }
148
- function readRestoreSnapshotSync(fsModule, fd, maxBytes, stat) {
91
+ function* readRestoreSnapshot(io, file, maxBytes, stat) {
149
92
  let position = 0;
150
- return readBoundedSync(maxBytes, (buffer, length) => {
151
- const bytesRead = fsModule.readSync(fd, buffer, 0, length, position);
93
+ function* read(buffer, length) {
94
+ const result = file.read(buffer, 0, length, position);
95
+ const bytesRead = io.asynchronous
96
+ ? (yield result).bytesRead
97
+ : result;
152
98
  position += bytesRead;
153
99
  return bytesRead;
154
- }, restoreReadOptions(stat, maxBytes));
100
+ }
101
+ const options = {
102
+ initialSize: Number.isSafeInteger(stat.size) && stat.size >= 0 ? stat.size : undefined,
103
+ createLimitError: () => new FsSafeError("too-large", `Atomic replace restore snapshot exceeds maxRestoreBytes (${maxBytes})`),
104
+ };
105
+ return io.asynchronous
106
+ ? yield* wait(readBoundedAsync(maxBytes, (buffer, length) => runAsync(read(buffer, length)), options))
107
+ : readBoundedSync(maxBytes, (buffer, length) => runSync(read(buffer, length)), options);
155
108
  }
156
109
  function restoreFailure(writeError, cleanup, restoreError) {
157
110
  const primary = writeError instanceof Error ? writeError : new Error(String(writeError));
@@ -163,25 +116,29 @@ function restoreFailure(writeError, cleanup, restoreError) {
163
116
  ? `Atomic copy fallback failed; original destination restored: ${primary.message}`
164
117
  : `Atomic copy fallback failed and original restoration failed: ${primary.message}`, { cause, details });
165
118
  }
166
- async function replacePinnedWithRestore(fsModule, handle, replacement, maxRestoreBytes, replacementMode, destination, mutation) {
167
- const originalStat = fsModule === fs ? syncFs.fstatSync(handle.fd) : await handle.stat();
119
+ function* replacePinnedWithRestore(io, file, replacement, maxRestoreBytes, replacementMode, destination, mutation) {
120
+ const originalStat = yield* file.stat();
168
121
  const originalMode = originalStat.mode;
169
- const original = await readRestoreSnapshot(handle, maxRestoreBytes, originalStat);
122
+ const original = yield* readRestoreSnapshot(io, file, maxRestoreBytes, originalStat);
170
123
  try {
171
- await writeAtomicDestination(handle, replacement, destination?.beforeWrite, destination?.assertBeforeMutation, destination?.writing);
124
+ yield* writeAtomicDestination(file, replacement, destination);
172
125
  if (destination)
173
- await destination.verify();
174
- await handle.chmod(replacementMode);
175
- await handle.sync();
126
+ yield* destination.verify();
127
+ const chmod = file.chmod(replacementMode);
128
+ if (io.asynchronous)
129
+ yield chmod;
130
+ yield* file.sync();
176
131
  }
177
132
  catch (writeError) {
178
133
  mutation.rethrowRefusal();
179
134
  try {
180
- await writeAtomicDestination(handle, original, destination?.beforeRestore, destination?.assertBeforeMutation, destination?.writing);
135
+ yield* writeAtomicDestination(file, original, destination, true);
181
136
  if (destination)
182
- await destination.verifyRestore();
183
- await handle.chmod(originalMode);
184
- await handle.sync();
137
+ yield* destination.verify(true);
138
+ const chmod = file.chmod(originalMode);
139
+ if (io.asynchronous)
140
+ yield chmod;
141
+ yield* file.sync();
185
142
  throw restoreFailure(writeError, "restored");
186
143
  }
187
144
  catch (restoreError) {
@@ -193,61 +150,31 @@ async function replacePinnedWithRestore(fsModule, handle, replacement, maxRestor
193
150
  }
194
151
  }
195
152
  }
196
- function replacePinnedWithRestoreSync(fsModule, fd, replacement, maxRestoreBytes, replacementMode, fchmodSync, destination, mutation) {
197
- const originalStat = fsModule.fstatSync(fd);
198
- const originalMode = originalStat.mode;
199
- const original = readRestoreSnapshotSync(fsModule, fd, maxRestoreBytes, originalStat);
200
- try {
201
- writeAtomicDestinationSync(fsModule, fd, replacement, destination?.beforeWrite, destination?.writing);
202
- destination?.verify();
203
- fchmodSync?.(fd, replacementMode);
204
- fsModule.fsyncSync(fd);
205
- }
206
- catch (writeError) {
207
- mutation?.rethrowRefusal();
208
- try {
209
- writeAtomicDestinationSync(fsModule, fd, original, destination?.beforeRestore, destination?.writing);
210
- destination?.verifyRestore();
211
- fchmodSync?.(fd, originalMode);
212
- fsModule.fsyncSync(fd);
213
- throw restoreFailure(writeError, "restored");
214
- }
215
- catch (restoreError) {
216
- mutation?.rethrowRefusal();
217
- if (restoreError instanceof FsSafeError && restoreError.details?.cleanup === "restored") {
218
- throw restoreError;
219
- }
220
- throw restoreFailure(writeError, "restore-failed", restoreError);
221
- }
222
- }
223
- }
224
- export async function copyFallbackReplace(params) {
153
+ export function* copyFallbackReplace(io, params) {
225
154
  const mutation = params.mutation ?? new AtomicMutation({});
226
- const source = await readOwnedCopySource({
227
- fsModule: params.fsModule,
155
+ const source = yield* readOwnedCopySource(io, {
228
156
  src: params.src,
229
157
  expectedIdentity: params.expectedSourceIdentity,
230
158
  });
231
159
  const { replacement } = source;
232
- let destHandle = null;
160
+ let file = null;
233
161
  let closeRequiredForSuccess = false;
234
162
  let completed = false;
235
163
  let destination;
236
164
  try {
237
165
  if (params.restore === "restore-original") {
238
- const pinned = await openPinnedDestination(params.fsModule, params.dest, "restore", params.destinationHardlinks, mutation);
239
- if (pinned) {
240
- destHandle = pinned;
241
- if (mutation.active)
242
- destination = await captureAtomicDestination(params.fsModule, pinned, params.dest, mutation, params.destinationHardlinks === "reject");
243
- await replacePinnedWithRestore(params.fsModule, destHandle, replacement, params.maxRestoreBytes, source.mode, destination, mutation);
166
+ file = yield* openPinnedDestination(io, params.dest, "restore", params.destinationHardlinks, mutation);
167
+ if (file) {
168
+ if (mutation.active) {
169
+ destination = yield* captureAtomicDestination(io, file, params.dest, mutation, params.destinationHardlinks === "reject");
170
+ }
171
+ yield* replacePinnedWithRestore(io, file, replacement, params.maxRestoreBytes, source.mode, destination, mutation);
244
172
  }
245
173
  }
246
- if (!destHandle) {
174
+ if (!file) {
247
175
  let destStat = null;
248
176
  try {
249
- destStat = params.fsModule === fs
250
- ? syncFs.lstatSync(params.dest) : await params.fsModule.lstat(params.dest);
177
+ destStat = yield* io.lstat(params.dest);
251
178
  }
252
179
  catch (error) {
253
180
  if (!hasErrorCode(error, "ENOENT"))
@@ -257,112 +184,47 @@ export async function copyFallbackReplace(params) {
257
184
  throw new FsSafeError("symlink", `Refusing copy fallback through symlink destination: ${params.dest}`);
258
185
  }
259
186
  if (destStat) {
260
- await assertDestinationHardlinkPolicy(params.fsModule, params.dest, params.destinationHardlinks);
187
+ yield* assertDestinationHardlinkPolicy(io, params.dest, params.destinationHardlinks);
261
188
  mutation.assert();
262
- await params.fsModule.rm(params.dest, { force: true });
189
+ yield* io.remove(params.dest);
263
190
  mutation.removed(params.dest);
264
191
  }
265
192
  mutation.assert();
266
- destHandle = await params.fsModule.open(params.dest, OPEN_WRITE_EXCLUSIVE_FLAGS, source.mode & 0o777);
193
+ file = yield* io.open(params.dest, OPEN_WRITE_EXCLUSIVE_FLAGS, source.mode & 0o777);
267
194
  if (mutation.active) {
268
- destination = await captureAtomicDestination(params.fsModule, destHandle, params.dest, mutation, params.destinationHardlinks === "reject");
195
+ destination = yield* captureAtomicDestination(io, file, params.dest, mutation, params.destinationHardlinks === "reject");
269
196
  destination.writing();
270
- await writeAtomicDestination(destHandle, replacement, destination.beforeWrite, destination.assertBeforeMutation);
271
- await destination.verify();
272
197
  }
273
- else {
274
- await destHandle.writeFile(replacement);
198
+ if (destination || !io.asynchronous) {
199
+ yield* writeAtomicDestination(file, replacement, destination);
200
+ if (destination)
201
+ yield* destination.verify();
275
202
  }
276
- await destHandle.chmod(source.mode);
277
- if (params.sync) {
278
- await destHandle.sync();
203
+ else {
204
+ yield file.writeFile(replacement);
279
205
  }
206
+ const chmod = file.chmod(source.mode);
207
+ if (io.asynchronous)
208
+ yield chmod;
209
+ if (params.sync)
210
+ yield* file.sync();
280
211
  closeRequiredForSuccess = !params.sync;
281
212
  }
282
213
  if (destination)
283
- await destination.verify();
284
- destination?.published();
285
- completed = true;
286
- }
287
- finally {
288
- if (destHandle) {
289
- try {
290
- await destHandle.close();
291
- }
292
- catch (closeError) {
293
- if (closeRequiredForSuccess && completed) {
294
- throw closeError;
295
- }
296
- }
297
- }
298
- }
299
- }
300
- export function copyFallbackReplaceSync(params) {
301
- const mutation = params.mutation ?? new AtomicMutation({});
302
- const source = readOwnedCopySourceSync({
303
- fsModule: params.fsModule,
304
- src: params.src,
305
- expectedIdentity: params.expectedSourceIdentity,
306
- });
307
- const { replacement } = source;
308
- let destFd;
309
- let closeRequiredForSuccess = false;
310
- let completed = false;
311
- let destination;
312
- try {
313
- if (params.restore === "restore-original") {
314
- const pinned = openPinnedDestinationSync(params.fsModule, params.dest, "restore", params.destinationHardlinks, mutation);
315
- if (pinned !== null) {
316
- destFd = pinned;
317
- if (mutation.active)
318
- destination = captureAtomicDestinationSync(params.fsModule, pinned, params.dest, mutation, params.destinationHardlinks === "reject");
319
- replacePinnedWithRestoreSync(params.fsModule, destFd, replacement, params.maxRestoreBytes, source.mode, params.fchmodSync, destination, mutation);
320
- }
321
- }
322
- if (destFd === undefined) {
323
- let destStat = null;
324
- try {
325
- destStat = params.fsModule.lstatSync(params.dest);
326
- }
327
- catch (error) {
328
- if (!hasErrorCode(error, "ENOENT"))
329
- throw error;
330
- }
331
- if (destStat?.isSymbolicLink()) {
332
- throw new FsSafeError("symlink", `Refusing copy fallback through symlink destination: ${params.dest}`);
333
- }
334
- if (destStat) {
335
- assertDestinationHardlinkPolicySync(params.fsModule, params.dest, params.destinationHardlinks);
336
- mutation.assert();
337
- params.fsModule.rmSync(params.dest, { force: true });
338
- mutation.removed(params.dest);
339
- }
340
- mutation.assert();
341
- destFd = params.fsModule.openSync(params.dest, OPEN_WRITE_EXCLUSIVE_FLAGS, source.mode & 0o777);
342
- if (mutation.active)
343
- destination = captureAtomicDestinationSync(params.fsModule, destFd, params.dest, mutation, params.destinationHardlinks === "reject");
344
- destination?.writing();
345
- writeAtomicDestinationSync(params.fsModule, destFd, replacement, destination?.beforeWrite);
346
- destination?.verify();
347
- params.fchmodSync?.(destFd, source.mode);
348
- if (params.sync) {
349
- params.fsModule.fsyncSync(destFd);
350
- }
351
- closeRequiredForSuccess = !params.sync;
352
- }
353
- destination?.verify();
214
+ yield* destination.verify();
354
215
  destination?.published();
355
216
  completed = true;
356
217
  }
357
218
  finally {
358
- if (destFd !== undefined) {
219
+ if (file) {
359
220
  try {
360
- params.fsModule.closeSync(destFd);
221
+ const closing = file.close();
222
+ if (io.asynchronous)
223
+ yield closing;
361
224
  }
362
225
  catch (closeError) {
363
- if (closeRequiredForSuccess && completed) {
226
+ if (closeRequiredForSuccess && completed)
364
227
  throw closeError;
365
- }
366
228
  }
367
229
  }
368
230
  }
@@ -1,20 +1,9 @@
1
- import syncFs, { type BigIntStats } from "node:fs";
2
- type AsyncSourceFileSystem = Pick<typeof import("node:fs/promises"), "lstat" | "open">;
3
- type SyncSourceFileSystem = Pick<typeof syncFs, "closeSync" | "fstatSync" | "lstatSync" | "openSync" | "readSync">;
4
- export declare function readOwnedCopySource(params: {
5
- fsModule: AsyncSourceFileSystem;
1
+ import type { BigIntStats } from "node:fs";
2
+ import { type AtomicIo, type Procedure } from "./atomic-io.js";
3
+ export declare function readOwnedCopySource(io: AtomicIo, params: {
6
4
  src: string;
7
5
  expectedIdentity?: BigIntStats;
8
- }): Promise<{
6
+ }): Procedure<{
9
7
  replacement: Buffer;
10
8
  mode: number;
11
9
  }>;
12
- export declare function readOwnedCopySourceSync(params: {
13
- fsModule: SyncSourceFileSystem;
14
- src: string;
15
- expectedIdentity?: BigIntStats;
16
- }): {
17
- replacement: Buffer;
18
- mode: number;
19
- };
20
- export {};
@@ -1,9 +1,6 @@
1
- import syncFs, {} from "node:fs";
2
- import fs, {} from "node:fs/promises";
3
- import { readBoundedSync } from "./bounded-read.js";
1
+ import { inspectAtomicIdentity } from "./atomic-io.js";
4
2
  import { resolveReadOpenFlags } from "./read-open-flags.js";
5
3
  import { FsSafeError } from "./errors.js";
6
- import { inspectFileIdentity, inspectFileIdentitySync } from "./strict-file-identity.js";
7
4
  import { hasErrorCode } from "./file-cleanup.js";
8
5
  const OPEN_READ_FLAGS = resolveReadOpenFlags();
9
6
  function assertSourcePreview(source, src) {
@@ -25,77 +22,39 @@ function assertOpenedSource(opened, current, src) {
25
22
  throw new FsSafeError("hardlink", `Hardlinked copy fallback source not allowed: ${src}`);
26
23
  }
27
24
  }
28
- function sourceSymlinkError(src, cause) {
29
- return new FsSafeError("symlink", `Refusing copy fallback from non-file source: ${src}`, {
30
- cause,
31
- });
32
- }
33
- async function openSource(fsModule, src) {
34
- try {
35
- return await fsModule.open(src, OPEN_READ_FLAGS);
36
- }
37
- catch (error) {
38
- if (hasErrorCode(error, "ELOOP")) {
39
- throw sourceSymlinkError(src, error);
40
- }
41
- throw error;
42
- }
43
- }
44
- function openSourceSync(fsModule, src) {
45
- try {
46
- return fsModule.openSync(src, OPEN_READ_FLAGS);
47
- }
48
- catch (error) {
49
- if (hasErrorCode(error, "ELOOP")) {
50
- throw sourceSymlinkError(src, error);
51
- }
52
- throw error;
53
- }
54
- }
55
- function readAllSync(fsModule, fd, size) {
56
- let position = 0;
57
- return readBoundedSync(Infinity, (buffer, length) => {
58
- const bytesRead = fsModule.readSync(fd, buffer, 0, length, position);
59
- position += bytesRead;
60
- return bytesRead;
61
- }, { initialSize: Number.isSafeInteger(size) && size >= 0 ? size : undefined });
62
- }
63
- export async function readOwnedCopySource(params) {
64
- assertSourcePreview(params.fsModule === fs
65
- ? syncFs.lstatSync(params.src) : await params.fsModule.lstat(params.src), params.src);
66
- const handle = await openSource(params.fsModule, params.src);
25
+ export function* readOwnedCopySource(io, params) {
26
+ assertSourcePreview(yield* io.lstat(params.src), params.src);
27
+ const handle = yield* openSource(io, params.src);
67
28
  try {
68
- const opened = await inspectFileIdentity(() => params.fsModule === fs
69
- ? syncFs.fstatSync(handle.fd, { bigint: true }) : handle.stat({ bigint: true }), params.expectedIdentity);
70
- const current = await inspectFileIdentity(() => params.fsModule === fs
71
- ? syncFs.lstatSync(params.src, { bigint: true }) : params.fsModule.lstat(params.src, { bigint: true }), opened);
29
+ const openedInspection = inspectAtomicIdentity(io, () => handle.statExact(), params.expectedIdentity);
30
+ const opened = (io.asynchronous ? (yield openedInspection) : openedInspection);
31
+ const currentInspection = inspectAtomicIdentity(io, () => io.lstatExact(params.src), opened);
32
+ const current = (io.asynchronous ? (yield currentInspection) : currentInspection);
72
33
  assertOpenedSource(opened, current, params.src);
73
- return { replacement: await handle.readFile(), mode: Number(opened.mode) };
34
+ const replacement = yield* handle.readFile(io.asynchronous ? undefined : Number(opened.size));
35
+ return { replacement, mode: Number(opened.mode) };
74
36
  }
75
37
  finally {
76
38
  try {
77
- await handle.close();
39
+ const closing = handle.close();
40
+ if (io.asynchronous)
41
+ yield closing;
78
42
  }
79
43
  catch {
80
- // Best-effort close includes synchronous adapter throws.
44
+ // Source close never replaces the selected read or admission result.
81
45
  }
82
46
  }
83
47
  }
84
- export function readOwnedCopySourceSync(params) {
85
- assertSourcePreview(params.fsModule.lstatSync(params.src), params.src);
86
- const fd = openSourceSync(params.fsModule, params.src);
48
+ function* openSource(io, src) {
87
49
  try {
88
- const opened = inspectFileIdentitySync(() => params.fsModule.fstatSync(fd, { bigint: true }), params.expectedIdentity);
89
- const current = inspectFileIdentitySync(() => params.fsModule.lstatSync(params.src, { bigint: true }), opened);
90
- assertOpenedSource(opened, current, params.src);
91
- return { replacement: readAllSync(params.fsModule, fd, Number(opened.size)), mode: Number(opened.mode) };
50
+ return yield* io.open(src, OPEN_READ_FLAGS);
92
51
  }
93
- finally {
94
- try {
95
- params.fsModule.closeSync(fd);
96
- }
97
- catch {
98
- // Best-effort close after reading a copy-fallback source.
52
+ catch (error) {
53
+ if (hasErrorCode(error, "ELOOP")) {
54
+ throw new FsSafeError("symlink", `Refusing copy fallback from non-file source: ${src}`, {
55
+ cause: error,
56
+ });
99
57
  }
58
+ throw error;
100
59
  }
101
60
  }
@@ -1,46 +1,21 @@
1
- import syncFs, { type BigIntStats } from "node:fs";
2
- import fs, { type FileHandle } from "node:fs/promises";
3
- import { type DirectoryModeOwner } from "./directory-mode-node.js";
1
+ import { type BigIntStats } from "node:fs";
4
2
  import type { AtomicMutation } from "./replace-file-mutation.js";
5
- type AsyncTempFileSystem = Pick<typeof fs, "lstat" | "open" | "writeFile">;
6
- type SyncTempFileSystem = Pick<typeof syncFs, "closeSync" | "fstatSync" | "fsyncSync" | "lstatSync" | "openSync" | "writeFileSync">;
7
- export type SyncFchmod = (fd: number, mode: number) => void;
8
- export declare function syncDirectoryBestEffort(fsModule: Pick<typeof fs, "open">, dirPath: string): Promise<void>;
9
- export declare function syncDirectoryBestEffortSync(fsModule: Pick<typeof syncFs, "openSync" | "fsyncSync" | "closeSync">, dirPath: string): void;
10
- export declare function pinDirectoryForMode(params: {
11
- fsModule: AsyncTempFileSystem;
12
- dirPath: string;
13
- /** Compatibility for best-effort directory modes; admission and close still fail closed. */
14
- ignoreChmodError?: boolean;
15
- mutation?: AtomicMutation;
16
- }): Promise<DirectoryModeOwner | undefined>;
17
- export declare function applyDirectoryMode(params: Parameters<typeof pinDirectoryForMode>[0] & {
18
- mode: number;
19
- }): Promise<void>;
20
- export declare function applyDirectoryModeSync(params: {
21
- fsModule: SyncTempFileSystem;
3
+ import { type AtomicFile, type AtomicIo, type Procedure } from "./atomic-io.js";
4
+ export declare function syncDirectoryBestEffort(io: AtomicIo, dirPath: string): Procedure<void>;
5
+ export declare function applyDirectoryMode(io: AtomicIo, params: {
22
6
  dirPath: string;
23
7
  mode: number;
24
- fchmodSync?: SyncFchmod;
8
+ ignoreChmodError?: boolean;
25
9
  mutation?: AtomicMutation;
26
- }): void;
27
- export declare function writeTempFile(params: {
28
- fsModule: AsyncTempFileSystem;
10
+ }): Procedure<void>;
11
+ export declare function writeTempFile(io: AtomicIo, params: {
29
12
  tempPath: string;
30
13
  content: string | Uint8Array;
31
14
  mode: number;
32
15
  sync: boolean;
33
16
  onIdentity?: (identity: BigIntStats) => void;
34
17
  mutation?: AtomicMutation;
35
- }): Promise<{
36
- handle: FileHandle;
18
+ }): Procedure<{
19
+ file: AtomicFile;
37
20
  identity: BigIntStats;
38
21
  }>;
39
- export declare function writeTempFileSync(params: Omit<Parameters<typeof writeTempFile>[0], "fsModule"> & {
40
- fsModule: SyncTempFileSystem;
41
- fchmodSync?: SyncFchmod;
42
- }): {
43
- fd: number;
44
- identity: BigIntStats;
45
- };
46
- export {};