@openclaw/fs-safe 0.5.6 → 0.7.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 (289) hide show
  1. package/CHANGELOG.md +89 -0
  2. package/README.md +23 -10
  3. package/dist/advanced.d.ts +2 -1
  4. package/dist/advanced.d.ts.map +1 -1
  5. package/dist/advanced.js +1 -0
  6. package/dist/archive-crc32.d.ts +2 -0
  7. package/dist/archive-crc32.d.ts.map +1 -0
  8. package/dist/archive-crc32.js +14 -0
  9. package/dist/archive-deadline.d.ts +3 -0
  10. package/dist/archive-deadline.d.ts.map +1 -1
  11. package/dist/archive-deadline.js +44 -8
  12. package/dist/archive-entry.d.ts.map +1 -1
  13. package/dist/archive-entry.js +1 -0
  14. package/dist/archive-errors.d.ts +1 -0
  15. package/dist/archive-errors.d.ts.map +1 -1
  16. package/dist/archive-errors.js +3 -0
  17. package/dist/archive-input.d.ts.map +1 -1
  18. package/dist/archive-input.js +26 -16
  19. package/dist/archive-kind.js +2 -2
  20. package/dist/archive-limits.d.ts +11 -3
  21. package/dist/archive-limits.d.ts.map +1 -1
  22. package/dist/archive-limits.js +24 -0
  23. package/dist/archive-native.d.ts +3 -2
  24. package/dist/archive-native.d.ts.map +1 -1
  25. package/dist/archive-native.js +31 -9
  26. package/dist/archive-policy.d.ts +2 -0
  27. package/dist/archive-policy.d.ts.map +1 -1
  28. package/dist/archive-policy.js +9 -1
  29. package/dist/archive-read.d.ts.map +1 -1
  30. package/dist/archive-read.js +88 -41
  31. package/dist/archive-staging.d.ts +3 -0
  32. package/dist/archive-staging.d.ts.map +1 -1
  33. package/dist/archive-staging.js +91 -43
  34. package/dist/archive-tar-admission.d.ts +7 -0
  35. package/dist/archive-tar-admission.d.ts.map +1 -0
  36. package/dist/archive-tar-admission.js +43 -0
  37. package/dist/archive-tar-gnu.d.ts +2 -0
  38. package/dist/archive-tar-gnu.d.ts.map +1 -0
  39. package/dist/archive-tar-gnu.js +20 -0
  40. package/dist/archive-tar-header.d.ts +8 -0
  41. package/dist/archive-tar-header.d.ts.map +1 -0
  42. package/dist/archive-tar-header.js +47 -0
  43. package/dist/archive-tar-meta.d.ts +29 -1
  44. package/dist/archive-tar-meta.d.ts.map +1 -1
  45. package/dist/archive-tar-meta.js +135 -19
  46. package/dist/archive-tar-pax.d.ts +8 -0
  47. package/dist/archive-tar-pax.d.ts.map +1 -0
  48. package/dist/archive-tar-pax.js +100 -0
  49. package/dist/archive-tar-runtime.d.ts +4 -0
  50. package/dist/archive-tar-runtime.d.ts.map +1 -1
  51. package/dist/archive-tar-runtime.js +3 -0
  52. package/dist/archive-tar.d.ts.map +1 -1
  53. package/dist/archive-tar.js +8 -2
  54. package/dist/archive-zip-admission.d.ts +7 -0
  55. package/dist/archive-zip-admission.d.ts.map +1 -0
  56. package/dist/archive-zip-admission.js +60 -0
  57. package/dist/archive-zip-count.d.ts +2 -0
  58. package/dist/archive-zip-count.d.ts.map +1 -0
  59. package/dist/archive-zip-count.js +140 -0
  60. package/dist/archive-zip-directory.d.ts +8 -0
  61. package/dist/archive-zip-directory.d.ts.map +1 -0
  62. package/dist/archive-zip-directory.js +224 -0
  63. package/dist/archive-zip-integrity.d.ts.map +1 -1
  64. package/dist/archive-zip-integrity.js +1 -14
  65. package/dist/archive-zip-names.d.ts +12 -0
  66. package/dist/archive-zip-names.d.ts.map +1 -0
  67. package/dist/archive-zip-names.js +99 -0
  68. package/dist/archive-zip-preflight.d.ts +1 -1
  69. package/dist/archive-zip-preflight.d.ts.map +1 -1
  70. package/dist/archive-zip-preflight.js +8 -145
  71. package/dist/archive.d.ts.map +1 -1
  72. package/dist/archive.js +63 -42
  73. package/dist/atomic.d.ts +1 -1
  74. package/dist/atomic.d.ts.map +1 -1
  75. package/dist/bounded-read-stream.d.ts.map +1 -1
  76. package/dist/bounded-read-stream.js +2 -4
  77. package/dist/bounded-read.d.ts.map +1 -1
  78. package/dist/bounded-read.js +3 -10
  79. package/dist/byte-budget.d.ts +5 -0
  80. package/dist/byte-budget.d.ts.map +1 -0
  81. package/dist/byte-budget.js +9 -0
  82. package/dist/file-hash.d.ts.map +1 -1
  83. package/dist/file-hash.js +25 -19
  84. package/dist/file-lock-sync.d.ts.map +1 -1
  85. package/dist/file-lock-sync.js +26 -9
  86. package/dist/file-store-boundary.d.ts.map +1 -1
  87. package/dist/file-store-boundary.js +9 -5
  88. package/dist/file-store-limit.d.ts +2 -0
  89. package/dist/file-store-limit.d.ts.map +1 -0
  90. package/dist/file-store-limit.js +8 -0
  91. package/dist/file-store-sync-write.d.ts.map +1 -1
  92. package/dist/file-store-sync-write.js +38 -8
  93. package/dist/file-store.d.ts.map +1 -1
  94. package/dist/file-store.js +26 -26
  95. package/dist/json-durable-queue-directory.d.ts +2 -0
  96. package/dist/json-durable-queue-directory.d.ts.map +1 -0
  97. package/dist/json-durable-queue-directory.js +20 -0
  98. package/dist/json-durable-queue-ownership.d.ts +14 -0
  99. package/dist/json-durable-queue-ownership.d.ts.map +1 -0
  100. package/dist/json-durable-queue-ownership.js +168 -0
  101. package/dist/json-durable-queue-retirement.d.ts +9 -0
  102. package/dist/json-durable-queue-retirement.d.ts.map +1 -0
  103. package/dist/json-durable-queue-retirement.js +126 -0
  104. package/dist/json-durable-queue-transfer-lock.d.ts +2 -0
  105. package/dist/json-durable-queue-transfer-lock.d.ts.map +1 -0
  106. package/dist/json-durable-queue-transfer-lock.js +19 -0
  107. package/dist/json-durable-queue.d.ts +1 -0
  108. package/dist/json-durable-queue.d.ts.map +1 -1
  109. package/dist/json-durable-queue.js +90 -57
  110. package/dist/json.d.ts.map +1 -1
  111. package/dist/json.js +27 -8
  112. package/dist/local-roots.d.ts.map +1 -1
  113. package/dist/local-roots.js +4 -2
  114. package/dist/native-binding.d.ts +15 -3
  115. package/dist/native-binding.d.ts.map +1 -1
  116. package/dist/native-operations.d.ts +4 -1
  117. package/dist/native-operations.d.ts.map +1 -1
  118. package/dist/native-operations.js +22 -6
  119. package/dist/native-pinned-write-windows.d.ts +8 -0
  120. package/dist/native-pinned-write-windows.d.ts.map +1 -0
  121. package/dist/native-pinned-write-windows.js +92 -0
  122. package/dist/native-pinned-write.d.ts.map +1 -1
  123. package/dist/native-pinned-write.js +136 -127
  124. package/dist/native-staged-file.d.ts +24 -0
  125. package/dist/native-staged-file.d.ts.map +1 -0
  126. package/dist/native-staged-file.js +337 -0
  127. package/dist/native.d.ts.map +1 -1
  128. package/dist/native.js +4 -4
  129. package/dist/opened-realpath.d.ts +2 -0
  130. package/dist/opened-realpath.d.ts.map +1 -1
  131. package/dist/opened-realpath.js +12 -7
  132. package/dist/output-sibling.d.ts.map +1 -1
  133. package/dist/output-sibling.js +11 -110
  134. package/dist/output.d.ts.map +1 -1
  135. package/dist/output.js +4 -2
  136. package/dist/owner-dacl.d.ts.map +1 -1
  137. package/dist/owner-dacl.js +2 -1
  138. package/dist/permission-exec.d.ts +19 -0
  139. package/dist/permission-exec.d.ts.map +1 -1
  140. package/dist/permission-exec.js +57 -11
  141. package/dist/permissions-public.d.ts +1 -1
  142. package/dist/permissions-public.d.ts.map +1 -1
  143. package/dist/permissions-windows.d.ts +3 -0
  144. package/dist/permissions-windows.d.ts.map +1 -1
  145. package/dist/permissions-windows.js +16 -5
  146. package/dist/permissions.d.ts +5 -0
  147. package/dist/permissions.d.ts.map +1 -1
  148. package/dist/pinned-open.d.ts.map +1 -1
  149. package/dist/pinned-open.js +27 -48
  150. package/dist/pinned-write.d.ts +6 -0
  151. package/dist/pinned-write.d.ts.map +1 -1
  152. package/dist/pinned-write.js +26 -37
  153. package/dist/private-directory.d.ts.map +1 -1
  154. package/dist/private-directory.js +3 -2
  155. package/dist/private-temp-workspace.d.ts +3 -1
  156. package/dist/private-temp-workspace.d.ts.map +1 -1
  157. package/dist/private-temp-workspace.js +81 -56
  158. package/dist/publish-file.d.ts.map +1 -1
  159. package/dist/publish-file.js +2 -4
  160. package/dist/read-opened-file.d.ts.map +1 -1
  161. package/dist/read-opened-file.js +6 -4
  162. package/dist/regular-file.d.ts.map +1 -1
  163. package/dist/regular-file.js +136 -82
  164. package/dist/replace-file-copy-fallback.d.ts +3 -1
  165. package/dist/replace-file-copy-fallback.d.ts.map +1 -1
  166. package/dist/replace-file-copy-fallback.js +21 -36
  167. package/dist/replace-file-copy-source.d.ts +21 -0
  168. package/dist/replace-file-copy-source.d.ts.map +1 -0
  169. package/dist/replace-file-copy-source.js +112 -0
  170. package/dist/replace-file-descriptor.d.ts +13 -3
  171. package/dist/replace-file-descriptor.d.ts.map +1 -1
  172. package/dist/replace-file-descriptor.js +32 -7
  173. package/dist/replace-file-rename-policy.d.ts +7 -0
  174. package/dist/replace-file-rename-policy.d.ts.map +1 -0
  175. package/dist/replace-file-rename-policy.js +30 -0
  176. package/dist/replace-file-temp-owner.d.ts +46 -0
  177. package/dist/replace-file-temp-owner.d.ts.map +1 -0
  178. package/dist/replace-file-temp-owner.js +346 -0
  179. package/dist/replace-file.d.ts +6 -1
  180. package/dist/replace-file.d.ts.map +1 -1
  181. package/dist/replace-file.js +72 -58
  182. package/dist/root-impl.d.ts.map +1 -1
  183. package/dist/root-impl.js +118 -98
  184. package/dist/root-paths.d.ts +11 -14
  185. package/dist/root-paths.d.ts.map +1 -1
  186. package/dist/root-paths.js +36 -27
  187. package/dist/root-write-verification.d.ts +11 -0
  188. package/dist/root-write-verification.d.ts.map +1 -0
  189. package/dist/root-write-verification.js +91 -0
  190. package/dist/secret-file.d.ts +1 -6
  191. package/dist/secret-file.d.ts.map +1 -1
  192. package/dist/secret-file.js +49 -120
  193. package/dist/secret-read-async.d.ts +1 -1
  194. package/dist/secret-read-async.d.ts.map +1 -1
  195. package/dist/secret-read-async.js +51 -72
  196. package/dist/secret-read-policy.d.ts +13 -0
  197. package/dist/secret-read-policy.d.ts.map +1 -0
  198. package/dist/secret-read-policy.js +28 -0
  199. package/dist/secret.d.ts +2 -1
  200. package/dist/secret.d.ts.map +1 -1
  201. package/dist/secret.js +2 -1
  202. package/dist/secure-file.d.ts.map +1 -1
  203. package/dist/secure-file.js +42 -29
  204. package/dist/sibling-staged-file.d.ts +15 -0
  205. package/dist/sibling-staged-file.d.ts.map +1 -0
  206. package/dist/sibling-staged-file.js +148 -0
  207. package/dist/sibling-temp.d.ts +3 -0
  208. package/dist/sibling-temp.d.ts.map +1 -1
  209. package/dist/sibling-temp.js +30 -74
  210. package/dist/sidecar-lock-acquire.d.ts.map +1 -1
  211. package/dist/sidecar-lock-acquire.js +48 -27
  212. package/dist/sidecar-lock-handle.d.ts +6 -2
  213. package/dist/sidecar-lock-handle.d.ts.map +1 -1
  214. package/dist/sidecar-lock-handle.js +17 -3
  215. package/dist/sidecar-lock-policy.d.ts +2 -0
  216. package/dist/sidecar-lock-policy.d.ts.map +1 -1
  217. package/dist/sidecar-lock-policy.js +29 -0
  218. package/dist/sidecar-lock-reclaim.d.ts.map +1 -1
  219. package/dist/sidecar-lock-reclaim.js +29 -7
  220. package/dist/sidecar-lock.d.ts.map +1 -1
  221. package/dist/sidecar-lock.js +28 -16
  222. package/dist/staged-directory.d.ts +16 -0
  223. package/dist/staged-directory.d.ts.map +1 -0
  224. package/dist/staged-directory.js +60 -0
  225. package/dist/staged-file-types.d.ts +56 -0
  226. package/dist/staged-file-types.d.ts.map +1 -0
  227. package/dist/staged-file-types.js +1 -0
  228. package/dist/staged-file.d.ts +10 -0
  229. package/dist/staged-file.d.ts.map +1 -0
  230. package/dist/staged-file.js +15 -0
  231. package/dist/strict-file-identity.d.ts +6 -0
  232. package/dist/strict-file-identity.d.ts.map +1 -0
  233. package/dist/strict-file-identity.js +48 -0
  234. package/dist/suppressed-error.d.ts +6 -0
  235. package/dist/suppressed-error.d.ts.map +1 -0
  236. package/dist/suppressed-error.js +15 -0
  237. package/dist/temp-cleanup.d.ts +2 -0
  238. package/dist/temp-cleanup.d.ts.map +1 -1
  239. package/dist/temp-cleanup.js +25 -10
  240. package/dist/temp-workspace-owner.d.ts +23 -0
  241. package/dist/temp-workspace-owner.d.ts.map +1 -0
  242. package/dist/temp-workspace-owner.js +320 -0
  243. package/dist/temp.d.ts +1 -1
  244. package/dist/temp.d.ts.map +1 -1
  245. package/dist/test-hooks.d.ts +5 -0
  246. package/dist/test-hooks.d.ts.map +1 -1
  247. package/dist/windows-owner.d.ts +3 -0
  248. package/dist/windows-owner.d.ts.map +1 -1
  249. package/dist/windows-owner.js +10 -2
  250. package/docs/advanced.md +19 -2
  251. package/docs/archive.md +250 -35
  252. package/docs/atomic.md +11 -2
  253. package/docs/config.md +7 -0
  254. package/docs/contributing.md +45 -7
  255. package/docs/durability.md +18 -5
  256. package/docs/errors.md +16 -1
  257. package/docs/file-store.md +2 -0
  258. package/docs/index.md +3 -1
  259. package/docs/install.md +20 -8
  260. package/docs/json.md +8 -4
  261. package/docs/migrating-to-0.5.md +7 -7
  262. package/docs/migrating-to-0.6.md +43 -0
  263. package/docs/native-helper.md +30 -8
  264. package/docs/native.md +73 -17
  265. package/docs/output.md +10 -0
  266. package/docs/path-scope.md +28 -2
  267. package/docs/permissions.md +13 -2
  268. package/docs/public-api.md +6 -3
  269. package/docs/quickstart.md +1 -1
  270. package/docs/reading.md +1 -1
  271. package/docs/regular-file.md +9 -2
  272. package/docs/root.md +3 -1
  273. package/docs/secret-file.md +12 -0
  274. package/docs/secure-file.md +21 -3
  275. package/docs/security-model.md +33 -1
  276. package/docs/sidecar-lock.md +15 -1
  277. package/docs/staged-file.md +178 -0
  278. package/docs/store.md +13 -0
  279. package/docs/temp.md +128 -14
  280. package/docs/testing.md +1 -1
  281. package/docs/writing.md +19 -0
  282. package/package.json +16 -9
  283. package/dist/native/darwin-arm64/fs-safe-native.node +0 -0
  284. package/dist/native/darwin-x64/fs-safe-native.node +0 -0
  285. package/dist/native/linux-arm64-gnu/fs-safe-native.node +0 -0
  286. package/dist/native/linux-arm64-musl/fs-safe-native.node +0 -0
  287. package/dist/native/linux-x64-gnu/fs-safe-native.node +0 -0
  288. package/dist/native/linux-x64-musl/fs-safe-native.node +0 -0
  289. package/dist/native/win32-x64-msvc/fs-safe-native.node +0 -0
@@ -1,142 +1,151 @@
1
- import { randomUUID } from "node:crypto";
2
- import fsSync, {} from "node:fs";
1
+ var __addDisposableResource = (this && this.__addDisposableResource) || function (env, value, async) {
2
+ if (value !== null && value !== void 0) {
3
+ if (typeof value !== "object" && typeof value !== "function") throw new TypeError("Object expected.");
4
+ var dispose, inner;
5
+ if (async) {
6
+ if (!Symbol.asyncDispose) throw new TypeError("Symbol.asyncDispose is not defined.");
7
+ dispose = value[Symbol.asyncDispose];
8
+ }
9
+ if (dispose === void 0) {
10
+ if (!Symbol.dispose) throw new TypeError("Symbol.dispose is not defined.");
11
+ dispose = value[Symbol.dispose];
12
+ if (async) inner = dispose;
13
+ }
14
+ if (typeof dispose !== "function") throw new TypeError("Object not disposable.");
15
+ if (inner) dispose = function() { try { inner.call(this); } catch (e) { return Promise.reject(e); } };
16
+ env.stack.push({ value: value, dispose: dispose, async: async });
17
+ }
18
+ else if (async) {
19
+ env.stack.push({ async: true });
20
+ }
21
+ return value;
22
+ };
23
+ var __disposeResources = (this && this.__disposeResources) || (function (SuppressedError) {
24
+ return function (env) {
25
+ function fail(e) {
26
+ env.error = env.hasError ? new SuppressedError(e, env.error, "An error was suppressed during disposal.") : e;
27
+ env.hasError = true;
28
+ }
29
+ var r, s = 0;
30
+ function next() {
31
+ while (r = env.stack.pop()) {
32
+ try {
33
+ if (!r.async && s === 1) return s = 0, env.stack.push(r), Promise.resolve().then(next);
34
+ if (r.dispose) {
35
+ var result = r.dispose.call(r.value);
36
+ if (r.async) return s |= 2, Promise.resolve(result).then(next, function(e) { fail(e); return next(); });
37
+ }
38
+ else s |= 1;
39
+ }
40
+ catch (e) {
41
+ fail(e);
42
+ }
43
+ }
44
+ if (s === 1) return env.hasError ? Promise.reject(env.error) : Promise.resolve();
45
+ if (env.hasError) throw env.error;
46
+ }
47
+ return next();
48
+ };
49
+ })(typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
50
+ var e = new Error(message);
51
+ return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
52
+ });
53
+ import fsSync from "node:fs";
3
54
  import fs from "node:fs/promises";
4
55
  import path from "node:path";
5
56
  import { FsSafeError } from "./errors.js";
6
- import { removeNativeCreatedFileIfStillPinned, syncNativeFileBestEffort, writeNativeFd, } from "./native-operations.js";
7
- function assertWithinMaxBytes(bytes, maxBytes) {
8
- if (maxBytes !== undefined && bytes > maxBytes) {
9
- throw new FsSafeError("too-large", `file exceeds limit of ${maxBytes} bytes (got at least ${bytes})`);
10
- }
11
- }
12
- async function writeNativeInput(fd, input, maxBytes) {
13
- if (input.kind === "buffer") {
14
- const data = typeof input.data === "string"
15
- ? Buffer.from(input.data, input.encoding ?? "utf8")
16
- : Buffer.from(input.data);
17
- assertWithinMaxBytes(data.byteLength, maxBytes);
18
- writeNativeFd(fd, data);
19
- return;
20
- }
21
- let bytes = 0;
22
- for await (const chunk of input.stream) {
23
- const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
24
- bytes += buffer.byteLength;
25
- assertWithinMaxBytes(bytes, maxBytes);
26
- writeNativeFd(fd, buffer);
27
- }
28
- }
29
- function nativeOpenFlags(flags) {
30
- const closeOnExec = fsSync.constants.O_CLOEXEC;
31
- return (flags |
32
- (closeOnExec ?? 0) |
33
- (typeof fsSync.constants.O_NOFOLLOW === "number" ? fsSync.constants.O_NOFOLLOW : 0));
34
- }
35
- function sameNativeIdentity(left, right) {
36
- return left.dev === right.dev && left.ino === right.ino;
37
- }
57
+ import { runPinnedWriteWindows, sameNativeIdentity } from "./native-pinned-write-windows.js";
58
+ import { assertNativeStaging, writeNativeStage } from "./native-staged-file.js";
59
+ import { describeStagedDirectory, exactIdentityMatches } from "./staged-directory.js";
38
60
  export async function runPinnedWriteNative(binding, params) {
39
- const root = await fs.open(params.rootPath, fsSync.constants.O_RDONLY |
40
- (typeof fsSync.constants.O_DIRECTORY === "number" ? fsSync.constants.O_DIRECTORY : 0));
41
- let parentFd;
42
- let tempFd;
43
- let targetFd;
44
- let tempIdentity;
45
- let parentPath = params.rootPath;
46
- const tempName = `.${params.basename}.${randomUUID()}.native.tmp`;
47
- let renamed = false;
61
+ const env_1 = { stack: [], error: void 0, hasError: false };
48
62
  try {
49
- const rootIdentity = binding.fstatIdentity(root.fd);
50
- if (params.rootIdentity && !sameNativeIdentity(params.rootIdentity, rootIdentity)) {
51
- throw new FsSafeError("path-mismatch", "root path changed during native write");
52
- }
53
- if (params.mkdir) {
54
- binding.mkdirBeneath(root.fd, params.relativeParentPath, 0o777);
55
- }
56
- const parentFlags = fsSync.constants.O_RDONLY |
57
- (typeof fsSync.constants.O_DIRECTORY === "number" ? fsSync.constants.O_DIRECTORY : 0);
58
- parentFd = binding.openBeneath(root.fd, params.relativeParentPath, parentFlags).fd;
59
- parentPath = await fs.realpath(params.relativeParentPath
60
- ? path.join(params.rootPath, ...params.relativeParentPath.split("/"))
61
- : params.rootPath);
62
- const parentPathStat = await fs.lstat(parentPath);
63
- const parentIdentity = binding.fstatIdentity(parentFd);
64
- if (parentPathStat.isSymbolicLink() ||
65
- !sameNativeIdentity(parentPathStat, parentIdentity)) {
66
- throw new FsSafeError("path-mismatch", "native write parent changed during resolution");
63
+ const windows = process.platform === "win32";
64
+ if (!windows) {
65
+ assertNativeStaging(binding);
67
66
  }
68
- if (params.overwrite === false) {
69
- try {
70
- await fs.lstat(path.join(parentPath, params.basename));
71
- throw Object.assign(new Error("destination already exists"), { code: "EEXIST" });
67
+ const directoryFlags = fsSync.constants.O_RDONLY | (fsSync.constants.O_DIRECTORY ?? 0);
68
+ const root = await fs.open(params.rootPath, directoryFlags);
69
+ const posixRoot = __addDisposableResource(env_1, windows ? undefined : root, true);
70
+ let parentFd;
71
+ let windowsOwnsDirectories = false;
72
+ // Until stage construction takes ownership, even admission failures must close
73
+ // the raw POSIX parent. Disposal preserves both admission and close failures.
74
+ const parentGuard = __addDisposableResource(env_1, {
75
+ [Symbol.dispose]() {
76
+ if (!windows && parentFd !== undefined) {
77
+ fsSync.closeSync(parentFd);
78
+ }
79
+ },
80
+ }, false);
81
+ try {
82
+ let rootMatches;
83
+ if (windows) {
84
+ const identity = binding.fstatIdentity(root.fd);
85
+ rootMatches = !params.rootIdentity || sameNativeIdentity(params.rootIdentity, identity);
72
86
  }
73
- catch (error) {
74
- if (error.code !== "ENOENT") {
75
- throw error;
87
+ else {
88
+ const identity = fsSync.fstatSync(root.fd, { bigint: true });
89
+ rootMatches = !params.rootIdentity || exactIdentityMatches(params.rootIdentity, identity);
90
+ }
91
+ if (!rootMatches) {
92
+ throw new FsSafeError("path-mismatch", "root path changed during native write");
93
+ }
94
+ if (params.mkdir) {
95
+ binding.mkdirBeneath(root.fd, params.relativeParentPath, 0o777);
96
+ }
97
+ parentFd = binding.openBeneath(root.fd, params.relativeParentPath, directoryFlags).fd;
98
+ const parentPath = await fs.realpath(params.relativeParentPath
99
+ ? path.join(params.rootPath, ...params.relativeParentPath.split("/"))
100
+ : params.rootPath);
101
+ const directory = windows ? undefined : describeStagedDirectory(parentFd, parentPath);
102
+ const parentPathStat = await fs.lstat(parentPath);
103
+ if (windows) {
104
+ const parentIdentity = binding.fstatIdentity(parentFd);
105
+ if (parentPathStat.isSymbolicLink() || !sameNativeIdentity(parentPathStat, parentIdentity)) {
106
+ throw new FsSafeError("path-mismatch", "native write parent changed during resolution");
76
107
  }
77
108
  }
109
+ else if (parentPathStat.isSymbolicLink() || !exactIdentityMatches(parentPathStat, directory.identity)) {
110
+ throw new FsSafeError("path-mismatch", "native write parent changed during resolution");
111
+ }
112
+ const verificationGuard = { dir: parentPath, realPath: parentPath, stat: parentPathStat };
113
+ if (params.overwrite === false) {
114
+ try {
115
+ await fs.lstat(path.join(parentPath, params.basename));
116
+ throw Object.assign(new Error("destination already exists"), { code: "EEXIST" });
117
+ }
118
+ catch (error) {
119
+ if (error.code !== "ENOENT") {
120
+ throw error;
121
+ }
122
+ }
123
+ }
124
+ if (windows) {
125
+ // The Windows leaf owns both directories after admission.
126
+ windowsOwnsDirectories = true;
127
+ return await runPinnedWriteWindows(binding, params, root, parentFd, verificationGuard);
128
+ }
129
+ const ownedParent = parentFd;
130
+ parentFd = undefined;
131
+ return await writeNativeStage(binding, ownedParent, directory, params, verificationGuard);
78
132
  }
79
- tempFd = binding.openBeneath(parentFd, tempName, nativeOpenFlags(fsSync.constants.O_WRONLY | fsSync.constants.O_CREAT | fsSync.constants.O_EXCL)).fd;
80
- tempIdentity = fsSync.fstatSync(tempFd);
81
- // Creation is requested at 0600 in the binding, but a restrictive umask
82
- // can remove owner access. Keep the unpublished inode private and
83
- // reopenable until the published name has been identity-fenced.
84
- fsSync.fchmodSync(tempFd, 0o600);
85
- await writeNativeInput(tempFd, params.input, params.maxBytes);
86
- syncNativeFileBestEffort(tempFd);
87
- if (params.overwrite === false) {
88
- binding.renameNoReplace(parentFd, tempName, parentFd, params.basename);
89
- }
90
- else {
91
- binding.renameReplace(parentFd, tempName, parentFd, params.basename);
92
- }
93
- renamed = true;
94
- targetFd = binding.openBeneath(parentFd, params.basename, nativeOpenFlags(fsSync.constants.O_RDONLY)).fd;
95
- const targetIdentity = binding.fstatIdentity(targetFd);
96
- if (!targetIdentity.isFile || !sameNativeIdentity(tempIdentity, targetIdentity)) {
97
- throw new FsSafeError("path-mismatch", "native write target changed after rename");
98
- }
99
- // Native exclusive creation starts at 0600. Apply the requested mode only
100
- // after reopening and fencing the published name, both so mode 000 stays
101
- // verifiable and so broader modes are never exposed before that fence.
102
- try {
103
- fsSync.fchmodSync(targetFd, params.mode);
104
- syncNativeFileBestEffort(targetFd);
105
- }
106
- catch (error) {
107
- fsSync.closeSync(targetFd);
108
- targetFd = undefined;
109
- removeNativeCreatedFileIfStillPinned({
110
- binding,
111
- parentPath,
112
- parentFd,
113
- basename: params.basename,
114
- created: tempIdentity,
115
- });
116
- throw error;
133
+ finally {
134
+ if (windows && !windowsOwnsDirectories) {
135
+ if (parentFd !== undefined) {
136
+ fsSync.closeSync(parentFd);
137
+ }
138
+ await root.close().catch(() => undefined);
139
+ }
117
140
  }
118
- syncNativeFileBestEffort(parentFd);
119
- return { dev: targetIdentity.dev, ino: targetIdentity.ino };
141
+ }
142
+ catch (e_1) {
143
+ env_1.error = e_1;
144
+ env_1.hasError = true;
120
145
  }
121
146
  finally {
122
- if (targetFd !== undefined) {
123
- fsSync.closeSync(targetFd);
124
- }
125
- if (tempFd !== undefined) {
126
- fsSync.closeSync(tempFd);
127
- }
128
- if (!renamed && parentFd !== undefined) {
129
- removeNativeCreatedFileIfStillPinned({
130
- binding,
131
- parentPath,
132
- parentFd,
133
- basename: tempName,
134
- created: tempIdentity,
135
- });
136
- }
137
- if (parentFd !== undefined) {
138
- fsSync.closeSync(parentFd);
139
- }
140
- await root.close().catch(() => undefined);
147
+ const result_1 = __disposeResources(env_1);
148
+ if (result_1)
149
+ await result_1;
141
150
  }
142
151
  }
@@ -0,0 +1,24 @@
1
+ import type { AsyncDirectoryGuard } from "./directory-guard.js";
2
+ import type { FileIdentityStat } from "./file-identity.js";
3
+ import type { NativeBinding } from "./native-binding.js";
4
+ import type { PinnedWriteInput, PinnedWriteParams } from "./pinned-write.js";
5
+ import type { PublishedFileReceipt, StagedFile, StagedFileCleanupReceipt, StagedFileReceipt } from "./staged-file-types.js";
6
+ export type NativeStagingBinding = NativeBinding & Required<Pick<NativeBinding, "createStagedFile" | "stagedFileMatches" | "removeStagedFile">>;
7
+ export declare function assertNativeStaging(binding: NativeBinding): asserts binding is NativeStagingBinding;
8
+ declare class NativeStagedFile implements StagedFile {
9
+ #private;
10
+ constructor(binding: NativeStagingBinding, parentFd: number, directory: StagedFileReceipt["directory"], portableNames: boolean, publishedMode: number);
11
+ static create(binding: NativeStagingBinding, parentFd: number, directory: StagedFileReceipt["directory"], input: PinnedWriteInput, mode: number, maxBytes?: number, portableNames?: boolean): Promise<NativeStagedFile>;
12
+ static write(binding: NativeStagingBinding, parentFd: number, directory: StagedFileReceipt["directory"], params: PinnedWriteParams, parentGuard: AsyncDirectoryGuard): Promise<FileIdentityStat>;
13
+ get receipt(): StagedFileReceipt;
14
+ assertCurrent(): Promise<void>;
15
+ publish(basename: string, options: {
16
+ overwrite: boolean;
17
+ }): Promise<PublishedFileReceipt>;
18
+ cleanup(): Promise<StagedFileCleanupReceipt>;
19
+ [Symbol.asyncDispose](): Promise<void>;
20
+ }
21
+ export declare const createNativeStage: (...args: Parameters<typeof NativeStagedFile.create>) => Promise<StagedFile>;
22
+ export declare const writeNativeStage: typeof NativeStagedFile.write;
23
+ export {};
24
+ //# sourceMappingURL=native-staged-file.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"native-staged-file.d.ts","sourceRoot":"","sources":["../src/native-staged-file.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAEhE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAC3D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEzD,OAAO,KAAK,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAE7E,OAAO,KAAK,EACV,oBAAoB,EACpB,UAAU,EACV,wBAAwB,EAGxB,iBAAiB,EAClB,MAAM,wBAAwB,CAAC;AAEhC,MAAM,MAAM,oBAAoB,GAAG,aAAa,GAAG,QAAQ,CAAC,IAAI,CAC9D,aAAa,EACb,kBAAkB,GAAG,mBAAmB,GAAG,kBAAkB,CAC9D,CAAC,CAAC;AAEH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,OAAO,IAAI,oBAAoB,CAUnG;AA6BD,cAAM,gBAAiB,YAAW,UAAU;;IAU1C,YACE,OAAO,EAAE,oBAAoB,EAC7B,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,iBAAiB,CAAC,WAAW,CAAC,EACzC,aAAa,EAAE,OAAO,EACtB,aAAa,EAAE,MAAM,EAOtB;IAGD,OAAa,MAAM,CACjB,OAAO,EAAE,oBAAoB,EAC7B,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,iBAAiB,CAAC,WAAW,CAAC,EACzC,KAAK,EAAE,gBAAgB,EACvB,IAAI,EAAE,MAAM,EACZ,QAAQ,CAAC,EAAE,MAAM,EAEjB,aAAa,UAAO,GACnB,OAAO,CAAC,gBAAgB,CAAC,CAc3B;IAED,OAAa,KAAK,CAChB,OAAO,EAAE,oBAAoB,EAC7B,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,iBAAiB,CAAC,WAAW,CAAC,EACzC,MAAM,EAAE,iBAAiB,EACzB,WAAW,EAAE,mBAAmB,GAC/B,OAAO,CAAC,gBAAgB,CAAC,CAU3B;IAED,IAAI,OAAO,IAAI,iBAAiB,CAK/B;IAoFK,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC,CAEnC;IAEK,OAAO,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE;QAAE,SAAS,EAAE,OAAO,CAAA;KAAE,GAAG,OAAO,CAAC,oBAAoB,CAAC,CAgD9F;IA6CK,OAAO,IAAI,OAAO,CAAC,wBAAwB,CAAC,CAMjD;IAEK,CAAC,MAAM,CAAC,YAAY,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,CAO3C;CACF;AAED,eAAO,MAAM,iBAAiB,EAAE,CAAC,GAAG,IAAI,EAAE,UAAU,CAAC,OAAO,gBAAgB,CAAC,MAAM,CAAC,KAAK,OAAO,CAAC,UAAU,CAClF,CAAC;AAC1B,eAAO,MAAM,gBAAgB,+BAAyB,CAAC"}
@@ -0,0 +1,337 @@
1
+ var __addDisposableResource = (this && this.__addDisposableResource) || function (env, value, async) {
2
+ if (value !== null && value !== void 0) {
3
+ if (typeof value !== "object" && typeof value !== "function") throw new TypeError("Object expected.");
4
+ var dispose, inner;
5
+ if (async) {
6
+ if (!Symbol.asyncDispose) throw new TypeError("Symbol.asyncDispose is not defined.");
7
+ dispose = value[Symbol.asyncDispose];
8
+ }
9
+ if (dispose === void 0) {
10
+ if (!Symbol.dispose) throw new TypeError("Symbol.dispose is not defined.");
11
+ dispose = value[Symbol.dispose];
12
+ if (async) inner = dispose;
13
+ }
14
+ if (typeof dispose !== "function") throw new TypeError("Object not disposable.");
15
+ if (inner) dispose = function() { try { inner.call(this); } catch (e) { return Promise.reject(e); } };
16
+ env.stack.push({ value: value, dispose: dispose, async: async });
17
+ }
18
+ else if (async) {
19
+ env.stack.push({ async: true });
20
+ }
21
+ return value;
22
+ };
23
+ var __disposeResources = (this && this.__disposeResources) || (function (SuppressedError) {
24
+ return function (env) {
25
+ function fail(e) {
26
+ env.error = env.hasError ? new SuppressedError(e, env.error, "An error was suppressed during disposal.") : e;
27
+ env.hasError = true;
28
+ }
29
+ var r, s = 0;
30
+ function next() {
31
+ while (r = env.stack.pop()) {
32
+ try {
33
+ if (!r.async && s === 1) return s = 0, env.stack.push(r), Promise.resolve().then(next);
34
+ if (r.dispose) {
35
+ var result = r.dispose.call(r.value);
36
+ if (r.async) return s |= 2, Promise.resolve(result).then(next, function(e) { fail(e); return next(); });
37
+ }
38
+ else s |= 1;
39
+ }
40
+ catch (e) {
41
+ fail(e);
42
+ }
43
+ }
44
+ if (s === 1) return env.hasError ? Promise.reject(env.error) : Promise.resolve();
45
+ if (env.hasError) throw env.error;
46
+ }
47
+ return next();
48
+ };
49
+ })(typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
50
+ var e = new Error(message);
51
+ return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
52
+ });
53
+ import { randomUUID } from "node:crypto";
54
+ import fs from "node:fs";
55
+ import { FsSafeError } from "./errors.js";
56
+ import { syncNativeFileBestEffort, writeNativeInput } from "./native-operations.js";
57
+ import { assertStagedDirectoryCurrent } from "./staged-directory.js";
58
+ export function assertNativeStaging(binding) {
59
+ if ([
60
+ binding.createStagedFile,
61
+ binding.stagedFileMatches,
62
+ binding.removeStagedFile,
63
+ binding.renameReplace,
64
+ binding.renameNoReplace,
65
+ ].some((fn) => typeof fn !== "function")) {
66
+ throw new FsSafeError("helper-unavailable", "native retained-directory staging is unavailable");
67
+ }
68
+ }
69
+ function assertBasename(name, portable) {
70
+ if (!name || name === "." || name === ".." || name.includes("/") || name.includes("\0") ||
71
+ (portable && /[\\:\u0000-\u001f\u007f-\u009f\u2028\u2029]/u.test(name))) {
72
+ throw new FsSafeError("invalid-path", "publication requires one direct-child basename");
73
+ }
74
+ }
75
+ const NOT_PUBLISHED = Object.freeze({ status: "not-published" });
76
+ const UNCOMMITTED_RENAME_ERRORS = new Set([
77
+ "EACCES", "EBADF", "EBUSY", "EEXIST", "EINVAL", "EISDIR", "ELOOP", "EMLINK",
78
+ "ENAMETOOLONG", "ENOENT", "ENOSPC", "ENOSYS", "ENOTDIR", "ENOTEMPTY", "ENOTSUP",
79
+ "EPERM", "EROFS", "ETXTBSY", "EXDEV",
80
+ ]);
81
+ function failure(error, details) {
82
+ const code = error instanceof FsSafeError
83
+ ? error.code
84
+ : error?.code === "EEXIST" ? "already-exists" : "helper-failed";
85
+ return new FsSafeError(code, `staged file ${details.phase} failed`, { cause: error, details });
86
+ }
87
+ class NativeStagedFile {
88
+ #binding;
89
+ #parentFd;
90
+ #directory;
91
+ #portableNames;
92
+ #publishedMode;
93
+ #name = `.fs-safe-${randomUUID()}.tmp`;
94
+ #state = { status: "open", publication: NOT_PUBLISHED };
95
+ #receipt;
96
+ constructor(binding, parentFd, directory, portableNames, publishedMode) {
97
+ this.#binding = binding;
98
+ this.#parentFd = parentFd;
99
+ this.#directory = directory;
100
+ this.#portableNames = portableNames;
101
+ this.#publishedMode = publishedMode;
102
+ }
103
+ // Takes ownership of parentFd, including all construction and preparation failures.
104
+ static async create(binding, parentFd, directory, input, mode, maxBytes,
105
+ // Public staging uses portable names; existing POSIX writes accept literal names.
106
+ portableNames = true) {
107
+ let staged;
108
+ try {
109
+ staged = new NativeStagedFile(binding, parentFd, directory, portableNames, mode);
110
+ }
111
+ catch (error) {
112
+ try {
113
+ fs.closeSync(parentFd);
114
+ }
115
+ catch (closeError) {
116
+ throw new AggregateError([error, closeError], "staged owner construction and close failed");
117
+ }
118
+ throw error;
119
+ }
120
+ await staged.#prepare(input, maxBytes);
121
+ return staged;
122
+ }
123
+ static async write(binding, parentFd, directory, params, parentGuard) {
124
+ const env_1 = { stack: [], error: void 0, hasError: false };
125
+ try {
126
+ // This owner never escapes. Only the internal verifier borrows its fd;
127
+ // public descriptor methods remain await-free and cannot race disposal.
128
+ const staged = __addDisposableResource(env_1, await NativeStagedFile.create(binding, parentFd, directory, params.input, params.mode, params.maxBytes, false), true);
129
+ const published = await staged.publish(params.basename, { overwrite: params.overwrite !== false });
130
+ const identity = published.staged.identity;
131
+ await params.verifyPublished?.(staged.#file(), identity, parentGuard);
132
+ return { dev: identity.dev, ino: identity.ino };
133
+ }
134
+ catch (e_1) {
135
+ env_1.error = e_1;
136
+ env_1.hasError = true;
137
+ }
138
+ finally {
139
+ const result_1 = __disposeResources(env_1);
140
+ if (result_1)
141
+ await result_1;
142
+ }
143
+ }
144
+ get receipt() {
145
+ if (!this.#receipt) {
146
+ throw new FsSafeError("helper-failed", "staged file preparation is incomplete");
147
+ }
148
+ return this.#receipt;
149
+ }
150
+ #open() {
151
+ if (this.#state.status === "closed") {
152
+ throw new FsSafeError("helper-failed", "staged file is closed");
153
+ }
154
+ return this.#state;
155
+ }
156
+ #file() {
157
+ const state = this.#open();
158
+ if (state.fileFd === undefined) {
159
+ throw new FsSafeError("helper-failed", "staged file has not been created");
160
+ }
161
+ return state.fileFd;
162
+ }
163
+ #assertNamed(name) {
164
+ const fd = this.#file();
165
+ if (!this.#binding.stagedFileMatches(this.#parentFd, name, fd) ||
166
+ fs.fstatSync(fd, { bigint: true }).nlink !== 1n) {
167
+ throw new FsSafeError("path-mismatch", "staged entry no longer names the exclusive created file");
168
+ }
169
+ }
170
+ #assertCurrent() {
171
+ if (this.#open().publication.status !== "not-published") {
172
+ throw new FsSafeError("helper-failed", "staged file publication has already been attempted");
173
+ }
174
+ assertStagedDirectoryCurrent(this.#directory);
175
+ this.#assertNamed(this.#name);
176
+ }
177
+ async #prepare(input, maxBytes) {
178
+ try {
179
+ const mode = this.#publishedMode;
180
+ if (!Number.isInteger(mode) || mode < 0 || mode > 0o7777) {
181
+ throw new FsSafeError("invalid-path", "invalid staged file mode");
182
+ }
183
+ assertStagedDirectoryCurrent(this.#directory);
184
+ // The exclusive open performs no fallible post-open checks. Store its fd
185
+ // before every subsequent operation, including the first metadata read.
186
+ const state = this.#open();
187
+ state.fileFd = this.#binding.createStagedFile(this.#parentFd, this.#name);
188
+ const fd = state.fileFd;
189
+ fs.fchmodSync(fd, 0o600);
190
+ await writeNativeInput(fd, input, maxBytes);
191
+ syncNativeFileBestEffort(fd);
192
+ const stat = fs.fstatSync(fd, { bigint: true });
193
+ this.#receipt = Object.freeze({
194
+ directory: this.#directory,
195
+ temporaryBasename: this.#name,
196
+ identity: Object.freeze({
197
+ dev: stat.dev,
198
+ ino: stat.ino,
199
+ mode: Number(stat.mode & 4095n),
200
+ nlink: stat.nlink,
201
+ size: stat.size,
202
+ uid: Number(stat.uid),
203
+ gid: Number(stat.gid),
204
+ mtimeNs: stat.mtimeNs,
205
+ ctimeNs: stat.ctimeNs,
206
+ }),
207
+ });
208
+ this.#assertCurrent();
209
+ }
210
+ catch (error) {
211
+ const { receipt: cleanup, error: cleanupError } = this.#finalize();
212
+ if (cleanupError) {
213
+ throw failure(new AggregateError([error, cleanupError], "preparation and cleanup failed"), { phase: "prepare", publication: cleanup.publication, cleanup });
214
+ }
215
+ if (cleanup.status === "preserved") {
216
+ throw failure(error, { phase: "prepare", publication: cleanup.publication, cleanup });
217
+ }
218
+ throw error;
219
+ }
220
+ }
221
+ // Keep public descriptor work await-free: each call completes its mutations
222
+ // before the next invocation, including closure and cached cleanup failures.
223
+ async assertCurrent() {
224
+ this.#assertCurrent();
225
+ }
226
+ async publish(basename, options) {
227
+ const overwrite = options?.overwrite;
228
+ try {
229
+ const state = this.#open();
230
+ assertBasename(basename, this.#portableNames);
231
+ if (basename === this.#name || typeof overwrite !== "boolean") {
232
+ throw new FsSafeError("invalid-path", "publication needs a distinct basename and explicit overwrite policy");
233
+ }
234
+ this.#assertCurrent();
235
+ try {
236
+ if (overwrite) {
237
+ this.#binding.renameReplace(this.#parentFd, this.#name, this.#parentFd, basename);
238
+ }
239
+ else {
240
+ this.#binding.renameNoReplace(this.#parentFd, this.#name, this.#parentFd, basename);
241
+ }
242
+ }
243
+ catch (error) {
244
+ // Unknown rename errors can be indeterminate on remote filesystems.
245
+ if (!UNCOMMITTED_RENAME_ERRORS.has(error?.code ?? "")) {
246
+ state.publication = Object.freeze({ status: "indeterminate", basename, overwrite });
247
+ }
248
+ throw error;
249
+ }
250
+ // Commit is recorded synchronously before any post-rename operation.
251
+ const receipt = Object.freeze({
252
+ status: "published",
253
+ staged: this.receipt,
254
+ basename,
255
+ overwrite,
256
+ });
257
+ state.publication = receipt;
258
+ this.#assertNamed(basename);
259
+ assertStagedDirectoryCurrent(this.#directory);
260
+ // Keep contents private until the published name passes its identity
261
+ // fence. Mode changes use the owned fd, including for final mode 000.
262
+ const fd = this.#file();
263
+ fs.fchmodSync(fd, this.#publishedMode);
264
+ syncNativeFileBestEffort(fd);
265
+ syncNativeFileBestEffort(this.#parentFd);
266
+ this.#assertNamed(basename);
267
+ assertStagedDirectoryCurrent(this.#directory);
268
+ return receipt;
269
+ }
270
+ catch (error) {
271
+ // Closure rejects further use, not the recorded outcome of an earlier publication.
272
+ const publication = this.#state.status === "closed"
273
+ ? this.#state.receipt.publication
274
+ : this.#state.publication;
275
+ throw failure(error, { phase: "publish", publication });
276
+ }
277
+ }
278
+ #finalize() {
279
+ if (this.#state.status === "closed") {
280
+ return this.#state;
281
+ }
282
+ const state = this.#state;
283
+ let outcome = "not-needed";
284
+ const errors = [];
285
+ if (state.publication.status === "indeterminate") {
286
+ outcome = "preserved";
287
+ }
288
+ else if (state.publication.status === "not-published" && state.fileFd !== undefined) {
289
+ try {
290
+ outcome = this.#binding.removeStagedFile(this.#parentFd, this.#name, state.fileFd);
291
+ }
292
+ catch (error) {
293
+ outcome = "failed";
294
+ errors.push(error);
295
+ }
296
+ }
297
+ let resources = "closed";
298
+ for (const fd of [state.fileFd, this.#parentFd]) {
299
+ if (fd === undefined) {
300
+ continue;
301
+ }
302
+ try {
303
+ fs.closeSync(fd);
304
+ }
305
+ catch (error) {
306
+ resources = "close-failed";
307
+ errors.push(error);
308
+ }
309
+ }
310
+ const receipt = Object.freeze({
311
+ temporaryBasename: this.#name,
312
+ publication: state.publication,
313
+ status: outcome,
314
+ resources,
315
+ });
316
+ const error = errors.length ? failure(errors.length === 1 ? errors[0] : new AggregateError(errors, "staged cleanup failed"), { phase: "cleanup", publication: state.publication, cleanup: receipt }) : undefined;
317
+ this.#state = { status: "closed", receipt, error };
318
+ return this.#state;
319
+ }
320
+ async cleanup() {
321
+ const closed = this.#finalize();
322
+ if (closed.error) {
323
+ throw closed.error;
324
+ }
325
+ return closed.receipt;
326
+ }
327
+ async [Symbol.asyncDispose]() {
328
+ const cleanup = await this.cleanup();
329
+ if (cleanup.status === "preserved") {
330
+ throw new FsSafeError("not-removable", "staged cleanup preserved an unverified entry", {
331
+ details: { phase: "cleanup", publication: cleanup.publication, cleanup },
332
+ });
333
+ }
334
+ }
335
+ }
336
+ export const createNativeStage = NativeStagedFile.create;
337
+ export const writeNativeStage = NativeStagedFile.write;
@@ -1 +1 @@
1
- {"version":3,"file":"native.d.ts","sourceRoot":"","sources":["../src/native.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAGzD,YAAY,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAmKzD,wBAAgB,0BAA0B,IAAI,aAAa,CAE1D;AAED,wBAAgB,8BAA8B,IAAI;IAChD,MAAM,EAAE,OAAO,GAAG,SAAS,CAAC;IAC5B,UAAU,EAAE,OAAO,GAAG,SAAS,CAAC;IAChC,cAAc,EAAE,OAAO,GAAG,SAAS,CAAC;CACrC,CAMA;AAED,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,MAAM,CAAC,QAAQ,EACzB,IAAI,EAAE,MAAM,EACZ,IAAI,UAAQ,GACX,MAAM,GAAG,SAAS,CAEpB;AAED,wBAAgB,gBAAgB,IAAI,aAAa,GAAG,SAAS,CAkB5D;AAED,wBAAgB,oBAAoB,IAAI,aAAa,CAQpD;AAED,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,MAAM,aAAa,GAAG,IAAI,CAK1E;AAED,wBAAgB,0BAA0B,IAAI,IAAI,CAKjD"}
1
+ {"version":3,"file":"native.d.ts","sourceRoot":"","sources":["../src/native.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAGzD,YAAY,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAsKzD,wBAAgB,0BAA0B,IAAI,aAAa,CAE1D;AAED,wBAAgB,8BAA8B,IAAI;IAChD,MAAM,EAAE,OAAO,GAAG,SAAS,CAAC;IAC5B,UAAU,EAAE,OAAO,GAAG,SAAS,CAAC;IAChC,cAAc,EAAE,OAAO,GAAG,SAAS,CAAC;CACrC,CAMA;AAED,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,MAAM,CAAC,QAAQ,EACzB,IAAI,EAAE,MAAM,EACZ,IAAI,UAAQ,GACX,MAAM,GAAG,SAAS,CAEpB;AAED,wBAAgB,gBAAgB,IAAI,aAAa,GAAG,SAAS,CAkB5D;AAED,wBAAgB,oBAAoB,IAAI,aAAa,CAQpD;AAED,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,MAAM,aAAa,GAAG,IAAI,CAK1E;AAED,wBAAgB,0BAA0B,IAAI,IAAI,CAKjD"}