@openclaw/fs-safe 0.15.0 → 0.16.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 (184) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/README.md +28 -6
  3. package/dist/advanced.d.ts +1 -0
  4. package/dist/advanced.d.ts.map +1 -1
  5. package/dist/advanced.js +1 -0
  6. package/dist/archive-kind.d.ts +0 -1
  7. package/dist/archive-kind.d.ts.map +1 -1
  8. package/dist/archive-kind.js +5 -17
  9. package/dist/archive-parser.wasm +0 -0
  10. package/dist/archive-read.d.ts.map +1 -1
  11. package/dist/archive-read.js +6 -7
  12. package/dist/archive-tar-stream.d.ts +3 -0
  13. package/dist/archive-tar-stream.d.ts.map +1 -1
  14. package/dist/archive-tar-stream.js +56 -37
  15. package/dist/archive-tar-wasm.d.ts +16 -4
  16. package/dist/archive-tar-wasm.d.ts.map +1 -1
  17. package/dist/archive-tar-wasm.js +134 -34
  18. package/dist/archive.d.ts.map +1 -1
  19. package/dist/archive.js +5 -6
  20. package/dist/clone-metadata.d.ts +1 -0
  21. package/dist/clone-metadata.d.ts.map +1 -1
  22. package/dist/clone-metadata.js +6 -2
  23. package/dist/create-directory.d.ts +20 -0
  24. package/dist/create-directory.d.ts.map +1 -0
  25. package/dist/create-directory.js +130 -0
  26. package/dist/create-file-async.d.ts +7 -0
  27. package/dist/create-file-async.d.ts.map +1 -0
  28. package/dist/create-file-async.js +121 -0
  29. package/dist/create-file.d.ts +8 -0
  30. package/dist/create-file.d.ts.map +1 -0
  31. package/dist/create-file.js +190 -0
  32. package/dist/create-owned-file.d.ts +8 -0
  33. package/dist/create-owned-file.d.ts.map +1 -0
  34. package/dist/create-owned-file.js +16 -0
  35. package/dist/create.d.ts +4 -0
  36. package/dist/create.d.ts.map +1 -0
  37. package/dist/create.js +2 -0
  38. package/dist/creation-darwin.d.ts +7 -0
  39. package/dist/creation-darwin.d.ts.map +1 -0
  40. package/dist/creation-darwin.js +79 -0
  41. package/dist/creation-file-state.d.ts +19 -0
  42. package/dist/creation-file-state.d.ts.map +1 -0
  43. package/dist/creation-file-state.js +118 -0
  44. package/dist/creation-path.d.ts +21 -0
  45. package/dist/creation-path.d.ts.map +1 -0
  46. package/dist/creation-path.js +71 -0
  47. package/dist/creation-permissions.d.ts +19 -0
  48. package/dist/creation-permissions.d.ts.map +1 -0
  49. package/dist/creation-permissions.js +125 -0
  50. package/dist/directory-durability.d.ts +1 -1
  51. package/dist/directory-durability.d.ts.map +1 -1
  52. package/dist/directory-durability.js +22 -80
  53. package/dist/directory-guard.d.ts +3 -0
  54. package/dist/directory-guard.d.ts.map +1 -1
  55. package/dist/directory-mode-node.d.ts +2 -0
  56. package/dist/directory-mode-node.d.ts.map +1 -1
  57. package/dist/directory-mode-node.js +8 -0
  58. package/dist/directory-receipt.d.ts +24 -0
  59. package/dist/directory-receipt.d.ts.map +1 -0
  60. package/dist/directory-receipt.js +127 -0
  61. package/dist/file-cleanup.d.ts +19 -0
  62. package/dist/file-cleanup.d.ts.map +1 -0
  63. package/dist/file-cleanup.js +78 -0
  64. package/dist/file-observation.d.ts +1 -1
  65. package/dist/file-observation.d.ts.map +1 -1
  66. package/dist/file-store-boundary.d.ts +2 -6
  67. package/dist/file-store-boundary.d.ts.map +1 -1
  68. package/dist/file-store-boundary.js +3 -9
  69. package/dist/file-store-sync-write.d.ts.map +1 -1
  70. package/dist/file-store-sync-write.js +2 -5
  71. package/dist/guarded-mkdir.d.ts +1 -0
  72. package/dist/guarded-mkdir.d.ts.map +1 -1
  73. package/dist/guarded-mkdir.js +36 -7
  74. package/dist/move-path.js +1 -1
  75. package/dist/native-binding.d.ts +11 -1
  76. package/dist/native-binding.d.ts.map +1 -1
  77. package/dist/native-fallback-warning.d.ts +4 -0
  78. package/dist/native-fallback-warning.d.ts.map +1 -0
  79. package/dist/native-fallback-warning.js +11 -0
  80. package/dist/native-operations.d.ts +0 -2
  81. package/dist/native-operations.d.ts.map +1 -1
  82. package/dist/native-operations.js +0 -24
  83. package/dist/native-parent-admission.d.ts +2 -0
  84. package/dist/native-parent-admission.d.ts.map +1 -1
  85. package/dist/native-parent-admission.js +3 -2
  86. package/dist/native-pinned-write-windows.d.ts +1 -1
  87. package/dist/native-pinned-write-windows.d.ts.map +1 -1
  88. package/dist/native-pinned-write-windows.js +173 -28
  89. package/dist/native-pinned-write.d.ts.map +1 -1
  90. package/dist/native-pinned-write.js +19 -3
  91. package/dist/native-policy-parent-windows.d.ts.map +1 -1
  92. package/dist/native-policy-parent-windows.js +15 -6
  93. package/dist/native-staged-file.d.ts +3 -2
  94. package/dist/native-staged-file.d.ts.map +1 -1
  95. package/dist/native-staged-file.js +86 -39
  96. package/dist/owner-dacl.d.ts.map +1 -1
  97. package/dist/owner-dacl.js +10 -4
  98. package/dist/pinned-write-input.d.ts +4 -0
  99. package/dist/pinned-write-input.d.ts.map +1 -0
  100. package/dist/pinned-write-input.js +25 -0
  101. package/dist/pinned-write-mode.d.ts +5 -0
  102. package/dist/pinned-write-mode.d.ts.map +1 -0
  103. package/dist/pinned-write-mode.js +24 -0
  104. package/dist/pinned-write-staged.d.ts +6 -0
  105. package/dist/pinned-write-staged.d.ts.map +1 -0
  106. package/dist/pinned-write-staged.js +187 -0
  107. package/dist/pinned-write-types.d.ts +3 -0
  108. package/dist/pinned-write-types.d.ts.map +1 -1
  109. package/dist/pinned-write.d.ts.map +1 -1
  110. package/dist/pinned-write.js +35 -145
  111. package/dist/private-directory.d.ts.map +1 -1
  112. package/dist/private-directory.js +18 -4
  113. package/dist/private-producer-handoff-sync.d.ts +14 -0
  114. package/dist/private-producer-handoff-sync.d.ts.map +1 -0
  115. package/dist/private-producer-handoff-sync.js +114 -0
  116. package/dist/private-producer-handoff.d.ts +22 -4
  117. package/dist/private-producer-handoff.d.ts.map +1 -1
  118. package/dist/private-producer-handoff.js +140 -77
  119. package/dist/publish-copy-stage.d.ts +2 -1
  120. package/dist/publish-copy-stage.d.ts.map +1 -1
  121. package/dist/publish-copy-stage.js +16 -7
  122. package/dist/publish-file.d.ts.map +1 -1
  123. package/dist/publish-file.js +2 -2
  124. package/dist/replace-file-temp-owner.d.ts +0 -7
  125. package/dist/replace-file-temp-owner.d.ts.map +1 -1
  126. package/dist/replace-file-temp-owner.js +3 -54
  127. package/dist/root-create-input.d.ts +2 -1
  128. package/dist/root-create-input.d.ts.map +1 -1
  129. package/dist/root-create-input.js +13 -4
  130. package/dist/root-directory-creation.d.ts +3 -3
  131. package/dist/root-directory-creation.d.ts.map +1 -1
  132. package/dist/root-directory-creation.js +15 -3
  133. package/dist/root-impl.d.ts.map +1 -1
  134. package/dist/root-impl.js +28 -7
  135. package/dist/root-move-noreplace.d.ts.map +1 -1
  136. package/dist/root-move-noreplace.js +22 -13
  137. package/dist/root-options.d.ts +12 -4
  138. package/dist/root-options.d.ts.map +1 -1
  139. package/dist/root-path-stat.d.ts.map +1 -1
  140. package/dist/root-path-stat.js +59 -7
  141. package/dist/root-write-publication.js +1 -1
  142. package/dist/secret-file.d.ts.map +1 -1
  143. package/dist/secret-file.js +1 -0
  144. package/dist/secure-file-windows.d.ts +6 -0
  145. package/dist/secure-file-windows.d.ts.map +1 -1
  146. package/dist/secure-file-windows.js +34 -117
  147. package/dist/secure-file.js +2 -2
  148. package/dist/sidecar-lock-root.d.ts.map +1 -1
  149. package/dist/sidecar-lock-root.js +2 -1
  150. package/dist/staged-directory.d.ts.map +1 -1
  151. package/dist/staged-directory.js +6 -6
  152. package/dist/staged-file-settlement.d.ts +17 -0
  153. package/dist/staged-file-settlement.d.ts.map +1 -0
  154. package/dist/staged-file-settlement.js +57 -0
  155. package/dist/windows-owner.d.ts.map +1 -1
  156. package/dist/windows-owner.js +2 -1
  157. package/dist/windows-security-bridge.cs +336 -0
  158. package/dist/windows-security-bridge.ps1 +15 -0
  159. package/dist/windows-security-command.d.ts +26 -0
  160. package/dist/windows-security-command.d.ts.map +1 -0
  161. package/dist/windows-security-command.js +363 -0
  162. package/dist/windows-security-facts.d.ts +6 -0
  163. package/dist/windows-security-facts.d.ts.map +1 -0
  164. package/dist/windows-security-facts.js +108 -0
  165. package/docs/advanced.md +3 -1
  166. package/docs/archive.md +61 -37
  167. package/docs/config.md +6 -2
  168. package/docs/contributing.md +44 -4
  169. package/docs/copy.md +2 -0
  170. package/docs/creation.md +128 -0
  171. package/docs/durability.md +24 -0
  172. package/docs/install.md +31 -7
  173. package/docs/migrating-to-0.5.md +15 -6
  174. package/docs/migrating-to-0.6.md +9 -4
  175. package/docs/native-helper.md +22 -9
  176. package/docs/native.md +38 -7
  177. package/docs/permissions.md +37 -14
  178. package/docs/root.md +34 -0
  179. package/docs/secret-file.md +11 -2
  180. package/docs/secure-file.md +9 -4
  181. package/docs/sidecar-lock.md +5 -4
  182. package/docs/staged-file.md +5 -0
  183. package/docs/writing.md +71 -5
  184. package/package.json +18 -15
@@ -2,13 +2,14 @@ import fsSync, {} from "node:fs";
2
2
  import fs, {} from "node:fs/promises";
3
3
  import { FsSafeError } from "./errors.js";
4
4
  import { sameFileIdentityForCleanup } from "./file-identity.js";
5
+ import { assertSynchronousCallbackResult } from "./mutation-authority.js";
5
6
  import { resolveReadOpenFlags } from "./read-open-flags.js";
6
7
  import { inspectFileIdentitySync } from "./strict-file-identity.js";
7
8
  import { registerTempPathForExit, } from "./temp-cleanup.js";
8
9
  function pathMismatch(message) {
9
10
  return new FsSafeError("path-mismatch", message);
10
11
  }
11
- function assertInitialSource(stat) {
12
+ export function assertInitialSource(stat) {
12
13
  if (stat.isSymbolicLink()) {
13
14
  throw new FsSafeError("path-alias", "isolated producer output must not be a symlink");
14
15
  }
@@ -19,7 +20,7 @@ function assertInitialSource(stat) {
19
20
  throw new FsSafeError("hardlink", "isolated producer output must have exactly one link");
20
21
  }
21
22
  }
22
- function inspectLinkedFile(inspect, expected, links, label) {
23
+ export function inspectLinkedFile(inspect, expected, links, label) {
23
24
  return inspectFileIdentitySync(() => {
24
25
  const stat = inspect();
25
26
  if (stat.isSymbolicLink() || !stat.isFile() || stat.nlink !== links) {
@@ -28,11 +29,11 @@ function inspectLinkedFile(inspect, expected, links, label) {
28
29
  return stat;
29
30
  }, expected);
30
31
  }
31
- function assertParents(params) {
32
+ export function assertParents(params) {
32
33
  params.assertTargetParent();
33
34
  params.assertSourceParent();
34
35
  }
35
- function normalizeLinkError(error) {
36
+ export function normalizeLinkError(error) {
36
37
  const code = error?.code;
37
38
  if (code === "EEXIST") {
38
39
  return new FsSafeError("already-exists", "isolated producer sibling already exists", {
@@ -101,7 +102,7 @@ async function throwAfterClosing(primary, handles) {
101
102
  throw combinedFailure(primary, settlement);
102
103
  }
103
104
  function inspectSourceDescriptor(handle, identity, links = 1n) {
104
- inspectLinkedFile(() => fsSync.fstatSync(handle.fd, { bigint: true }), identity, links, "isolated producer descriptor");
105
+ return inspectLinkedFile(() => fsSync.fstatSync(handle.fd, { bigint: true }), identity, links, "isolated producer descriptor");
105
106
  }
106
107
  function historicalOpenFlags(readWrite) {
107
108
  const access = readWrite ? fsSync.constants.O_RDWR : fsSync.constants.O_RDONLY;
@@ -164,109 +165,171 @@ async function openProducerSource(params, identity, links = 1n) {
164
165
  }
165
166
  return fallback;
166
167
  }
167
- function assertLinkedHandoff(params, handle, identity) {
168
+ function inspectHandoff(params, handle, identity, state, links) {
168
169
  assertParents(params);
169
- inspectSourceDescriptor(handle, identity, 2n);
170
- inspectLinkedFile(() => fsSync.lstatSync(params.sourcePath, { bigint: true }), identity, 2n, "isolated producer path");
171
- inspectLinkedFile(() => fsSync.lstatSync(params.targetPath, { bigint: true }), identity, 2n, "isolated producer sibling");
170
+ const opened = inspectSourceDescriptor(handle, identity, links);
171
+ if (state.cleanup !== "removed") {
172
+ inspectLinkedFile(() => fsSync.lstatSync(params.sourcePath, { bigint: true }), identity, links, "isolated producer path");
173
+ }
174
+ const named = state.publication === "published" ? inspectLinkedFile(() => fsSync.lstatSync(params.targetPath, { bigint: true }), identity, links, "isolated producer sibling") : undefined;
175
+ return { opened, named };
172
176
  }
173
- export async function handoffPrivateProducerFile(params) {
174
- assertParents(params);
175
- const identity = inspectFileIdentitySync(() => {
177
+ async function handoffFile(params) {
178
+ const created = "source" in params ? params : undefined;
179
+ const identity = created?.identity ?? inspectFileIdentitySync(() => {
180
+ assertParents(params);
176
181
  const stat = fsSync.lstatSync(params.sourcePath, { bigint: true });
177
182
  assertInitialSource(stat);
178
183
  return stat;
179
184
  });
180
- let handle;
181
- let siblingHandle;
185
+ let handle = created?.source;
186
+ const owners = new Set(handle ? [handle] : []);
187
+ const failures = [];
182
188
  let unregister;
183
- const state = { siblingOwned: false };
189
+ const state = {
190
+ siblingOwned: false, publication: "not-published", cleanup: "preserved", closeFailed: false,
191
+ };
192
+ async function close(owned) {
193
+ // Final creation consumes its pin before close can release and recycle it.
194
+ // Legacy temporary handoff retains its historical FileHandle close retry.
195
+ if (created)
196
+ owners.delete(owned);
197
+ try {
198
+ await owned.close();
199
+ owners.delete(owned);
200
+ }
201
+ catch (error) {
202
+ state.closeFailed = true;
203
+ throw error;
204
+ }
205
+ }
184
206
  try {
185
- handle = await openProducerSource(params, identity);
186
- inspectLinkedFile(() => fsSync.lstatSync(params.sourcePath, { bigint: true }), identity, 1n, "isolated producer path");
187
- assertParents(params);
188
- unregister = registerTempPathForExit(params.targetPath, {
189
- cleanupSync: () => removeSiblingIfOwned({
190
- assertTargetParent: params.assertTargetParent,
191
- identity,
192
- state,
193
- targetPath: params.targetPath,
194
- }),
195
- });
196
- assertParents(params);
207
+ assertInitialSource(identity);
208
+ if (!handle) {
209
+ handle = await openProducerSource({ ...params, readWrite: "readWrite" in params && params.readWrite }, identity);
210
+ owners.add(handle);
211
+ }
212
+ inspectHandoff(params, handle, identity, state, 1n);
213
+ if (created?.verifyDescriptor)
214
+ await created.verifyDescriptor(handle.fd, params.sourcePath, 1);
215
+ if (!created) {
216
+ unregister = registerTempPathForExit(params.targetPath, {
217
+ cleanupSync: () => removeSiblingIfOwned({
218
+ assertTargetParent: params.assertTargetParent, identity, state, targetPath: params.targetPath,
219
+ }),
220
+ });
221
+ }
222
+ assertSynchronousCallbackResult(created?.assertBeforeMutation?.(), "assertBeforeMutation");
223
+ inspectHandoff(params, handle, identity, state, 1n);
224
+ state.publication = "indeterminate";
197
225
  try {
198
226
  fsSync.linkSync(params.sourcePath, params.targetPath);
199
227
  }
200
228
  catch (error) {
201
- throw normalizeLinkError(error);
229
+ const normalized = normalizeLinkError(error);
230
+ if (normalized instanceof FsSafeError &&
231
+ (normalized.code === "already-exists" || normalized.code === "helper-unavailable")) {
232
+ state.publication = "not-published";
233
+ }
234
+ throw normalized;
202
235
  }
203
- state.siblingOwned = true;
204
- assertLinkedHandoff(params, handle, identity);
236
+ state.publication = "published";
237
+ state.siblingOwned = !created;
238
+ try {
239
+ assertSynchronousCallbackResult(created?.onPublished?.(), "onPublished");
240
+ }
241
+ catch (error) {
242
+ failures.push(error);
243
+ }
244
+ inspectHandoff(params, handle, identity, state, 2n);
205
245
  if (process.platform === "win32") {
206
246
  // Legacy Windows unlink retains a delete-pending name until handles
207
247
  // opened through that name close. Transfer the pin before retiring it.
208
- siblingHandle = await openProducerSource({ ...params, sourcePath: params.targetPath }, identity, 2n);
209
- assertLinkedHandoff(params, siblingHandle, identity);
248
+ const sibling = created
249
+ ? await fs.open(params.targetPath, historicalOpenFlags(true))
250
+ : await openProducerSource({ ...params, sourcePath: params.targetPath,
251
+ readWrite: "readWrite" in params && params.readWrite }, identity, 2n);
252
+ owners.add(sibling);
253
+ inspectHandoff(params, sibling, identity, state, 2n);
254
+ if (created?.verifyDescriptor)
255
+ await created.verifyDescriptor(sibling.fd, params.targetPath, 2);
256
+ inspectHandoff(params, sibling, identity, state, 2n);
210
257
  inspectSourceDescriptor(handle, identity, 2n);
211
- await handle.close();
212
- handle = siblingHandle;
213
- siblingHandle = undefined;
258
+ await close(handle);
259
+ handle = sibling;
214
260
  // The old handle's close yielded; both names must still be ours.
215
- assertLinkedHandoff(params, handle, identity);
261
+ inspectHandoff(params, handle, identity, state, 2n);
216
262
  }
217
- assertParents(params);
263
+ if (created?.verifyDescriptor)
264
+ await created.verifyDescriptor(handle.fd, process.platform === "win32" ? params.targetPath : params.sourcePath, 2);
265
+ assertSynchronousCallbackResult(created?.assertBeforeMutation?.(), "assertBeforeMutation");
266
+ inspectHandoff(params, handle, identity, state, 2n);
267
+ state.cleanup = "failed";
218
268
  fsSync.unlinkSync(params.sourcePath);
219
- assertParents(params);
220
- const opened = inspectLinkedFile(() => fsSync.fstatSync(handle.fd, { bigint: true }), identity, 1n, "isolated producer descriptor");
221
- const sibling = inspectLinkedFile(() => fsSync.lstatSync(params.targetPath, { bigint: true }), identity, 1n, "isolated producer sibling");
269
+ state.cleanup = "removed";
270
+ if (created?.verifyDescriptor)
271
+ await created.verifyDescriptor(handle.fd, params.targetPath, 1);
272
+ const { opened, named } = inspectHandoff(params, handle, identity, state, 1n);
222
273
  const originalMode = identity.mode & 511n;
223
- if (process.platform === "win32" && (originalMode & 128n) === 0n &&
224
- ((opened.mode & 511n) !== originalMode || (sibling.mode & 511n) !== originalMode)) {
274
+ if (!created && process.platform === "win32" && (originalMode & 128n) === 0n &&
275
+ ((opened.mode & 511n) !== originalMode || (named.mode & 511n) !== originalMode)) {
225
276
  // Legacy unlink clears the shared read-only attribute. Repair only the
226
277
  // admitted sibling inode, without reopening or chmodding its pathname.
227
278
  assertParents(params);
228
279
  fsSync.fchmodSync(handle.fd, Number(originalMode));
229
- assertParents(params);
230
- const restored = inspectLinkedFile(() => fsSync.fstatSync(handle.fd, { bigint: true }), identity, 1n, "isolated producer descriptor");
231
- const restoredSibling = inspectLinkedFile(() => fsSync.lstatSync(params.targetPath, { bigint: true }), identity, 1n, "isolated producer sibling");
232
- if ((restored.mode & 511n) !== originalMode || (restoredSibling.mode & 511n) !== originalMode) {
280
+ const restored = inspectHandoff(params, handle, identity, state, 1n);
281
+ if ((restored.opened.mode & 511n) !== originalMode || (restored.named.mode & 511n) !== originalMode) {
233
282
  throw pathMismatch("isolated producer read-only mode could not be restored");
234
283
  }
235
284
  }
236
- const registered = unregister;
237
- const release = (() => {
238
- state.siblingOwned = false;
239
- registered();
240
- });
241
- release.setIdentity = registered.setIdentity;
242
- return { handle, identity, unregister: release };
285
+ if (!created) {
286
+ const registered = unregister;
287
+ const release = (() => {
288
+ state.siblingOwned = false;
289
+ registered();
290
+ });
291
+ release.setIdentity = registered.setIdentity;
292
+ return { handle, identity, unregister: release };
293
+ }
243
294
  }
244
295
  catch (error) {
245
- const settlement = [];
246
- if (unregister) {
247
- try {
248
- removeSiblingIfOwned({
249
- assertTargetParent: params.assertTargetParent,
250
- identity,
251
- state,
252
- targetPath: params.targetPath,
253
- });
254
- unregister();
255
- }
256
- catch (cleanupError) {
257
- settlement.push(cleanupError);
258
- }
296
+ failures.push(error);
297
+ }
298
+ if (failures.length === 0)
299
+ return handle;
300
+ if (unregister) {
301
+ try {
302
+ removeSiblingIfOwned({ assertTargetParent: params.assertTargetParent, identity, state, targetPath: params.targetPath });
303
+ unregister();
259
304
  }
260
- for (const retained of new Set([siblingHandle, handle])) {
261
- if (!retained)
262
- continue;
263
- try {
264
- await retained.close();
265
- }
266
- catch (closeError) {
267
- settlement.push(closeError);
268
- }
305
+ catch (error) {
306
+ failures.push(error);
307
+ }
308
+ }
309
+ for (const owned of [...owners].reverse()) {
310
+ try {
311
+ await close(owned);
312
+ }
313
+ catch (error) {
314
+ failures.push(error);
269
315
  }
270
- throw combinedFailure(error, settlement);
271
316
  }
317
+ const primary = failures[0];
318
+ const cause = combinedFailure(primary, failures.slice(1));
319
+ if (!created)
320
+ throw cause;
321
+ throw new FsSafeError(state.closeFailed || failures.length > 1 ? "helper-failed" : primary instanceof FsSafeError ? primary.code : "helper-failed", "created file publication failed", {
322
+ cause,
323
+ details: {
324
+ publication: { status: state.publication }, cleanup: state.cleanup,
325
+ resources: state.closeFailed ? "close-failed" : "closed",
326
+ path: params.targetPath, dev: identity.dev, ino: identity.ino,
327
+ },
328
+ });
329
+ }
330
+ export function handoffPrivateProducerFile(params) {
331
+ return handoffFile(params);
332
+ }
333
+ export function handoffCreatedFile(params) {
334
+ return handoffFile(params);
272
335
  }
@@ -1,5 +1,5 @@
1
1
  import { type BigIntStats } from "node:fs";
2
- import type { AnyAsyncDirectoryGuard } from "./directory-guard.js";
2
+ import { type AnyAsyncDirectoryGuard } from "./directory-guard.js";
3
3
  import type { PublishedWriteIdentity } from "./pinned-write.js";
4
4
  export declare function publishCopyStage(params: {
5
5
  temporaryPath: string;
@@ -8,6 +8,7 @@ export declare function publishCopyStage(params: {
8
8
  identity: BigIntStats;
9
9
  parentGuard: AnyAsyncDirectoryGuard;
10
10
  assertBeforeMutation?: () => void;
11
+ onPublicationAttempt?: () => void;
11
12
  onPublished?: (identity: PublishedWriteIdentity) => void;
12
13
  }): void;
13
14
  //# sourceMappingURL=publish-copy-stage.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"publish-copy-stage.d.ts","sourceRoot":"","sources":["../src/publish-copy-stage.ts"],"names":[],"mappings":"AAAA,OAAW,EAAE,KAAK,WAAW,EAAE,MAAM,SAAS,CAAC;AAE/C,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AAGnE,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,mBAAmB,CAAC;AAIhE,wBAAgB,gBAAgB,CAAC,MAAM,EAAE;IACvC,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,WAAW,CAAC;IACtB,WAAW,EAAE,sBAAsB,CAAC;IACpC,oBAAoB,CAAC,EAAE,MAAM,IAAI,CAAC;IAClC,WAAW,CAAC,EAAE,CAAC,QAAQ,EAAE,sBAAsB,KAAK,IAAI,CAAC;CAC1D,GAAG,IAAI,CAqCP"}
1
+ {"version":3,"file":"publish-copy-stage.d.ts","sourceRoot":"","sources":["../src/publish-copy-stage.ts"],"names":[],"mappings":"AAAA,OAAW,EAAE,KAAK,WAAW,EAAE,MAAM,SAAS,CAAC;AAE/C,OAAO,EAA4B,KAAK,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AAG7F,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,mBAAmB,CAAC;AAIhE,wBAAgB,gBAAgB,CAAC,MAAM,EAAE;IACvC,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,WAAW,CAAC;IACtB,WAAW,EAAE,sBAAsB,CAAC;IACpC,oBAAoB,CAAC,EAAE,MAAM,IAAI,CAAC;IAClC,oBAAoB,CAAC,EAAE,MAAM,IAAI,CAAC;IAClC,WAAW,CAAC,EAAE,CAAC,QAAQ,EAAE,sBAAsB,KAAK,IAAI,CAAC;CAC1D,GAAG,IAAI,CA4CP"}
@@ -1,19 +1,28 @@
1
1
  import fs, {} from "node:fs";
2
2
  import path from "node:path";
3
+ import { assertSyncDirectoryGuard } from "./directory-guard.js";
3
4
  import { FsSafeError } from "./errors.js";
4
5
  import { sameFileIdentityForCleanup } from "./file-identity.js";
5
6
  // Node has no portable no-replace rename. Link only our completed private stage,
6
7
  // then unlink it in the same JS turn so local readers never observe two names.
7
8
  export function publishCopyStage(params) {
8
9
  const { identity, temporaryPath, targetPath } = params;
9
- const staged = fs.lstatSync(temporaryPath, { bigint: true });
10
- const opened = fs.fstatSync(params.fd, { bigint: true });
11
- if (!staged.isFile() || staged.isSymbolicLink() || staged.nlink !== 1n ||
12
- !sameFileIdentityForCleanup(staged, identity) ||
13
- !sameFileIdentityForCleanup(opened, identity)) {
14
- throw new FsSafeError("path-mismatch", "exclusive copy stage changed before publication");
15
- }
10
+ const assertCurrent = () => {
11
+ assertSyncDirectoryGuard(params.parentGuard);
12
+ const staged = fs.lstatSync(temporaryPath, { bigint: true });
13
+ const opened = fs.fstatSync(params.fd, { bigint: true });
14
+ if (!staged.isFile() || staged.isSymbolicLink() || staged.nlink !== 1n ||
15
+ !sameFileIdentityForCleanup(staged, identity) ||
16
+ !sameFileIdentityForCleanup(opened, identity)) {
17
+ throw new FsSafeError("path-mismatch", "exclusive copy stage changed before publication");
18
+ }
19
+ };
20
+ assertCurrent();
16
21
  params.assertBeforeMutation?.();
22
+ // Caller authority checks can synchronously replace a parent or staged entry.
23
+ if (params.assertBeforeMutation)
24
+ assertCurrent();
25
+ params.onPublicationAttempt?.();
17
26
  fs.linkSync(temporaryPath, targetPath);
18
27
  let observerRejected = false;
19
28
  let observerError;
@@ -1 +1 @@
1
- {"version":3,"file":"publish-file.d.ts","sourceRoot":"","sources":["../src/publish-file.ts"],"names":[],"mappings":"AACA,OAAe,EAAoB,KAAK,KAAK,EAAE,MAAM,SAAS,CAAC;AAG/D,OAAO,EAEL,KAAK,gBAAgB,EACrB,KAAK,oBAAoB,EAC1B,MAAM,2BAA2B,CAAC;AAGnC,OAAO,EAGL,KAAK,gBAAgB,EACtB,MAAM,oBAAoB,CAAC;AAK5B,OAAO,EAML,KAAK,qCAAqC,EAC3C,MAAM,2BAA2B,CAAC;AAKnC,YAAY,EACV,2BAA2B,EAC3B,wCAAwC,EACxC,kCAAkC,EAClC,gCAAgC,EAChC,qCAAqC,GACtC,MAAM,2BAA2B,CAAC;AAEnC,MAAM,MAAM,4BAA4B,GAAG,cAAc,GAAG,eAAe,GAAG,kBAAkB,CAAC;AAEjG,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;AAIF,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAE/D;AA0ND,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,CA6NtC"}
1
+ {"version":3,"file":"publish-file.d.ts","sourceRoot":"","sources":["../src/publish-file.ts"],"names":[],"mappings":"AACA,OAAe,EAAoB,KAAK,KAAK,EAAE,MAAM,SAAS,CAAC;AAG/D,OAAO,EAEL,KAAK,gBAAgB,EACrB,KAAK,oBAAoB,EAC1B,MAAM,2BAA2B,CAAC;AAInC,OAAO,EAGL,KAAK,gBAAgB,EACtB,MAAM,oBAAoB,CAAC;AAK5B,OAAO,EAML,KAAK,qCAAqC,EAC3C,MAAM,2BAA2B,CAAC;AAKnC,YAAY,EACV,2BAA2B,EAC3B,wCAAwC,EACxC,kCAAkC,EAClC,gCAAgC,EAChC,qCAAqC,GACtC,MAAM,2BAA2B,CAAC;AAEnC,MAAM,MAAM,4BAA4B,GAAG,cAAc,GAAG,eAAe,GAAG,kBAAkB,CAAC;AAEjG,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;AAIF,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAE/D;AA0ND,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,CA4NtC"}
@@ -4,6 +4,7 @@ import fs, {} from "node:fs/promises";
4
4
  import path from "node:path";
5
5
  import { pinDirectory, } from "./directory-durability.js";
6
6
  import { FsSafeError } from "./errors.js";
7
+ import { ownDirectoryReceipt } from "./directory-receipt.js";
7
8
  import { hashFileHandle } from "./file-hash.js";
8
9
  import { sameFileIdentity, sameFileIdentityForCleanup, } from "./file-identity.js";
9
10
  import { syncFileBestEffortSync } from "./file-sync.js";
@@ -183,8 +184,7 @@ export async function publishFileExclusive(params) {
183
184
  const targetPathInput = admitStandalonePublicationPath(params.targetPath, "publication target uses a Windows filesystem namespace alias");
184
185
  const parentReceiptInput = params.parentReceipt;
185
186
  const parentReceipt = parentReceiptInput
186
- ? { path: parentReceiptInput.path, realPath: parentReceiptInput.realPath,
187
- identity: parentReceiptInput.identity }
187
+ ? ownDirectoryReceipt(parentReceiptInput)
188
188
  : undefined;
189
189
  if (parentReceipt) {
190
190
  assertNoWindowsPathAlias(parentReceipt.path, "filesystem", "publication parent uses a Windows filesystem namespace alias");
@@ -1,18 +1,11 @@
1
1
  import syncFs, { type BigIntStats } from "node:fs";
2
2
  import fs, { type FileHandle } from "node:fs/promises";
3
- import { type AnyAsyncDirectoryGuard } from "./directory-guard.js";
4
3
  type AsyncOwnerFileSystem = Pick<typeof fs, "lstat" | "open" | "unlink">;
5
4
  type SyncOwnerFileSystem = Pick<typeof syncFs, "closeSync" | "fstatSync" | "lstatSync" | "openSync" | "readFileSync" | "unlinkSync">;
6
5
  export type AtomicTempFailure = Readonly<{
7
6
  error: unknown;
8
7
  }>;
9
8
  export declare function removePathIfIdentityUnchanged(targetPath: string, identity: Pick<BigIntStats, "dev" | "ino">): Promise<void>;
10
- export declare function cleanupPinnedFilePath(params: {
11
- pathname: string;
12
- handle: FileHandle;
13
- identity?: BigIntStats;
14
- parentGuard: AnyAsyncDirectoryGuard;
15
- }): Promise<void>;
16
9
  export declare class AsyncAtomicTempOwner {
17
10
  #private;
18
11
  readonly pathname: string;
@@ -1 +1 @@
1
- {"version":3,"file":"replace-file-temp-owner.d.ts","sourceRoot":"","sources":["../src/replace-file-temp-owner.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"replace-file-temp-owner.d.ts","sourceRoot":"","sources":["../src/replace-file-temp-owner.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,EAAE,EAAE,KAAK,WAAW,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,EAAE,EAAE,EAAE,KAAK,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAWvD,KAAK,oBAAoB,GAAG,IAAI,CAAC,OAAO,EAAE,EAAE,OAAO,GAAG,MAAM,GAAG,QAAQ,CAAC,CAAC;AACzE,KAAK,mBAAmB,GAAG,IAAI,CAC7B,OAAO,MAAM,EACb,WAAW,GAAG,WAAW,GAAG,WAAW,GAAG,UAAU,GAAG,cAAc,GAAG,YAAY,CACrF,CAAC;AAIF,MAAM,MAAM,iBAAiB,GAAG,QAAQ,CAAC;IAAE,KAAK,EAAE,OAAO,CAAA;CAAE,CAAC,CAAC;AAmB7D,wBAAsB,6BAA6B,CACjD,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,IAAI,CAAC,WAAW,EAAE,KAAK,GAAG,KAAK,CAAC,GACzC,OAAO,CAAC,IAAI,CAAC,CAOf;AAkFD,qBAAa,oBAAoB;;IAC/B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAM1B,YAAY,QAAQ,EAAE,MAAM,EAG3B;IAED,KAAK,IAAI,IAAI,CAEZ;IAED,QAAQ,CAAC,UAAU,aAAc,WAAW,KAAG,IAAI,CAGjD;IAEF,KAAK,CAAC,IAAI,EAAE;QAAE,MAAM,EAAE,UAAU,CAAC;QAAC,QAAQ,EAAE,WAAW,CAAA;KAAE,GAAG,IAAI,CAG/D;IAED,IAAI,QAAQ,IAAI,WAAW,CAG1B;IAEK,aAAa,CAAC,QAAQ,EAAE,oBAAoB,EAAE,QAAQ,SAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,CAoB3F;IAEK,eAAe,CACnB,QAAQ,EAAE,oBAAoB,EAC9B,QAAQ,EAAE,MAAM,EAChB,YAAY,CAAC,EAAE,MAAM,GACpB,OAAO,CAAC,IAAI,CAAC,CA8Cf;IAED,WAAW,IAAI,IAAI,CAGlB;IAEK,MAAM,CAAC,MAAM,EAAE;QACnB,QAAQ,EAAE,oBAAoB,CAAC;QAC/B,eAAe,CAAC,EAAE,iBAAiB,CAAC;QACpC,mBAAmB,EAAE,OAAO,CAAC;KAC9B,GAAG,OAAO,CAAC,IAAI,CAAC,CAqChB;CACF;AAED,qBAAa,mBAAmB;;IAC9B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAM1B,YAAY,QAAQ,EAAE,MAAM,EAG3B;IAED,KAAK,IAAI,IAAI,CAEZ;IAED,QAAQ,CAAC,UAAU,aAAc,WAAW,KAAG,IAAI,CAGjD;IAEF,KAAK,CAAC,IAAI,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,WAAW,CAAA;KAAE,GAAG,IAAI,CAGvD;IAED,IAAI,QAAQ,IAAI,WAAW,CAG1B;IAED,aAAa,CAAC,QAAQ,EAAE,mBAAmB,EAAE,QAAQ,SAAgB,GAAG,IAAI,CAkB3E;IAED,eAAe,CACb,QAAQ,EAAE,mBAAmB,EAC7B,QAAQ,EAAE,MAAM,EAChB,YAAY,CAAC,EAAE,MAAM,GACpB,IAAI,CAkDN;IAED,WAAW,IAAI,IAAI,CAGlB;IAED,MAAM,CAAC,MAAM,EAAE;QACb,QAAQ,EAAE,mBAAmB,CAAC;QAC9B,eAAe,CAAC,EAAE,iBAAiB,CAAC;QACpC,mBAAmB,EAAE,OAAO,CAAC;KAC9B,GAAG,IAAI,CAuCP;CACF"}
@@ -1,7 +1,8 @@
1
1
  import syncFs, {} from "node:fs";
2
2
  import fs, {} from "node:fs/promises";
3
3
  import path from "node:path";
4
- import { assertAsyncDirectoryGuard, createAsyncDirectoryGuard } from "./directory-guard.js";
4
+ import { createAsyncDirectoryGuard } from "./directory-guard.js";
5
+ import { hasErrorCode, removeOwnedPath } from "./file-cleanup.js";
5
6
  import { resolveReadOpenFlags } from "./read-open-flags.js";
6
7
  import { FsSafeError } from "./errors.js";
7
8
  import { sameFileIdentityForCleanup, sha256Hex } from "./file-identity.js";
@@ -9,17 +10,6 @@ import { withAsyncDirectoryGuards } from "./guarded-mutation.js";
9
10
  import { inspectFileIdentity, inspectFileIdentitySync } from "./strict-file-identity.js";
10
11
  import { registerTempPathForExit } from "./temp-cleanup.js";
11
12
  const PUBLISHED_READ_FLAGS = resolveReadOpenFlags();
12
- function hasErrorCode(error, expected) {
13
- if ((typeof error !== "object" || error === null) && typeof error !== "function") {
14
- return false;
15
- }
16
- try {
17
- return Reflect.get(error, "code") === expected;
18
- }
19
- catch {
20
- return false;
21
- }
22
- }
23
13
  function describeFailure(error) {
24
14
  try {
25
15
  return String(error);
@@ -68,58 +58,17 @@ function cleanupFailure(originalFailure, cleanupError) {
68
58
  return isErrorValue(cleanupError) ? cleanupError : new Error(describeFailure(cleanupError));
69
59
  }
70
60
  async function cleanupOwnedPath(params) {
71
- if (!params.identity)
72
- return true;
73
61
  try {
74
- const current = params.fsModule === fs
75
- ? syncFs.lstatSync(params.pathname, { bigint: true })
76
- : await params.fsModule.lstat(params.pathname, { bigint: true });
77
- if (current.isSymbolicLink() ||
78
- !current.isFile() ||
79
- current.nlink !== 1n ||
80
- !sameFileIdentityForCleanup(current, params.identity)) {
81
- return true;
82
- }
83
- await params.fsModule.unlink(params.pathname);
62
+ await removeOwnedPath(params);
84
63
  return true;
85
64
  }
86
65
  catch (cleanupError) {
87
- if (hasErrorCode(cleanupError, "ENOENT"))
88
- return true;
89
66
  if (params.throwOnCleanupError) {
90
67
  throw cleanupFailure(params.originalFailure, cleanupError);
91
68
  }
92
69
  return false;
93
70
  }
94
71
  }
95
- // Borrowed handle: the caller retains it until this best-effort cleanup finishes.
96
- export async function cleanupPinnedFilePath(params) {
97
- if (!params.identity)
98
- return;
99
- try {
100
- const guard = params.parentGuard;
101
- if ([guard.stat.dev, guard.stat.ino].some((value) => typeof value === "number" && !Number.isSafeInteger(value)))
102
- return;
103
- await assertAsyncDirectoryGuard(guard);
104
- const parent = syncFs.lstatSync(guard.dir, { bigint: true });
105
- if (parent.isSymbolicLink() || !parent.isDirectory() ||
106
- !sameFileIdentityForCleanup(parent, guard.stat))
107
- return;
108
- const opened = syncFs.fstatSync(params.handle.fd, { bigint: true });
109
- if (!opened.isFile() || opened.nlink !== 1n ||
110
- !sameFileIdentityForCleanup(opened, params.identity))
111
- return;
112
- await cleanupOwnedPath({
113
- fsModule: fs,
114
- pathname: params.pathname,
115
- identity: params.identity,
116
- throwOnCleanupError: false,
117
- });
118
- }
119
- catch {
120
- // Unverifiable authority must preserve the path and the original write failure.
121
- }
122
- }
123
72
  function cleanupOwnedPathSync(params) {
124
73
  if (!params.identity)
125
74
  return true;
@@ -4,9 +4,10 @@ declare const exclusiveSidecarCreate: unique symbol;
4
4
  export declare const sidecarExclusiveCreate: Readonly<{
5
5
  [exclusiveSidecarCreate]: true;
6
6
  }>;
7
- export type RootWriteParams = RootWriteOptions & RootCreateStreamOptions & {
7
+ export type RootWriteParams = RootWriteOptions & RootCreateOptions & RootCreateStreamOptions & {
8
8
  relativePath: string;
9
9
  data: string | Buffer | AsyncIterable<Uint8Array>;
10
+ strictFileSync?: boolean;
10
11
  [exclusiveSidecarCreate]?: true;
11
12
  };
12
13
  export declare function rethrowCreateInputError(error: unknown): never;
@@ -1 +1 @@
1
- {"version":3,"file":"root-create-input.d.ts","sourceRoot":"","sources":["../src/root-create-input.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAC1D,OAAO,KAAK,EAAE,iBAAiB,EAAE,uBAAuB,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAItG,QAAA,MAAM,sBAAsB,eAAmC,CAAC;AAChE,eAAO,MAAM,sBAAsB;;EAA6D,CAAC;AAEjG,MAAM,MAAM,eAAe,GAAG,gBAAgB,GAAG,uBAAuB,GAAG;IACzE,YAAY,EAAE,MAAM,CAAC;IACrB,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,aAAa,CAAC,UAAU,CAAC,CAAC;IAClD,CAAC,sBAAsB,CAAC,CAAC,EAAE,IAAI,CAAC;CACjC,CAAC;AAQF,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,CAG7D;AAED,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,eAAe,CAAC,MAAM,CAAC,EAC7B,OAAO,EAAE,iBAAiB,GAAG,uBAAuB,EACpD,eAAe,CAAC,EAAE,MAAM,GACvB,iBAAiB,GAAG,uBAAuB,CAe7C;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,eAAe,GAAG,gBAAgB,CAwCxE"}
1
+ {"version":3,"file":"root-create-input.d.ts","sourceRoot":"","sources":["../src/root-create-input.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAC1D,OAAO,KAAK,EAAE,iBAAiB,EAAE,uBAAuB,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAItG,QAAA,MAAM,sBAAsB,eAAmC,CAAC;AAChE,eAAO,MAAM,sBAAsB;;EAA6D,CAAC;AAEjG,MAAM,MAAM,eAAe,GAAG,gBAAgB,GAAG,iBAAiB,GAAG,uBAAuB,GAAG;IAC7F,YAAY,EAAE,MAAM,CAAC;IACrB,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,aAAa,CAAC,UAAU,CAAC,CAAC;IAClD,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,CAAC,sBAAsB,CAAC,CAAC,EAAE,IAAI,CAAC;CACjC,CAAC;AAQF,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,CAG7D;AAED,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,eAAe,CAAC,MAAM,CAAC,EAC7B,OAAO,EAAE,iBAAiB,GAAG,uBAAuB,EACpD,eAAe,CAAC,EAAE,MAAM,GACvB,iBAAiB,GAAG,uBAAuB,CAkB7C;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,eAAe,GAAG,gBAAgB,CA8CxE"}
@@ -20,24 +20,33 @@ export function rethrowCreateInputError(error) {
20
20
  export function createInputOptions(data, options, defaultMaxBytes) {
21
21
  if (typeof data === "string" || Buffer.isBuffer(data))
22
22
  return options;
23
- options.signal?.throwIfAborted();
23
+ const { signal } = options;
24
+ signal?.throwIfAborted();
24
25
  if (options.encoding !== undefined || options.renameIdentity !== undefined) {
25
26
  throw new TypeError("streamed create does not accept encoding or renameIdentity");
26
27
  }
27
28
  const maxBytes = normalizeMaxBytes(options.maxBytes, { defaultValue: defaultMaxBytes });
29
+ const { assertBeforeMutation } = options;
28
30
  return {
29
31
  ...options,
30
32
  maxBytes,
33
+ signal,
31
34
  assertBeforeMutation: () => {
32
- options.signal?.throwIfAborted();
33
- return options.assertBeforeMutation?.();
35
+ signal?.throwIfAborted();
36
+ return assertBeforeMutation == null ? undefined : Reflect.apply(assertBeforeMutation, options, []);
34
37
  },
35
38
  };
36
39
  }
37
40
  export function rootWriteInput(params) {
41
+ if (params.atomic !== undefined && typeof params.atomic !== "boolean") {
42
+ throw new TypeError("atomic must be a boolean");
43
+ }
38
44
  const data = params.data;
39
45
  if (typeof data === "string" || Buffer.isBuffer(data)) {
40
- return { kind: "buffer", data, encoding: params.encoding, stageBeforePublish: !params[exclusiveSidecarCreate] };
46
+ return {
47
+ kind: "buffer", data, encoding: params.encoding,
48
+ stageBeforePublish: params[exclusiveSidecarCreate] ? false : params.atomic === true ? true : undefined,
49
+ };
41
50
  }
42
51
  return {
43
52
  kind: "stream",
@@ -1,13 +1,13 @@
1
1
  import type { PinnedWriteMutationAdmission } from "./pinned-write.js";
2
- import type { RootContext } from "./root-context.js";
2
+ import { type RootContext } from "./root-context.js";
3
3
  /**
4
4
  * Complete the common Root.mkdir case with one exact existing parent. This is
5
5
  * optional evidence: every uncertain probe returns to the ordered root walk.
6
6
  */
7
7
  export declare function tryMkdirAtExactParent(root: RootContext, targetPath: string, mutationAdmission: PinnedWriteMutationAdmission): Promise<boolean>;
8
- export declare function prepareRootWriteTarget(root: RootContext, targetPath: string, assertBeforeMutation?: () => void, mutationAdmission?: PinnedWriteMutationAdmission): Promise<string>;
8
+ export declare function prepareRootWriteTarget(root: RootContext, targetPath: string, assertBeforeMutation?: () => void, mutationAdmission?: PinnedWriteMutationAdmission, privateMode?: boolean): Promise<string>;
9
9
  export declare function mkdirPathFallback(root: RootContext, resolved: {
10
10
  rootReal: string;
11
11
  resolved: string;
12
- }, assertBeforeMutation?: () => void, rejectSymlinks?: boolean, mutationAdmission?: PinnedWriteMutationAdmission): Promise<void>;
12
+ }, assertBeforeMutation?: () => void, rejectSymlinks?: boolean, mutationAdmission?: PinnedWriteMutationAdmission, privateMode?: boolean): Promise<void>;
13
13
  //# sourceMappingURL=root-directory-creation.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"root-directory-creation.d.ts","sourceRoot":"","sources":["../src/root-directory-creation.ts"],"names":[],"mappings":"AAkBA,OAAO,KAAK,EAAE,4BAA4B,EAAE,MAAM,mBAAmB,CAAC;AAEtE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAuDrD;;;GAGG;AACH,wBAAsB,qBAAqB,CACzC,IAAI,EAAE,WAAW,EACjB,UAAU,EAAE,MAAM,EAClB,iBAAiB,EAAE,4BAA4B,GAC9C,OAAO,CAAC,OAAO,CAAC,CAoGlB;AAED,wBAAsB,sBAAsB,CAC1C,IAAI,EAAE,WAAW,EACjB,UAAU,EAAE,MAAM,EAClB,oBAAoB,CAAC,EAAE,MAAM,IAAI,EACjC,iBAAiB,CAAC,EAAE,4BAA4B,GAC/C,OAAO,CAAC,MAAM,CAAC,CA6BjB;AAED,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,WAAW,EACjB,QAAQ,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,EAChD,oBAAoB,CAAC,EAAE,MAAM,IAAI,EACjC,cAAc,UAAQ,EACtB,iBAAiB,CAAC,EAAE,4BAA4B,GAC/C,OAAO,CAAC,IAAI,CAAC,CAyBf"}
1
+ {"version":3,"file":"root-directory-creation.d.ts","sourceRoot":"","sources":["../src/root-directory-creation.ts"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EAAE,4BAA4B,EAAE,MAAM,mBAAmB,CAAC;AAEtE,OAAO,EAA6B,KAAK,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAuDhF;;;GAGG;AACH,wBAAsB,qBAAqB,CACzC,IAAI,EAAE,WAAW,EACjB,UAAU,EAAE,MAAM,EAClB,iBAAiB,EAAE,4BAA4B,GAC9C,OAAO,CAAC,OAAO,CAAC,CAoGlB;AAED,wBAAsB,sBAAsB,CAC1C,IAAI,EAAE,WAAW,EACjB,UAAU,EAAE,MAAM,EAClB,oBAAoB,CAAC,EAAE,MAAM,IAAI,EACjC,iBAAiB,CAAC,EAAE,4BAA4B,EAChD,WAAW,UAAQ,GAClB,OAAO,CAAC,MAAM,CAAC,CA+BjB;AAED,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,WAAW,EACjB,QAAQ,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,EAChD,oBAAoB,CAAC,EAAE,MAAM,IAAI,EACjC,cAAc,UAAQ,EACtB,iBAAiB,CAAC,EAAE,4BAA4B,EAChD,WAAW,UAAQ,GAClB,OAAO,CAAC,IAAI,CAAC,CAiCf"}