@openclaw/fs-safe 0.4.7 → 0.5.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 (247) hide show
  1. package/CHANGELOG.md +58 -0
  2. package/README.md +82 -43
  3. package/dist/archive-entry.d.ts.map +1 -1
  4. package/dist/archive-entry.js +5 -4
  5. package/dist/archive-errors.d.ts +11 -0
  6. package/dist/archive-errors.d.ts.map +1 -0
  7. package/dist/archive-errors.js +16 -0
  8. package/dist/archive-input.d.ts +12 -0
  9. package/dist/archive-input.d.ts.map +1 -0
  10. package/dist/archive-input.js +72 -0
  11. package/dist/archive-kind.d.ts +1 -1
  12. package/dist/archive-kind.d.ts.map +1 -1
  13. package/dist/archive-kind.js +18 -0
  14. package/dist/archive-limits.d.ts +10 -0
  15. package/dist/archive-limits.d.ts.map +1 -1
  16. package/dist/archive-limits.js +18 -0
  17. package/dist/archive-native.d.ts +18 -0
  18. package/dist/archive-native.d.ts.map +1 -0
  19. package/dist/archive-native.js +115 -0
  20. package/dist/archive-options.d.ts +21 -0
  21. package/dist/archive-options.d.ts.map +1 -0
  22. package/dist/archive-options.js +1 -0
  23. package/dist/archive-policy.d.ts +20 -0
  24. package/dist/archive-policy.d.ts.map +1 -0
  25. package/dist/archive-policy.js +29 -0
  26. package/dist/archive-read.d.ts +6 -0
  27. package/dist/archive-read.d.ts.map +1 -0
  28. package/dist/archive-read.js +200 -0
  29. package/dist/archive-staging.d.ts +2 -5
  30. package/dist/archive-staging.d.ts.map +1 -1
  31. package/dist/archive-staging.js +19 -10
  32. package/dist/archive-tar-meta.d.ts +6 -0
  33. package/dist/archive-tar-meta.d.ts.map +1 -0
  34. package/dist/archive-tar-meta.js +143 -0
  35. package/dist/archive-tar-runtime.d.ts +43 -0
  36. package/dist/archive-tar-runtime.d.ts.map +1 -0
  37. package/dist/archive-tar-runtime.js +8 -0
  38. package/dist/archive-tar.d.ts +5 -1
  39. package/dist/archive-tar.d.ts.map +1 -1
  40. package/dist/archive-tar.js +24 -6
  41. package/dist/archive-zip-entry.d.ts +15 -0
  42. package/dist/archive-zip-entry.d.ts.map +1 -0
  43. package/dist/archive-zip-entry.js +17 -0
  44. package/dist/archive.d.ts +7 -17
  45. package/dist/archive.d.ts.map +1 -1
  46. package/dist/archive.js +111 -108
  47. package/dist/atomic.d.ts +1 -1
  48. package/dist/atomic.d.ts.map +1 -1
  49. package/dist/config.d.ts +1 -1
  50. package/dist/config.d.ts.map +1 -1
  51. package/dist/config.js +1 -1
  52. package/dist/containment.d.ts +2 -0
  53. package/dist/containment.d.ts.map +1 -0
  54. package/dist/containment.js +1 -0
  55. package/dist/device-path.d.ts.map +1 -1
  56. package/dist/device-path.js +24 -2
  57. package/dist/durability.d.ts +2 -0
  58. package/dist/durability.d.ts.map +1 -1
  59. package/dist/durability.js +2 -0
  60. package/dist/errors.d.ts +4 -1
  61. package/dist/errors.d.ts.map +1 -1
  62. package/dist/errors.js +2 -0
  63. package/dist/file-hash.d.ts +10 -0
  64. package/dist/file-hash.d.ts.map +1 -0
  65. package/dist/file-hash.js +73 -0
  66. package/dist/file-lock-sync.d.ts +35 -0
  67. package/dist/file-lock-sync.d.ts.map +1 -0
  68. package/dist/file-lock-sync.js +233 -0
  69. package/dist/file-lock.d.ts +2 -0
  70. package/dist/file-lock.d.ts.map +1 -1
  71. package/dist/file-lock.js +1 -0
  72. package/dist/filename.d.ts.map +1 -1
  73. package/dist/filename.js +4 -1
  74. package/dist/guarded-mkdir.d.ts +7 -1
  75. package/dist/guarded-mkdir.d.ts.map +1 -1
  76. package/dist/guarded-mkdir.js +45 -3
  77. package/dist/index.d.ts +4 -3
  78. package/dist/index.d.ts.map +1 -1
  79. package/dist/index.js +1 -1
  80. package/dist/json-document-store.d.ts.map +1 -1
  81. package/dist/json-document-store.js +30 -15
  82. package/dist/move-path.d.ts.map +1 -1
  83. package/dist/move-path.js +101 -17
  84. package/dist/native/darwin-arm64/fs-safe-native.node +0 -0
  85. package/dist/native/darwin-x64/fs-safe-native.node +0 -0
  86. package/dist/native/linux-arm64-gnu/fs-safe-native.node +0 -0
  87. package/dist/native/linux-arm64-musl/fs-safe-native.node +0 -0
  88. package/dist/native/linux-x64-gnu/fs-safe-native.node +0 -0
  89. package/dist/native/linux-x64-musl/fs-safe-native.node +0 -0
  90. package/dist/native/win32-x64-msvc/fs-safe-native.node +0 -0
  91. package/dist/native-binding.d.ts +79 -0
  92. package/dist/native-binding.d.ts.map +1 -0
  93. package/dist/native-binding.js +1 -0
  94. package/dist/native-config.d.ts +17 -0
  95. package/dist/native-config.d.ts.map +1 -0
  96. package/dist/native-config.js +71 -0
  97. package/dist/native-operations.d.ts +21 -0
  98. package/dist/native-operations.d.ts.map +1 -0
  99. package/dist/native-operations.js +115 -0
  100. package/dist/native-pinned-write.d.ts +5 -0
  101. package/dist/native-pinned-write.d.ts.map +1 -0
  102. package/dist/native-pinned-write.js +115 -0
  103. package/dist/native.d.ts +14 -0
  104. package/dist/native.d.ts.map +1 -0
  105. package/dist/native.js +204 -0
  106. package/dist/output-sibling.d.ts +8 -0
  107. package/dist/output-sibling.d.ts.map +1 -0
  108. package/dist/output-sibling.js +122 -0
  109. package/dist/output.d.ts +2 -0
  110. package/dist/output.d.ts.map +1 -1
  111. package/dist/output.js +32 -5
  112. package/dist/owner-dacl.d.ts +31 -0
  113. package/dist/owner-dacl.d.ts.map +1 -0
  114. package/dist/owner-dacl.js +27 -0
  115. package/dist/path-policy.js +1 -1
  116. package/dist/path.d.ts.map +1 -1
  117. package/dist/path.js +3 -2
  118. package/dist/permissions-public.d.ts +2 -0
  119. package/dist/permissions-public.d.ts.map +1 -1
  120. package/dist/permissions-public.js +2 -0
  121. package/dist/permissions.d.ts.map +1 -1
  122. package/dist/permissions.js +6 -0
  123. package/dist/pinned-operation.d.ts +2 -0
  124. package/dist/pinned-operation.d.ts.map +1 -0
  125. package/dist/pinned-operation.js +35 -0
  126. package/dist/pinned-write.d.ts +3 -16
  127. package/dist/pinned-write.d.ts.map +1 -1
  128. package/dist/pinned-write.js +20 -85
  129. package/dist/private-directory.d.ts +5 -0
  130. package/dist/private-directory.d.ts.map +1 -0
  131. package/dist/private-directory.js +13 -0
  132. package/dist/private-temp-workspace.d.ts +6 -2
  133. package/dist/private-temp-workspace.d.ts.map +1 -1
  134. package/dist/private-temp-workspace.js +40 -13
  135. package/dist/publish-file-failure.d.ts +27 -0
  136. package/dist/publish-file-failure.d.ts.map +1 -0
  137. package/dist/publish-file-failure.js +21 -0
  138. package/dist/publish-file.d.ts +21 -0
  139. package/dist/publish-file.d.ts.map +1 -0
  140. package/dist/publish-file.js +351 -0
  141. package/dist/read-opened-file.d.ts +3 -0
  142. package/dist/read-opened-file.d.ts.map +1 -1
  143. package/dist/read-opened-file.js +1 -0
  144. package/dist/replace-file-copy-fallback.d.ts +34 -0
  145. package/dist/replace-file-copy-fallback.d.ts.map +1 -0
  146. package/dist/replace-file-copy-fallback.js +339 -0
  147. package/dist/replace-file.d.ts +6 -2
  148. package/dist/replace-file.d.ts.map +1 -1
  149. package/dist/replace-file.js +39 -90
  150. package/dist/root-context.d.ts +4 -1
  151. package/dist/root-context.d.ts.map +1 -1
  152. package/dist/root-context.js +20 -1
  153. package/dist/root-impl.d.ts +5 -0
  154. package/dist/root-impl.d.ts.map +1 -1
  155. package/dist/root-impl.js +128 -225
  156. package/dist/root-path-existing.d.ts +3 -0
  157. package/dist/root-path-existing.d.ts.map +1 -0
  158. package/dist/root-path-existing.js +69 -0
  159. package/dist/root-path.d.ts +1 -2
  160. package/dist/root-path.d.ts.map +1 -1
  161. package/dist/root-path.js +67 -81
  162. package/dist/root-walk.d.ts +41 -0
  163. package/dist/root-walk.d.ts.map +1 -0
  164. package/dist/root-walk.js +114 -0
  165. package/dist/root.d.ts +2 -0
  166. package/dist/root.d.ts.map +1 -1
  167. package/dist/safe-path-segment.d.ts.map +1 -1
  168. package/dist/safe-path-segment.js +16 -4
  169. package/dist/secret-file.d.ts +5 -2
  170. package/dist/secret-file.d.ts.map +1 -1
  171. package/dist/secret-file.js +20 -2
  172. package/dist/secret-read-async.d.ts +4 -0
  173. package/dist/secret-read-async.d.ts.map +1 -0
  174. package/dist/secret-read-async.js +95 -0
  175. package/dist/secret.d.ts +2 -1
  176. package/dist/secret.d.ts.map +1 -1
  177. package/dist/secret.js +2 -1
  178. package/dist/sibling-temp.d.ts.map +1 -1
  179. package/dist/sibling-temp.js +1 -0
  180. package/dist/sidecar-lock-handle.d.ts +20 -0
  181. package/dist/sidecar-lock-handle.d.ts.map +1 -0
  182. package/dist/sidecar-lock-handle.js +28 -0
  183. package/dist/sidecar-lock-policy.d.ts +10 -0
  184. package/dist/sidecar-lock-policy.d.ts.map +1 -0
  185. package/dist/sidecar-lock-policy.js +29 -0
  186. package/dist/sidecar-lock-reclaim.d.ts +22 -6
  187. package/dist/sidecar-lock-reclaim.d.ts.map +1 -1
  188. package/dist/sidecar-lock-reclaim.js +106 -20
  189. package/dist/sidecar-lock-types.d.ts +56 -0
  190. package/dist/sidecar-lock-types.d.ts.map +1 -0
  191. package/dist/sidecar-lock-types.js +1 -0
  192. package/dist/sidecar-lock.d.ts +2 -45
  193. package/dist/sidecar-lock.d.ts.map +1 -1
  194. package/dist/sidecar-lock.js +94 -55
  195. package/dist/temp-cleanup.d.ts +7 -1
  196. package/dist/temp-cleanup.d.ts.map +1 -1
  197. package/dist/temp-cleanup.js +35 -5
  198. package/dist/temp-target.d.ts.map +1 -1
  199. package/dist/temp-target.js +51 -3
  200. package/dist/temp.d.ts +2 -1
  201. package/dist/temp.d.ts.map +1 -1
  202. package/dist/test-hooks.d.ts +3 -0
  203. package/dist/test-hooks.d.ts.map +1 -1
  204. package/dist/windows-permissions-native.d.ts +9 -0
  205. package/dist/windows-permissions-native.d.ts.map +1 -0
  206. package/dist/windows-permissions-native.js +31 -0
  207. package/docs/archive.md +131 -13
  208. package/docs/atomic.md +30 -1
  209. package/docs/config.md +51 -21
  210. package/docs/durability.md +211 -1
  211. package/docs/errors.md +36 -6
  212. package/docs/filename.md +0 -0
  213. package/docs/index.md +11 -8
  214. package/docs/install.md +27 -26
  215. package/docs/json-store.md +18 -3
  216. package/docs/migrating-to-0.5.md +196 -0
  217. package/docs/native-helper.md +85 -0
  218. package/docs/native.md +134 -0
  219. package/docs/output.md +37 -11
  220. package/docs/path.md +1 -1
  221. package/docs/permissions.md +84 -1
  222. package/docs/quickstart.md +5 -2
  223. package/docs/reading.md +4 -4
  224. package/docs/root.md +31 -15
  225. package/docs/secret-file.md +50 -1
  226. package/docs/security-model.md +29 -8
  227. package/docs/sidecar-lock.md +92 -7
  228. package/docs/temp.md +24 -1
  229. package/docs/test-hooks.md +22 -2
  230. package/docs/testing.md +18 -7
  231. package/docs/types.md +3 -1
  232. package/docs/walk.md +55 -0
  233. package/docs/writing.md +3 -3
  234. package/package.json +10 -4
  235. package/dist/pinned-helper.d.ts +0 -9
  236. package/dist/pinned-helper.d.ts.map +0 -1
  237. package/dist/pinned-helper.js +0 -19
  238. package/dist/pinned-path.d.ts +0 -7
  239. package/dist/pinned-path.d.ts.map +0 -1
  240. package/dist/pinned-path.js +0 -21
  241. package/dist/pinned-python-config.d.ts +0 -9
  242. package/dist/pinned-python-config.d.ts.map +0 -1
  243. package/dist/pinned-python-config.js +0 -38
  244. package/dist/pinned-python.d.ts +0 -12
  245. package/dist/pinned-python.d.ts.map +0 -1
  246. package/dist/pinned-python.js +0 -688
  247. package/docs/python-helper.md +0 -103
@@ -1,6 +1,6 @@
1
1
  import type { Readable } from "node:stream";
2
2
  import type { FileIdentityStat } from "./file-identity.js";
3
- type PinnedWriteInput = {
3
+ export type PinnedWriteInput = {
4
4
  kind: "buffer";
5
5
  data: string | Buffer;
6
6
  encoding?: BufferEncoding;
@@ -8,9 +8,9 @@ type PinnedWriteInput = {
8
8
  kind: "stream";
9
9
  stream: Readable;
10
10
  };
11
- type RenameIdentityMismatchPolicy = "throw" | "verify-content";
11
+ export type RenameIdentityMismatchPolicy = "throw" | "verify-content";
12
12
  export type RenameIdentityPolicy = "strict" | "verify-content-with-lock";
13
- type PinnedWriteParams = {
13
+ export type PinnedWriteParams = {
14
14
  rootPath: string;
15
15
  relativeParentPath: string;
16
16
  basename: string;
@@ -27,17 +27,4 @@ export declare function runPinnedWriteWithRenamePolicy(params: PinnedWriteParams
27
27
  targetPath: string;
28
28
  renameIdentity?: RenameIdentityPolicy;
29
29
  }): Promise<FileIdentityStat>;
30
- export declare function runPinnedCopyHelper(params: {
31
- rootPath: string;
32
- relativeParentPath: string;
33
- basename: string;
34
- mkdir: boolean;
35
- mode: number;
36
- overwrite?: boolean;
37
- maxBytes?: number;
38
- sourcePath: string;
39
- sourceIdentity: FileIdentityStat;
40
- rootIdentity?: FileIdentityStat;
41
- }): Promise<FileIdentityStat>;
42
- export {};
43
30
  //# sourceMappingURL=pinned-write.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"pinned-write.d.ts","sourceRoot":"","sources":["../src/pinned-write.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAI5C,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAa3D,KAAK,gBAAgB,GACjB;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,cAAc,CAAA;CAAE,GACpE;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,MAAM,EAAE,QAAQ,CAAA;CAAE,CAAC;AAuFzC,KAAK,4BAA4B,GAAG,OAAO,GAAG,gBAAgB,CAAC;AAE/D,MAAM,MAAM,oBAAoB,GAAG,QAAQ,GAAG,0BAA0B,CAAC;AAEzE,KAAK,iBAAiB,GAAG;IACvB,QAAQ,EAAE,MAAM,CAAC;IACjB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,OAAO,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,gBAAgB,CAAC;IACxB,YAAY,CAAC,EAAE,gBAAgB,CAAC;IAChC,wBAAwB,CAAC,EAAE,4BAA4B,CAAC;CACzD,CAAC;AAEF,wBAAsB,oBAAoB,CAAC,MAAM,EAAE,iBAAiB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAkD/F;AAED,wBAAsB,8BAA8B,CAClD,MAAM,EAAE,iBAAiB,GAAG;IAC1B,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,CAAC,EAAE,oBAAoB,CAAC;CACvC,GACA,OAAO,CAAC,gBAAgB,CAAC,CA2B3B;AAED,wBAAsB,mBAAmB,CAAC,MAAM,EAAE;IAChD,QAAQ,EAAE,MAAM,CAAC;IACjB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,OAAO,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,EAAE,gBAAgB,CAAC;IACjC,YAAY,CAAC,EAAE,gBAAgB,CAAC;CACjC,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAqB5B"}
1
+ {"version":3,"file":"pinned-write.d.ts","sourceRoot":"","sources":["../src/pinned-write.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAI5C,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAU3D,MAAM,MAAM,gBAAgB,GACxB;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,cAAc,CAAA;CAAE,GACpE;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,MAAM,EAAE,QAAQ,CAAA;CAAE,CAAC;AAgEzC,MAAM,MAAM,4BAA4B,GAAG,OAAO,GAAG,gBAAgB,CAAC;AAEtE,MAAM,MAAM,oBAAoB,GAAG,QAAQ,GAAG,0BAA0B,CAAC;AAEzE,MAAM,MAAM,iBAAiB,GAAG;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,OAAO,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,gBAAgB,CAAC;IACxB,YAAY,CAAC,EAAE,gBAAgB,CAAC;IAChC,wBAAwB,CAAC,EAAE,4BAA4B,CAAC;CACzD,CAAC;AAEF,wBAAsB,oBAAoB,CAAC,MAAM,EAAE,iBAAiB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAe/F;AAED,wBAAsB,8BAA8B,CAClD,MAAM,EAAE,iBAAiB,GAAG;IAC1B,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,CAAC,EAAE,oBAAoB,CAAC;CACvC,GACA,OAAO,CAAC,gBAAgB,CAAC,CA2B3B"}
@@ -8,8 +8,9 @@ import { syncDirectoryBestEffort } from "./fsync.js";
8
8
  import { sameFileIdentity, sha256Hex } from "./file-identity.js";
9
9
  import { withAsyncDirectoryGuards } from "./guarded-mutation.js";
10
10
  import { mkdirPathComponentsWithGuards } from "./guarded-mkdir.js";
11
- import { canFallbackFromPythonError, getFsSafePythonConfig } from "./pinned-python-config.js";
12
- import { assertPinnedPythonOperationAvailable, runPinnedPythonOperation, validatePinnedOperationPayload, } from "./pinned-python.js";
11
+ import { runPinnedWriteNative } from "./native-pinned-write.js";
12
+ import { getNativeBinding } from "./native.js";
13
+ import { validatePinnedOperationPayload } from "./pinned-operation.js";
13
14
  import { withSidecarLock } from "./sidecar-lock.js";
14
15
  import { getFsSafeTestHooks } from "./test-hooks.js";
15
16
  function byteLength(input, encoding) {
@@ -57,74 +58,21 @@ async function writeStreamToHandle(stream, handle, maxBytes) {
57
58
  }
58
59
  }
59
60
  }
60
- async function inputToBase64(input, maxBytes) {
61
- if (input.kind === "buffer") {
62
- assertWithinMaxBytes(byteLength(input.data, input.encoding), maxBytes);
63
- return (typeof input.data === "string"
64
- ? Buffer.from(input.data, input.encoding ?? "utf8")
65
- : input.data).toString("base64");
66
- }
67
- const chunks = [];
68
- let bytes = 0;
69
- for await (const chunk of input.stream) {
70
- const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
71
- bytes += buffer.byteLength;
72
- assertWithinMaxBytes(bytes, maxBytes);
73
- chunks.push(buffer);
74
- }
75
- return Buffer.concat(chunks, bytes).toString("base64");
76
- }
77
61
  export async function runPinnedWriteHelper(params) {
78
62
  assertSafeBasename(params.basename);
79
63
  validatePinnedOperationPayload({
80
64
  relativeParentPath: params.relativeParentPath,
81
65
  });
82
- // The Python helper deliberately enforces the strict post-rename inode
83
- // contract. The explicit compatibility policy therefore uses the guarded
84
- // Node fallback, where content verification can replace that one check.
66
+ // The explicit compatibility policy uses the guarded Node fallback, where
67
+ // content verification can replace the strict post-rename inode check.
85
68
  if (params.onRenameIdentityMismatch === "verify-content") {
86
69
  return await runPinnedWriteFallback(params);
87
70
  }
88
- if (getFsSafePythonConfig().mode === "off") {
89
- return await runPinnedWriteFallback(params);
90
- }
91
- if (params.input.kind === "stream") {
92
- try {
93
- assertPinnedPythonOperationAvailable();
94
- }
95
- catch (error) {
96
- if (canFallbackFromPythonError(error)) {
97
- return await runPinnedWriteFallback(params);
98
- }
99
- throw error;
100
- }
101
- }
102
- const input = params.input.kind === "stream"
103
- ? { kind: "buffer", data: Buffer.from(await inputToBase64(params.input, params.maxBytes), "base64") }
104
- : params.input;
105
- const payload = {
106
- base64: await inputToBase64(input, params.maxBytes),
107
- basename: params.basename,
108
- maxBytes: params.maxBytes ?? -1,
109
- mkdir: params.mkdir,
110
- mode: params.mode || 0o600,
111
- overwrite: params.overwrite !== false,
112
- relativeParentPath: params.relativeParentPath,
113
- ...(params.rootIdentity ? { rootDev: params.rootIdentity.dev, rootIno: params.rootIdentity.ino } : {}),
114
- };
115
- try {
116
- return await runPinnedPythonOperation({
117
- operation: "write",
118
- rootPath: params.rootPath,
119
- payload,
120
- });
121
- }
122
- catch (error) {
123
- if (canFallbackFromPythonError(error)) {
124
- return await runPinnedWriteFallback({ ...params, input });
125
- }
126
- throw error;
71
+ const native = getNativeBinding();
72
+ if (native && params.overwrite === false) {
73
+ return await runPinnedWriteNative(native, params);
127
74
  }
75
+ return await runPinnedWriteFallback(params);
128
76
  }
129
77
  export async function runPinnedWriteWithRenamePolicy(params) {
130
78
  const { targetPath, renameIdentity, ...writeParams } = params;
@@ -147,34 +95,19 @@ export async function runPinnedWriteWithRenamePolicy(params) {
147
95
  onRenameIdentityMismatch: "verify-content",
148
96
  }));
149
97
  }
150
- export async function runPinnedCopyHelper(params) {
151
- assertSafeBasename(params.basename);
152
- validatePinnedOperationPayload({
153
- relativeParentPath: params.relativeParentPath,
154
- });
155
- return await runPinnedPythonOperation({
156
- operation: "copy",
157
- rootPath: params.rootPath,
158
- payload: {
159
- basename: params.basename,
160
- maxBytes: params.maxBytes ?? -1,
161
- mkdir: params.mkdir,
162
- mode: params.mode || 0o600,
163
- overwrite: params.overwrite !== false,
164
- relativeParentPath: params.relativeParentPath,
165
- ...(params.rootIdentity ? { rootDev: params.rootIdentity.dev, rootIno: params.rootIdentity.ino } : {}),
166
- sourceDev: params.sourceIdentity.dev,
167
- sourceIno: params.sourceIdentity.ino,
168
- sourcePath: params.sourcePath,
169
- },
170
- });
171
- }
172
98
  async function runPinnedWriteFallback(params) {
173
- const parentPath = params.relativeParentPath
99
+ let parentPath = params.relativeParentPath
174
100
  ? path.join(params.rootPath, ...params.relativeParentPath.split("/"))
175
101
  : params.rootPath;
176
102
  if (params.mkdir) {
177
- await mkdirPathComponentsWithGuards({ rootReal: params.rootPath, targetPath: parentPath });
103
+ // mkdirPathComponentsWithGuards may resolve the final component through
104
+ // an in-root symlink (e.g. a skill-bank layout). Use its returned real
105
+ // path for the subsequent guard and target path so we don't re-check the
106
+ // original, possibly-symlinked, lexical path and reject it outright.
107
+ parentPath = await mkdirPathComponentsWithGuards({
108
+ rootReal: params.rootPath,
109
+ targetPath: parentPath,
110
+ });
178
111
  }
179
112
  const parentGuard = params.mkdir
180
113
  ? await createAsyncDirectoryGuard(parentPath)
@@ -190,6 +123,7 @@ async function runPinnedWriteFallback(params) {
190
123
  });
191
124
  let created = true;
192
125
  try {
126
+ await handle.chmod(params.mode);
193
127
  if (params.input.kind === "buffer") {
194
128
  assertWithinMaxBytes(byteLength(params.input.data, params.input.encoding), params.maxBytes);
195
129
  if (typeof params.input.data === "string") {
@@ -229,6 +163,7 @@ async function runPinnedWriteFallback(params) {
229
163
  let renamed = false;
230
164
  try {
231
165
  handle = await fs.open(tempPath, tempFlags, params.mode);
166
+ await handle.chmod(params.mode);
232
167
  if (params.input.kind === "buffer") {
233
168
  assertWithinMaxBytes(byteLength(params.input.data, params.input.encoding), params.maxBytes);
234
169
  if (typeof params.input.data === "string") {
@@ -0,0 +1,5 @@
1
+ export type CreatePrivateDirectoryOptions = {
2
+ platform?: NodeJS.Platform;
3
+ };
4
+ export declare function createPrivateDirectory(targetPath: string, options?: CreatePrivateDirectoryOptions): Promise<void>;
5
+ //# sourceMappingURL=private-directory.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"private-directory.d.ts","sourceRoot":"","sources":["../src/private-directory.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,6BAA6B,GAAG;IAC1C,QAAQ,CAAC,EAAE,MAAM,CAAC,QAAQ,CAAC;CAC5B,CAAC;AAEF,wBAAsB,sBAAsB,CAC1C,UAAU,EAAE,MAAM,EAClB,OAAO,CAAC,EAAE,6BAA6B,GACtC,OAAO,CAAC,IAAI,CAAC,CAiBf"}
@@ -0,0 +1,13 @@
1
+ import { FsSafeError } from "./errors.js";
2
+ import { getNativeBinding } from "./native.js";
3
+ export async function createPrivateDirectory(targetPath, options) {
4
+ const platform = options?.platform ?? process.platform;
5
+ if (platform !== "win32") {
6
+ throw new FsSafeError("helper-unavailable", "private-directory creation is available only on Windows through the bundled native binding");
7
+ }
8
+ const native = getNativeBinding();
9
+ if (!native) {
10
+ throw new FsSafeError("helper-unavailable", "private Windows directory creation requires the bundled native binding");
11
+ }
12
+ native.createPrivateDirectory(targetPath);
13
+ }
@@ -1,4 +1,6 @@
1
1
  import { type FileStore, type FileStoreSync } from "./file-store.js";
2
+ import { type TempPathIdentityReceipt } from "./temp-cleanup.js";
3
+ export type TempWorkspaceCleanupResult = "removed" | "missing" | "identity-mismatch";
2
4
  export type TempWorkspaceOptions = {
3
5
  rootDir: string;
4
6
  prefix: string;
@@ -7,6 +9,7 @@ export type TempWorkspaceOptions = {
7
9
  };
8
10
  export type TempWorkspace = {
9
11
  dir: string;
12
+ identity: TempPathIdentityReceipt;
10
13
  store: FileStore;
11
14
  path(fileName: string): string;
12
15
  write(fileName: string, data: string | Uint8Array): Promise<string>;
@@ -16,11 +19,12 @@ export type TempWorkspace = {
16
19
  }): Promise<string>;
17
20
  copyIn(fileName: string, sourcePath: string): Promise<string>;
18
21
  read(fileName: string): Promise<Buffer>;
19
- cleanup(): Promise<void>;
22
+ cleanup(): Promise<TempWorkspaceCleanupResult>;
20
23
  [Symbol.asyncDispose](): Promise<void>;
21
24
  };
22
25
  export type TempWorkspaceSync = {
23
26
  dir: string;
27
+ identity: TempPathIdentityReceipt;
24
28
  store: FileStoreSync;
25
29
  path(fileName: string): string;
26
30
  write(fileName: string, data: string | Uint8Array): string;
@@ -29,7 +33,7 @@ export type TempWorkspaceSync = {
29
33
  trailingNewline?: boolean;
30
34
  }): string;
31
35
  read(fileName: string): Buffer;
32
- cleanup(): void;
36
+ cleanup(): TempWorkspaceCleanupResult;
33
37
  [Symbol.dispose](): void;
34
38
  };
35
39
  export declare function tempWorkspace(options: TempWorkspaceOptions): Promise<TempWorkspace>;
@@ -1 +1 @@
1
- {"version":3,"file":"private-temp-workspace.d.ts","sourceRoot":"","sources":["../src/private-temp-workspace.ts"],"names":[],"mappings":"AAIA,OAAO,EAGL,KAAK,SAAS,EACd,KAAK,aAAa,EACnB,MAAM,iBAAiB,CAAC;AAIzB,MAAM,MAAM,oBAAoB,GAAG;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAC1B,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,SAAS,CAAC;IACjB,IAAI,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAAC;IAC/B,KAAK,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACpE,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAC3D,SAAS,CACP,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,OAAO,EACb,OAAO,CAAC,EAAE;QAAE,eAAe,CAAC,EAAE,OAAO,CAAA;KAAE,GACtC,OAAO,CAAC,MAAM,CAAC,CAAC;IACnB,MAAM,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAC9D,IAAI,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACxC,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACzB,CAAC,MAAM,CAAC,YAAY,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxC,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,aAAa,CAAC;IACrB,IAAI,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAAC;IAC/B,KAAK,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,UAAU,GAAG,MAAM,CAAC;IAC3D,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IAClD,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE;QAAE,eAAe,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,MAAM,CAAC;IAC5F,IAAI,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAAC;IAC/B,OAAO,IAAI,IAAI,CAAC;IAChB,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,IAAI,CAAC;CAC1B,CAAC;AAsGF,wBAAsB,aAAa,CACjC,OAAO,EAAE,oBAAoB,GAC5B,OAAO,CAAC,aAAa,CAAC,CAExB;AAED,wBAAsB,iBAAiB,CAAC,CAAC,EACvC,OAAO,EAAE,oBAAoB,EAC7B,GAAG,EAAE,CAAC,SAAS,EAAE,aAAa,KAAK,OAAO,CAAC,CAAC,CAAC,GAC5C,OAAO,CAAC,CAAC,CAAC,CAUZ;AAED,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,oBAAoB,GAC5B,iBAAiB,CAwEnB;AAED,wBAAgB,qBAAqB,CAAC,CAAC,EACrC,OAAO,EAAE,oBAAoB,EAC7B,GAAG,EAAE,CAAC,SAAS,EAAE,iBAAiB,KAAK,CAAC,GACvC,CAAC,CAUH"}
1
+ {"version":3,"file":"private-temp-workspace.d.ts","sourceRoot":"","sources":["../src/private-temp-workspace.ts"],"names":[],"mappings":"AAIA,OAAO,EAGL,KAAK,SAAS,EACd,KAAK,aAAa,EACnB,MAAM,iBAAiB,CAAC;AAGzB,OAAO,EAEL,KAAK,uBAAuB,EAC7B,MAAM,mBAAmB,CAAC;AAE3B,MAAM,MAAM,0BAA0B,GAAG,SAAS,GAAG,SAAS,GAAG,mBAAmB,CAAC;AAErF,MAAM,MAAM,oBAAoB,GAAG;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAC1B,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,uBAAuB,CAAC;IAClC,KAAK,EAAE,SAAS,CAAC;IACjB,IAAI,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAAC;IAC/B,KAAK,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACpE,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAC3D,SAAS,CACP,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,OAAO,EACb,OAAO,CAAC,EAAE;QAAE,eAAe,CAAC,EAAE,OAAO,CAAA;KAAE,GACtC,OAAO,CAAC,MAAM,CAAC,CAAC;IACnB,MAAM,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAC9D,IAAI,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACxC,OAAO,IAAI,OAAO,CAAC,0BAA0B,CAAC,CAAC;IAC/C,CAAC,MAAM,CAAC,YAAY,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxC,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,uBAAuB,CAAC;IAClC,KAAK,EAAE,aAAa,CAAC;IACrB,IAAI,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAAC;IAC/B,KAAK,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,UAAU,GAAG,MAAM,CAAC;IAC3D,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IAClD,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE;QAAE,eAAe,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,MAAM,CAAC;IAC5F,IAAI,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAAC;IAC/B,OAAO,IAAI,0BAA0B,CAAC;IACtC,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,IAAI,CAAC;CAC1B,CAAC;AA0IF,wBAAsB,aAAa,CACjC,OAAO,EAAE,oBAAoB,GAC5B,OAAO,CAAC,aAAa,CAAC,CAExB;AAED,wBAAsB,iBAAiB,CAAC,CAAC,EACvC,OAAO,EAAE,oBAAoB,EAC7B,GAAG,EAAE,CAAC,SAAS,EAAE,aAAa,KAAK,OAAO,CAAC,CAAC,CAAC,GAC5C,OAAO,CAAC,CAAC,CAAC,CAUZ;AAED,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,oBAAoB,GAC5B,iBAAiB,CAsEnB;AAED,wBAAgB,qBAAqB,CAAC,CAAC,EACrC,OAAO,EAAE,oBAAoB,EAC7B,GAAG,EAAE,CAAC,SAAS,EAAE,iBAAiB,KAAK,CAAC,GACvC,CAAC,CAUH"}
@@ -4,7 +4,8 @@ import fs from "node:fs/promises";
4
4
  import path from "node:path";
5
5
  import { fileStore, fileStoreSync, } from "./file-store.js";
6
6
  import { openRootFileSync } from "./root-file.js";
7
- import { registerTempPathForExit } from "./temp-cleanup.js";
7
+ import { sameFileIdentity } from "./file-identity.js";
8
+ import { registerTempPathForExit, } from "./temp-cleanup.js";
8
9
  function sanitizeTempPrefix(prefix) {
9
10
  const sanitized = prefix.trim().replace(/[^a-zA-Z0-9._-]/g, "-");
10
11
  if (!sanitized || sanitized === "." || sanitized === "..") {
@@ -49,6 +50,34 @@ function ensurePrivateDirectorySync(dir, mode) {
49
50
  // Best-effort on platforms that do not enforce POSIX modes.
50
51
  }
51
52
  }
53
+ async function cleanupWorkspace(dir, identity) {
54
+ let current;
55
+ try {
56
+ current = await fs.lstat(dir);
57
+ }
58
+ catch (error) {
59
+ return error.code === "ENOENT" ? "missing" : "identity-mismatch";
60
+ }
61
+ if (!sameFileIdentity(current, identity)) {
62
+ return "identity-mismatch";
63
+ }
64
+ await fs.rm(dir, { recursive: true, force: true });
65
+ return "removed";
66
+ }
67
+ function cleanupWorkspaceSync(dir, identity) {
68
+ let current;
69
+ try {
70
+ current = fsSync.lstatSync(dir);
71
+ }
72
+ catch (error) {
73
+ return error.code === "ENOENT" ? "missing" : "identity-mismatch";
74
+ }
75
+ if (!sameFileIdentity(current, identity)) {
76
+ return "identity-mismatch";
77
+ }
78
+ fsSync.rmSync(dir, { recursive: true, force: true });
79
+ return "removed";
80
+ }
52
81
  async function createTempWorkspace(options) {
53
82
  const dirMode = options.dirMode ?? 0o700;
54
83
  const mode = options.mode ?? 0o600;
@@ -56,15 +85,17 @@ async function createTempWorkspace(options) {
56
85
  const root = await fs.realpath(requestedRoot).catch(() => requestedRoot);
57
86
  await ensurePrivateDirectory(root, dirMode);
58
87
  const dir = await fs.mkdtemp(path.join(root, sanitizeTempPrefix(options.prefix)));
59
- const unregisterTempDir = registerTempPathForExit(dir, { recursive: true });
60
88
  await fs.chmod(dir, dirMode).catch(() => undefined);
61
89
  const stat = await fs.lstat(dir);
62
90
  if (stat.isSymbolicLink() || !stat.isDirectory()) {
63
91
  throw new Error(`Temp workspace must be a directory: ${dir}`);
64
92
  }
93
+ const identity = { dev: stat.dev, ino: stat.ino };
94
+ const unregisterTempDir = registerTempPathForExit(dir, { recursive: true, identity });
65
95
  const store = fileStore({ rootDir: dir, private: true, dirMode, mode });
66
96
  return {
67
97
  dir,
98
+ identity,
68
99
  store,
69
100
  path: (fileName) => resolveWorkspaceLeaf(dir, fileName),
70
101
  write: async (fileName, data) => await store.write(assertWorkspaceFileName(fileName), data, { mode }),
@@ -77,7 +108,7 @@ async function createTempWorkspace(options) {
77
108
  read: async (fileName) => await store.readBytes(assertWorkspaceFileName(fileName)),
78
109
  cleanup: async () => {
79
110
  try {
80
- await fs.rm(dir, { recursive: true, force: true }).catch(() => undefined);
111
+ return await cleanupWorkspace(dir, identity);
81
112
  }
82
113
  finally {
83
114
  unregisterTempDir();
@@ -85,7 +116,7 @@ async function createTempWorkspace(options) {
85
116
  },
86
117
  [Symbol.asyncDispose]: async () => {
87
118
  try {
88
- await fs.rm(dir, { recursive: true, force: true }).catch(() => undefined);
119
+ await cleanupWorkspace(dir, identity);
89
120
  }
90
121
  finally {
91
122
  unregisterTempDir();
@@ -121,7 +152,6 @@ export function tempWorkspaceSync(options) {
121
152
  }
122
153
  ensurePrivateDirectorySync(root, dirMode);
123
154
  const dir = fsSync.mkdtempSync(path.join(root, sanitizeTempPrefix(options.prefix)));
124
- const unregisterTempDir = registerTempPathForExit(dir, { recursive: true });
125
155
  try {
126
156
  fsSync.chmodSync(dir, dirMode);
127
157
  }
@@ -132,9 +162,12 @@ export function tempWorkspaceSync(options) {
132
162
  if (stat.isSymbolicLink() || !stat.isDirectory()) {
133
163
  throw new Error(`Temp workspace must be a directory: ${dir}`);
134
164
  }
165
+ const identity = { dev: stat.dev, ino: stat.ino };
166
+ const unregisterTempDir = registerTempPathForExit(dir, { recursive: true, identity });
135
167
  const store = fileStoreSync({ rootDir: dir, private: true, dirMode, mode });
136
168
  return {
137
169
  dir,
170
+ identity,
138
171
  store,
139
172
  path: (fileName) => resolveWorkspaceLeaf(dir, fileName),
140
173
  write: (fileName, data) => store.write(assertWorkspaceFileName(fileName), data, { mode }),
@@ -162,10 +195,7 @@ export function tempWorkspaceSync(options) {
162
195
  },
163
196
  cleanup: () => {
164
197
  try {
165
- fsSync.rmSync(dir, { recursive: true, force: true });
166
- }
167
- catch {
168
- // Best-effort cleanup.
198
+ return cleanupWorkspaceSync(dir, identity);
169
199
  }
170
200
  finally {
171
201
  unregisterTempDir();
@@ -173,10 +203,7 @@ export function tempWorkspaceSync(options) {
173
203
  },
174
204
  [Symbol.dispose]: () => {
175
205
  try {
176
- fsSync.rmSync(dir, { recursive: true, force: true });
177
- }
178
- catch {
179
- // Best-effort cleanup.
206
+ cleanupWorkspaceSync(dir, identity);
180
207
  }
181
208
  finally {
182
209
  unregisterTempDir();
@@ -0,0 +1,27 @@
1
+ import { FsSafeError } from "./errors.js";
2
+ import type { FileIdentityStat } from "./file-identity.js";
3
+ export type PublishFileExclusiveSyncFailurePolicy = "rollback" | "preserve";
4
+ export type PublishFileExclusiveDirectorySyncFailure = {
5
+ status: "failed";
6
+ code?: string;
7
+ };
8
+ export type PublishFileExclusiveFailurePhase = "copy-create" | "copy-verify" | "directory-sync" | "hardlink-create" | "hardlink-verify" | "rename-create" | "rename-verify";
9
+ export type PublishFileExclusiveCleanup = "removed" | "preserved" | "unknown";
10
+ export type PublishFileExclusiveFailureDetails = {
11
+ phase: PublishFileExclusiveFailurePhase;
12
+ targetCreated: boolean;
13
+ targetIdentity?: FileIdentityStat;
14
+ cleanup: PublishFileExclusiveCleanup;
15
+ directorySync?: PublishFileExclusiveDirectorySyncFailure;
16
+ };
17
+ export type PublishFailureState = {
18
+ phase: PublishFileExclusiveFailurePhase;
19
+ targetCreated: boolean;
20
+ targetIdentity?: FileIdentityStat;
21
+ preserveTarget: boolean;
22
+ directorySync?: PublishFileExclusiveDirectorySyncFailure;
23
+ };
24
+ export declare function rememberCreatedTarget(state: PublishFailureState, identity: FileIdentityStat, phase: PublishFileExclusiveFailurePhase): void;
25
+ export declare function publicationFailure(error: unknown, state: PublishFailureState, cleanup: PublishFileExclusiveCleanup): FsSafeError;
26
+ export declare function directorySyncFailure(error: unknown): PublishFileExclusiveDirectorySyncFailure;
27
+ //# sourceMappingURL=publish-file-failure.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"publish-file-failure.d.ts","sourceRoot":"","sources":["../src/publish-file-failure.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAE3D,MAAM,MAAM,qCAAqC,GAAG,UAAU,GAAG,UAAU,CAAC;AAC5E,MAAM,MAAM,wCAAwC,GAAG;IACrD,MAAM,EAAE,QAAQ,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,CAAC;AACF,MAAM,MAAM,gCAAgC,GACxC,aAAa,GACb,aAAa,GACb,gBAAgB,GAChB,iBAAiB,GACjB,iBAAiB,GACjB,eAAe,GACf,eAAe,CAAC;AACpB,MAAM,MAAM,2BAA2B,GAAG,SAAS,GAAG,WAAW,GAAG,SAAS,CAAC;AAC9E,MAAM,MAAM,kCAAkC,GAAG;IAC/C,KAAK,EAAE,gCAAgC,CAAC;IACxC,aAAa,EAAE,OAAO,CAAC;IACvB,cAAc,CAAC,EAAE,gBAAgB,CAAC;IAClC,OAAO,EAAE,2BAA2B,CAAC;IACrC,aAAa,CAAC,EAAE,wCAAwC,CAAC;CAC1D,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG;IAChC,KAAK,EAAE,gCAAgC,CAAC;IACxC,aAAa,EAAE,OAAO,CAAC;IACvB,cAAc,CAAC,EAAE,gBAAgB,CAAC;IAClC,cAAc,EAAE,OAAO,CAAC;IACxB,aAAa,CAAC,EAAE,wCAAwC,CAAC;CAC1D,CAAC;AAEF,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,mBAAmB,EAC1B,QAAQ,EAAE,gBAAgB,EAC1B,KAAK,EAAE,gCAAgC,GACtC,IAAI,CAIN;AAED,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,OAAO,EACd,KAAK,EAAE,mBAAmB,EAC1B,OAAO,EAAE,2BAA2B,GACnC,WAAW,CAcb;AAED,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,OAAO,GACb,wCAAwC,CAG1C"}
@@ -0,0 +1,21 @@
1
+ import { FsSafeError } from "./errors.js";
2
+ export function rememberCreatedTarget(state, identity, phase) {
3
+ state.targetCreated = true;
4
+ state.targetIdentity = { dev: identity.dev, ino: identity.ino };
5
+ state.phase = phase;
6
+ }
7
+ export function publicationFailure(error, state, cleanup) {
8
+ const cause = error instanceof Error ? error : new Error(String(error));
9
+ const details = {
10
+ phase: state.phase,
11
+ targetCreated: state.targetCreated,
12
+ ...(state.targetIdentity ? { targetIdentity: state.targetIdentity } : {}),
13
+ ...(state.directorySync ? { directorySync: state.directorySync } : {}),
14
+ cleanup,
15
+ };
16
+ return new FsSafeError(error instanceof FsSafeError ? error.code : "helper-failed", `exclusive file publication failed during ${state.phase}: ${cause.message}`, { cause, details });
17
+ }
18
+ export function directorySyncFailure(error) {
19
+ const code = error?.code;
20
+ return typeof code === "string" ? { status: "failed", code } : { status: "failed" };
21
+ }
@@ -0,0 +1,21 @@
1
+ import { type Stats } from "node:fs";
2
+ import { type DirectoryReceipt, type DirectorySyncOutcome } from "./directory-durability.js";
3
+ import { type FileIdentityStat } from "./file-identity.js";
4
+ import { type PublishFileExclusiveSyncFailurePolicy } from "./publish-file-failure.js";
5
+ export type { PublishFileExclusiveCleanup, PublishFileExclusiveDirectorySyncFailure, PublishFileExclusiveFailureDetails, PublishFileExclusiveFailurePhase, PublishFileExclusiveSyncFailurePolicy, } from "./publish-file-failure.js";
6
+ export type PublishFileExclusiveStrategy = "link-or-copy" | "link-required" | "rename-noreplace";
7
+ export type PublishFileExclusiveResult = {
8
+ method: "hardlink" | "exclusive-copy" | "rename-noreplace";
9
+ identity: Stats;
10
+ directorySync: DirectorySyncOutcome;
11
+ };
12
+ export declare function isHardlinkFallbackError(error: unknown): boolean;
13
+ export declare function publishFileExclusive(params: {
14
+ sourcePath: string;
15
+ targetPath: string;
16
+ expectedSourceIdentity?: FileIdentityStat;
17
+ strategy: PublishFileExclusiveStrategy;
18
+ onSyncFailure?: PublishFileExclusiveSyncFailurePolicy;
19
+ parentReceipt?: DirectoryReceipt;
20
+ }): Promise<PublishFileExclusiveResult>;
21
+ //# sourceMappingURL=publish-file.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"publish-file.d.ts","sourceRoot":"","sources":["../src/publish-file.ts"],"names":[],"mappings":"AACA,OAAe,EAAE,KAAK,KAAK,EAAE,MAAM,SAAS,CAAC;AAI7C,OAAO,EAEL,KAAK,gBAAgB,EACrB,KAAK,oBAAoB,EAC1B,MAAM,2BAA2B,CAAC;AAGnC,OAAO,EAAoB,KAAK,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAG7E,OAAO,EAML,KAAK,qCAAqC,EAC3C,MAAM,2BAA2B,CAAC;AAGnC,YAAY,EACV,2BAA2B,EAC3B,wCAAwC,EACxC,kCAAkC,EAClC,gCAAgC,EAChC,qCAAqC,GACtC,MAAM,2BAA2B,CAAC;AAEnC,MAAM,MAAM,4BAA4B,GACpC,cAAc,GACd,eAAe,GACf,kBAAkB,CAAC;AAEvB,MAAM,MAAM,0BAA0B,GAAG;IACvC,MAAM,EAAE,UAAU,GAAG,gBAAgB,GAAG,kBAAkB,CAAC;IAC3D,QAAQ,EAAE,KAAK,CAAC;IAChB,aAAa,EAAE,oBAAoB,CAAC;CACrC,CAAC;AAkBF,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAE/D;AAoND,wBAAsB,oBAAoB,CAAC,MAAM,EAAE;IACjD,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,sBAAsB,CAAC,EAAE,gBAAgB,CAAC;IAC1C,QAAQ,EAAE,4BAA4B,CAAC;IACvC,aAAa,CAAC,EAAE,qCAAqC,CAAC;IACtD,aAAa,CAAC,EAAE,gBAAgB,CAAC;CAClC,GAAG,OAAO,CAAC,0BAA0B,CAAC,CA8LtC"}