@openclaw/fs-safe 0.9.0 → 0.10.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 (178) hide show
  1. package/CHANGELOG.md +78 -0
  2. package/README.md +17 -0
  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-crc32.d.ts.map +1 -1
  7. package/dist/archive-crc32.js +6 -1
  8. package/dist/archive-deadline.d.ts.map +1 -1
  9. package/dist/archive-deadline.js +3 -4
  10. package/dist/archive-gzip-tail.d.ts +2 -0
  11. package/dist/archive-gzip-tail.d.ts.map +1 -1
  12. package/dist/archive-gzip-tail.js +20 -3
  13. package/dist/archive-merge.js +1 -1
  14. package/dist/archive-native.d.ts.map +1 -1
  15. package/dist/archive-native.js +3 -2
  16. package/dist/archive-read.d.ts.map +1 -1
  17. package/dist/archive-read.js +67 -66
  18. package/dist/archive-tar-stream.d.ts +11 -4
  19. package/dist/archive-tar-stream.d.ts.map +1 -1
  20. package/dist/archive-tar-stream.js +20 -8
  21. package/dist/archive-tar-wasm.d.ts.map +1 -1
  22. package/dist/archive-tar-wasm.js +2 -1
  23. package/dist/bounded-read.d.ts +7 -0
  24. package/dist/bounded-read.d.ts.map +1 -1
  25. package/dist/bounded-read.js +73 -43
  26. package/dist/clone-metadata.d.ts +19 -0
  27. package/dist/clone-metadata.d.ts.map +1 -0
  28. package/dist/clone-metadata.js +32 -0
  29. package/dist/copy-file-input.d.ts +23 -0
  30. package/dist/copy-file-input.d.ts.map +1 -0
  31. package/dist/copy-file-input.js +91 -0
  32. package/dist/copy-policy.d.ts +3 -0
  33. package/dist/copy-policy.d.ts.map +1 -0
  34. package/dist/copy-policy.js +8 -0
  35. package/dist/copy-publication.d.ts +10 -1
  36. package/dist/copy-publication.d.ts.map +1 -1
  37. package/dist/copy-publication.js +27 -0
  38. package/dist/copy-tree-portable.d.ts +9 -0
  39. package/dist/copy-tree-portable.d.ts.map +1 -0
  40. package/dist/copy-tree-portable.js +191 -0
  41. package/dist/copy.d.ts +16 -0
  42. package/dist/copy.d.ts.map +1 -0
  43. package/dist/copy.js +123 -0
  44. package/dist/durability.d.ts +1 -1
  45. package/dist/durability.d.ts.map +1 -1
  46. package/dist/error-detail.d.ts.map +1 -1
  47. package/dist/error-detail.js +4 -1
  48. package/dist/file-hash.d.ts +6 -2
  49. package/dist/file-hash.d.ts.map +1 -1
  50. package/dist/file-hash.js +44 -8
  51. package/dist/filename.d.ts.map +1 -1
  52. package/dist/filename.js +2 -12
  53. package/dist/guarded-mkdir.d.ts +1 -0
  54. package/dist/guarded-mkdir.d.ts.map +1 -1
  55. package/dist/guarded-mkdir.js +1 -0
  56. package/dist/guarded-mutation.d.ts +2 -0
  57. package/dist/guarded-mutation.d.ts.map +1 -1
  58. package/dist/guarded-mutation.js +8 -4
  59. package/dist/home-dir.d.ts.map +1 -1
  60. package/dist/home-dir.js +9 -7
  61. package/dist/index.d.ts +1 -1
  62. package/dist/index.d.ts.map +1 -1
  63. package/dist/install-path.d.ts.map +1 -1
  64. package/dist/install-path.js +2 -6
  65. package/dist/json-durable-queue.d.ts.map +1 -1
  66. package/dist/json-durable-queue.js +13 -14
  67. package/dist/local-roots.d.ts.map +1 -1
  68. package/dist/local-roots.js +22 -24
  69. package/dist/move-path-stage.d.ts +8 -0
  70. package/dist/move-path-stage.d.ts.map +1 -0
  71. package/dist/move-path-stage.js +55 -0
  72. package/dist/move-path.d.ts.map +1 -1
  73. package/dist/move-path.js +26 -31
  74. package/dist/mutation-authority.d.ts +8 -0
  75. package/dist/mutation-authority.d.ts.map +1 -0
  76. package/dist/mutation-authority.js +36 -0
  77. package/dist/native-binding.d.ts +24 -1
  78. package/dist/native-binding.d.ts.map +1 -1
  79. package/dist/native-operations.d.ts +1 -1
  80. package/dist/native-operations.d.ts.map +1 -1
  81. package/dist/native-operations.js +11 -5
  82. package/dist/native-pinned-write-windows.d.ts.map +1 -1
  83. package/dist/native-pinned-write-windows.js +18 -7
  84. package/dist/native-pinned-write.d.ts.map +1 -1
  85. package/dist/native-pinned-write.js +1 -0
  86. package/dist/native-staged-file.d.ts +3 -3
  87. package/dist/native-staged-file.d.ts.map +1 -1
  88. package/dist/native-staged-file.js +35 -8
  89. package/dist/path.d.ts.map +1 -1
  90. package/dist/path.js +3 -1
  91. package/dist/permissions-windows.d.ts.map +1 -1
  92. package/dist/permissions-windows.js +38 -48
  93. package/dist/pinned-operation.js +1 -1
  94. package/dist/pinned-write.d.ts +5 -1
  95. package/dist/pinned-write.d.ts.map +1 -1
  96. package/dist/pinned-write.js +50 -30
  97. package/dist/positional-read.d.ts +9 -0
  98. package/dist/positional-read.d.ts.map +1 -0
  99. package/dist/positional-read.js +36 -0
  100. package/dist/publish-copy-stage.d.ts +13 -0
  101. package/dist/publish-copy-stage.d.ts.map +1 -0
  102. package/dist/publish-copy-stage.js +47 -0
  103. package/dist/publish-file.d.ts.map +1 -1
  104. package/dist/publish-file.js +6 -5
  105. package/dist/replace-file-temp-owner.d.ts.map +1 -1
  106. package/dist/replace-file-temp-owner.js +6 -2
  107. package/dist/root-context.d.ts +3 -0
  108. package/dist/root-context.d.ts.map +1 -1
  109. package/dist/root-context.js +51 -26
  110. package/dist/root-directory-list.d.ts +24 -0
  111. package/dist/root-directory-list.d.ts.map +1 -0
  112. package/dist/root-directory-list.js +201 -0
  113. package/dist/root-errors.d.ts +1 -0
  114. package/dist/root-errors.d.ts.map +1 -1
  115. package/dist/root-errors.js +3 -0
  116. package/dist/root-file.d.ts +2 -0
  117. package/dist/root-file.d.ts.map +1 -1
  118. package/dist/root-file.js +12 -4
  119. package/dist/root-impl.d.ts +6 -50
  120. package/dist/root-impl.d.ts.map +1 -1
  121. package/dist/root-impl.js +330 -210
  122. package/dist/root-options.d.ts +66 -0
  123. package/dist/root-options.d.ts.map +1 -0
  124. package/dist/root-options.js +18 -0
  125. package/dist/root-path-existing.d.ts +5 -0
  126. package/dist/root-path-existing.d.ts.map +1 -1
  127. package/dist/root-path-existing.js +56 -1
  128. package/dist/root-path.d.ts +1 -0
  129. package/dist/root-path.d.ts.map +1 -1
  130. package/dist/root-path.js +74 -162
  131. package/dist/root-symlink-policy.d.ts +13 -0
  132. package/dist/root-symlink-policy.d.ts.map +1 -0
  133. package/dist/root-symlink-policy.js +34 -0
  134. package/dist/root-walk.d.ts +5 -4
  135. package/dist/root-walk.d.ts.map +1 -1
  136. package/dist/root-walk.js +99 -50
  137. package/dist/root-write-mode.d.ts.map +1 -1
  138. package/dist/root-write-mode.js +1 -4
  139. package/dist/root.d.ts +3 -1
  140. package/dist/root.d.ts.map +1 -1
  141. package/dist/secure-file.d.ts.map +1 -1
  142. package/dist/secure-file.js +4 -4
  143. package/dist/sidecar-lock-acquire.d.ts.map +1 -1
  144. package/dist/sidecar-lock-acquire.js +2 -1
  145. package/dist/sidecar-lock-policy.d.ts.map +1 -1
  146. package/dist/sidecar-lock-policy.js +3 -1
  147. package/dist/temp-cleanup.d.ts.map +1 -1
  148. package/dist/temp-cleanup.js +3 -2
  149. package/dist/timing.d.ts +1 -0
  150. package/dist/timing.d.ts.map +1 -1
  151. package/dist/timing.js +25 -6
  152. package/dist/trash.js +2 -2
  153. package/dist/walk.d.ts.map +1 -1
  154. package/dist/walk.js +6 -26
  155. package/dist/windows-owner.d.ts +9 -12
  156. package/dist/windows-owner.d.ts.map +1 -1
  157. package/dist/windows-owner.js +24 -58
  158. package/dist/write-file-handle.d.ts +6 -0
  159. package/dist/write-file-handle.d.ts.map +1 -0
  160. package/dist/write-file-handle.js +25 -0
  161. package/docs/advanced.md +18 -4
  162. package/docs/archive.md +22 -7
  163. package/docs/atomic.md +7 -0
  164. package/docs/contributing.md +7 -0
  165. package/docs/copy.md +86 -0
  166. package/docs/durability.md +17 -2
  167. package/docs/errors.md +1 -1
  168. package/docs/local-roots.md +8 -1
  169. package/docs/native.md +14 -1
  170. package/docs/permissions.md +20 -10
  171. package/docs/positional-read.md +63 -0
  172. package/docs/root.md +166 -4
  173. package/docs/security-model.md +14 -0
  174. package/docs/timing.md +2 -0
  175. package/docs/types.md +18 -11
  176. package/docs/walk.md +54 -5
  177. package/docs/writing.md +20 -2
  178. package/package.json +13 -8
package/dist/root-walk.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import path from "node:path";
2
2
  import { FsSafeError } from "./errors.js";
3
3
  import { resolveRootPath } from "./root-path.js";
4
+ import { createSuppressedError } from "./suppressed-error.js";
4
5
  function validateBudget(name, value) {
5
6
  if (value === undefined)
6
7
  return Number.POSITIVE_INFINITY;
@@ -25,6 +26,9 @@ export async function* walkRoot(root, relativePath, options) {
25
26
  if (!["skip", "follow-within-root"].includes(options.symlinkPolicy)) {
26
27
  throw new TypeError(`invalid root walk symlink policy: ${String(options.symlinkPolicy)}`);
27
28
  }
29
+ if (options.order !== undefined && !["sorted", "filesystem"].includes(options.order)) {
30
+ throw new TypeError(`invalid root walk order: ${String(options.order)}`);
31
+ }
28
32
  if (options.limitBehavior !== undefined &&
29
33
  !["truncate", "throw"].includes(options.limitBehavior)) {
30
34
  throw new TypeError(`invalid root walk limit behavior: ${String(options.limitBehavior)}`);
@@ -38,6 +42,12 @@ export async function* walkRoot(root, relativePath, options) {
38
42
  const visitedDirectories = new Set();
39
43
  let examined = 0;
40
44
  let truncated = false;
45
+ const admitEntry = () => {
46
+ if (examined >= maxEntries)
47
+ return false;
48
+ examined += 1;
49
+ return true;
50
+ };
41
51
  const onLimit = (atPath) => {
42
52
  if ((options.limitBehavior ?? "truncate") === "throw") {
43
53
  throw new FsSafeError("too-large", `root walk budget exceeded at ${atPath || "."}`);
@@ -45,9 +55,15 @@ export async function* walkRoot(root, relativePath, options) {
45
55
  truncated = true;
46
56
  return limitEntry(atPath);
47
57
  };
58
+ const onDirectoryError = (directory, error) => {
59
+ // Rethrow cancellation with any disposal failure already attached.
60
+ if (options.signal?.aborted || (options.onDirectoryError ?? "throw") === "throw")
61
+ throw error;
62
+ return { relativePath: directory, kind: "directory-error", size: 0, error };
63
+ };
48
64
  async function* visit(directory, depth) {
49
65
  options.signal?.throwIfAborted();
50
- let entries;
66
+ let listing;
51
67
  try {
52
68
  const resolvedDirectory = await resolveRootPath({
53
69
  absolutePath: path.resolve(root.rootReal, directory),
@@ -67,66 +83,99 @@ export async function* walkRoot(root, relativePath, options) {
67
83
  .relative(root.rootReal, resolvedDirectory.canonicalPath)
68
84
  .split(path.sep)
69
85
  .join(path.posix.sep);
70
- entries = await root.list(listingDirectory, { withFileTypes: true });
86
+ listing = await root.list(listingDirectory, {
87
+ order: options.order ?? "sorted",
88
+ signal: options.signal,
89
+ snapshot: maxEntries === Number.POSITIVE_INFINITY,
90
+ admitEntry,
91
+ });
71
92
  }
72
93
  catch (error) {
73
- // Cancellation is never a recoverable directory read failure.
74
- options.signal?.throwIfAborted();
75
- if ((options.onDirectoryError ?? "throw") === "throw")
76
- throw error;
77
- yield { relativePath: directory, kind: "directory-error", size: 0, error };
94
+ yield onDirectoryError(directory, error);
78
95
  return;
79
96
  }
80
- options.signal?.throwIfAborted();
81
- for (const entry of entries) {
82
- options.signal?.throwIfAborted();
83
- const child = directory
84
- ? path.posix.join(directory.split(path.sep).join(path.posix.sep), entry.name)
85
- : entry.name;
86
- if (examined >= maxEntries) {
87
- yield onLimit(child);
88
- return;
89
- }
90
- examined += 1;
91
- let kind = entryKind(entry);
92
- let size = entry.size;
93
- if (kind === "symlink") {
94
- if (options.symlinkPolicy === "skip") {
97
+ // A thrown undefined still needs to be retained if closing also fails.
98
+ let failed = false;
99
+ let operationError;
100
+ try {
101
+ while (true) {
102
+ let next;
103
+ try {
104
+ next = await listing.next();
105
+ options.signal?.throwIfAborted();
106
+ }
107
+ catch (error) {
108
+ yield onDirectoryError(directory, error);
109
+ return;
110
+ }
111
+ if (next === undefined)
112
+ return;
113
+ const name = next.kind === "entry" ? next.entry.name : next.name;
114
+ const child = directory
115
+ ? path.posix.join(directory.split(path.sep).join(path.posix.sep), name)
116
+ : name;
117
+ if (next.kind === "limit") {
118
+ yield onLimit(child);
119
+ return;
120
+ }
121
+ const entry = next.entry;
122
+ let kind = entryKind(entry);
123
+ let size = entry.size;
124
+ if (kind === "symlink") {
125
+ if (options.symlinkPolicy === "skip") {
126
+ continue;
127
+ }
128
+ const resolved = await resolveRootPath({
129
+ absolutePath: path.resolve(root.rootReal, child),
130
+ rootPath: root.rootReal,
131
+ rootCanonicalPath: root.rootReal,
132
+ boundaryLabel: "root walk",
133
+ });
134
+ if (!resolved.exists) {
135
+ continue;
136
+ }
137
+ const target = await root.stat(path.relative(root.rootReal, resolved.canonicalPath));
138
+ kind = target.isDirectory ? "directory" : target.isFile ? "file" : "other";
139
+ size = target.size;
140
+ }
141
+ const walkEntry = { relativePath: child, kind, size };
142
+ const filterResult = options.entryFilter?.(walkEntry) ?? "include";
143
+ if (!["include", "skip", "skip-subtree"].includes(filterResult)) {
144
+ throw new TypeError(`invalid root walk entryFilter result: ${String(filterResult)}`);
145
+ }
146
+ if (filterResult === "include") {
147
+ yield walkEntry;
148
+ }
149
+ if (kind !== "directory") {
95
150
  continue;
96
151
  }
97
- const resolved = await resolveRootPath({
98
- absolutePath: path.resolve(root.rootReal, child),
99
- rootPath: root.rootReal,
100
- rootCanonicalPath: root.rootReal,
101
- boundaryLabel: "root walk",
102
- });
103
- if (!resolved.exists) {
152
+ if (filterResult === "skip-subtree") {
104
153
  continue;
105
154
  }
106
- kind = resolved.kind === "directory" ? "directory" : resolved.kind === "file" ? "file" : "other";
107
- size = entry.size;
108
- }
109
- const walkEntry = { relativePath: child, kind, size };
110
- const filterResult = options.entryFilter?.(walkEntry) ?? "include";
111
- if (!["include", "skip", "skip-subtree"].includes(filterResult)) {
112
- throw new TypeError(`invalid root walk entryFilter result: ${String(filterResult)}`);
113
- }
114
- if (filterResult === "include") {
115
- yield walkEntry;
116
- }
117
- if (kind !== "directory") {
118
- continue;
155
+ if (depth >= maxDepth) {
156
+ yield onLimit(child);
157
+ return;
158
+ }
159
+ yield* visit(child, depth + 1);
160
+ if (truncated)
161
+ return;
119
162
  }
120
- if (filterResult === "skip-subtree") {
121
- continue;
163
+ }
164
+ catch (error) {
165
+ failed = true;
166
+ operationError = error;
167
+ throw error;
168
+ }
169
+ finally {
170
+ try {
171
+ await listing[Symbol.asyncDispose]();
122
172
  }
123
- if (depth >= maxDepth) {
124
- yield onLimit(child);
125
- return;
173
+ catch (closeError) {
174
+ if (failed) {
175
+ throw createSuppressedError(closeError, operationError, "directory walk and close both failed");
176
+ }
177
+ throw closeError;
126
178
  }
127
- yield* visit(child, depth + 1);
128
- if (truncated)
129
- return;
130
179
  }
131
180
  }
132
181
  yield* visit(relativePath, 0);
@@ -1 +1 @@
1
- {"version":3,"file":"root-write-mode.d.ts","sourceRoot":"","sources":["../src/root-write-mode.ts"],"names":[],"mappings":"AAUA,wBAAsB,sBAAsB,CAAC,MAAM,EAAE;IACnD,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB,GAAG,OAAO,CAAC,MAAM,CAAC,CAwClB"}
1
+ {"version":3,"file":"root-write-mode.d.ts","sourceRoot":"","sources":["../src/root-write-mode.ts"],"names":[],"mappings":"AASA,wBAAsB,sBAAsB,CAAC,MAAM,EAAE;IACnD,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB,GAAG,OAAO,CAAC,MAAM,CAAC,CAsClB"}
@@ -1,7 +1,6 @@
1
1
  import fsSync from "node:fs";
2
2
  import fs from "node:fs/promises";
3
3
  import { FsSafeError } from "./errors.js";
4
- import { sameFileIdentity } from "./file-identity.js";
5
4
  import { isNotFoundPathError, isPathInside } from "./path.js";
6
5
  import { resolveReadOpenFlags } from "./read-open-flags.js";
7
6
  import { hardlinkedPathNotAllowedError, outsideWorkspaceError } from "./root-errors.js";
@@ -23,9 +22,7 @@ export async function inheritWriteTargetMode(params) {
23
22
  const handle = await fs.open(params.targetPath, resolveReadOpenFlags());
24
23
  try {
25
24
  // Bind admission to the inode whose metadata is inherited.
26
- if (!sameFileIdentity(fsSync.fstatSync(handle.fd, { bigint: true }), existing)) {
27
- throw new FsSafeError("path-mismatch", "write target changed during mode inheritance");
28
- }
25
+ await inspectFileIdentity(() => fsSync.fstatSync(handle.fd, { bigint: true }), existing);
29
26
  }
30
27
  finally {
31
28
  await handle.close().catch(() => undefined);
package/dist/root.d.ts CHANGED
@@ -1,4 +1,6 @@
1
- export { DEFAULT_ROOT_MAX_BYTES, openLocalFileSafely, readLocalFileSafely, resolveOpenedFileRealPathForHandle, root, type DenyMutationPolicy, type HardlinkPolicy, type RenameIdentityPolicy, type OpenResult, type ReadResult, type Root, type RootAppendOptions, type RootCopyOptions, type RootCreateJsonOptions, type RootCreateOptions, type RootDefaults, type RootMkdirOptions, type RootMoveOptions, type RootOpenOptions, type RootOpenWritableOptions, type RootOptions, type RootReadOptions, type RootRemoveOptions, type RootWriteJsonOptions, type RootWriteOptions, type SymlinkPolicy, type WritableOpenMode, type WritableOpenResult, } from "./root-impl.js";
1
+ export { DEFAULT_ROOT_MAX_BYTES, openLocalFileSafely, readLocalFileSafely, resolveOpenedFileRealPathForHandle, root, type DenyMutationPolicy, type HardlinkPolicy, type MutationSymlinkPolicy, type RenameIdentityPolicy, type OpenResult, type ReadResult, type Root, type RootAppendOptions, type RootCopyOptions, type RootCopySource, type RootCreateJsonOptions, type RootCreateOptions, type RootDefaults, type RootMkdirOptions, type RootMoveOptions, type RootOpenOptions, type RootOpenWritableOptions, type RootOptions, type RootReadOptions, type RootRemoveOptions, type RootWriteJsonOptions, type RootWriteOptions, type SymlinkPolicy, type WritableOpenMode, type WritableOpenResult, } from "./root-impl.js";
2
+ export type { RootCopyPublicationReceipt } from "./copy-publication.js";
3
+ export type { CopyCloneMode } from "./copy-policy.js";
2
4
  export type { ContainmentGuarantee } from "./containment.js";
3
5
  export type { RootWalkDataEntry, RootWalkDataEntryKind, RootWalkDirectoryErrorBehavior, RootWalkEntry, RootWalkEntryFilter, RootWalkEntryFilterResult, RootWalkEntryKind, RootWalkLimitBehavior, RootWalkOptions, RootWalkSymlinkPolicy, } from "./root-walk.js";
4
6
  //# sourceMappingURL=root.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"root.d.ts","sourceRoot":"","sources":["../src/root.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,sBAAsB,EACtB,mBAAmB,EACnB,mBAAmB,EACnB,kCAAkC,EAClC,IAAI,EACJ,KAAK,kBAAkB,EACvB,KAAK,cAAc,EACnB,KAAK,oBAAoB,EACzB,KAAK,UAAU,EACf,KAAK,UAAU,EACf,KAAK,IAAI,EACT,KAAK,iBAAiB,EACtB,KAAK,eAAe,EACpB,KAAK,qBAAqB,EAC1B,KAAK,iBAAiB,EACtB,KAAK,YAAY,EACjB,KAAK,gBAAgB,EACrB,KAAK,eAAe,EACpB,KAAK,eAAe,EACpB,KAAK,uBAAuB,EAC5B,KAAK,WAAW,EAChB,KAAK,eAAe,EACpB,KAAK,iBAAiB,EACtB,KAAK,oBAAoB,EACzB,KAAK,gBAAgB,EACrB,KAAK,aAAa,EAClB,KAAK,gBAAgB,EACrB,KAAK,kBAAkB,GACxB,MAAM,gBAAgB,CAAC;AACxB,YAAY,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAC7D,YAAY,EACV,iBAAiB,EACjB,qBAAqB,EACrB,8BAA8B,EAC9B,aAAa,EACb,mBAAmB,EACnB,yBAAyB,EACzB,iBAAiB,EACjB,qBAAqB,EACrB,eAAe,EACf,qBAAqB,GACtB,MAAM,gBAAgB,CAAC"}
1
+ {"version":3,"file":"root.d.ts","sourceRoot":"","sources":["../src/root.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,sBAAsB,EACtB,mBAAmB,EACnB,mBAAmB,EACnB,kCAAkC,EAClC,IAAI,EACJ,KAAK,kBAAkB,EACvB,KAAK,cAAc,EACnB,KAAK,qBAAqB,EAC1B,KAAK,oBAAoB,EACzB,KAAK,UAAU,EACf,KAAK,UAAU,EACf,KAAK,IAAI,EACT,KAAK,iBAAiB,EACtB,KAAK,eAAe,EACpB,KAAK,cAAc,EACnB,KAAK,qBAAqB,EAC1B,KAAK,iBAAiB,EACtB,KAAK,YAAY,EACjB,KAAK,gBAAgB,EACrB,KAAK,eAAe,EACpB,KAAK,eAAe,EACpB,KAAK,uBAAuB,EAC5B,KAAK,WAAW,EAChB,KAAK,eAAe,EACpB,KAAK,iBAAiB,EACtB,KAAK,oBAAoB,EACzB,KAAK,gBAAgB,EACrB,KAAK,aAAa,EAClB,KAAK,gBAAgB,EACrB,KAAK,kBAAkB,GACxB,MAAM,gBAAgB,CAAC;AACxB,YAAY,EAAE,0BAA0B,EAAE,MAAM,uBAAuB,CAAC;AACxE,YAAY,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACtD,YAAY,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAC7D,YAAY,EACV,iBAAiB,EACjB,qBAAqB,EACrB,8BAA8B,EAC9B,aAAa,EACb,mBAAmB,EACnB,yBAAyB,EACzB,iBAAiB,EACjB,qBAAqB,EACrB,eAAe,EACf,qBAAqB,GACtB,MAAM,gBAAgB,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"secure-file.d.ts","sourceRoot":"","sources":["../src/secure-file.ts"],"names":[],"mappings":"AAAA,OAAe,EAAE,KAAK,KAAK,EAAE,MAAM,SAAS,CAAC;AAa7C,OAAO,EAOL,KAAK,eAAe,EACpB,KAAK,sBAAsB,EAC5B,MAAM,kBAAkB,CAAC;AAG1B,MAAM,MAAM,qBAAqB,GAAG;IAClC,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,sBAAsB,CAAC;IAC/B,WAAW,CAAC,EAAE,2BAA2B,CAAC;IAC1C,MAAM,CAAC,EAAE,uBAAuB,CAAC;IACjC,EAAE,CAAC,EAAE,mBAAmB,CAAC;CAC1B,CAAC;AAEF,MAAM,MAAM,sBAAsB,GAAG;IACnC,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B,CAAC;AAEF,MAAM,MAAM,2BAA2B,GAAG;IACxC,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,qBAAqB,CAAC,EAAE,OAAO,CAAC;CACjC,CAAC;AAEF,MAAM,MAAM,uBAAuB,GAAG,sBAAsB,CAAC;AAE7D,MAAM,MAAM,mBAAmB,GAAG;IAChC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,KAAK,CAAC;IACZ,WAAW,CAAC,EAAE,eAAe,CAAC;CAC/B,CAAC;AAmMF,wBAAsB,cAAc,CAClC,OAAO,EAAE,qBAAqB,GAC7B,OAAO,CAAC,oBAAoB,CAAC,CAe/B"}
1
+ {"version":3,"file":"secure-file.d.ts","sourceRoot":"","sources":["../src/secure-file.ts"],"names":[],"mappings":"AAAA,OAAe,EAAE,KAAK,KAAK,EAAE,MAAM,SAAS,CAAC;AAa7C,OAAO,EAOL,KAAK,eAAe,EACpB,KAAK,sBAAsB,EAC5B,MAAM,kBAAkB,CAAC;AAI1B,MAAM,MAAM,qBAAqB,GAAG;IAClC,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,sBAAsB,CAAC;IAC/B,WAAW,CAAC,EAAE,2BAA2B,CAAC;IAC1C,MAAM,CAAC,EAAE,uBAAuB,CAAC;IACjC,EAAE,CAAC,EAAE,mBAAmB,CAAC;CAC1B,CAAC;AAEF,MAAM,MAAM,sBAAsB,GAAG;IACnC,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B,CAAC;AAEF,MAAM,MAAM,2BAA2B,GAAG;IACxC,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,qBAAqB,CAAC,EAAE,OAAO,CAAC;CACjC,CAAC;AAEF,MAAM,MAAM,uBAAuB,GAAG,sBAAsB,CAAC;AAE7D,MAAM,MAAM,mBAAmB,GAAG;IAChC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,KAAK,CAAC;IACZ,WAAW,CAAC,EAAE,eAAe,CAAC;CAC/B,CAAC;AAmMF,wBAAsB,cAAc,CAClC,OAAO,EAAE,qBAAqB,GAC7B,OAAO,CAAC,oBAAoB,CAAC,CAe/B"}
@@ -11,6 +11,7 @@ import { formatPermissionErrorDetail } from "./permission-exec.js";
11
11
  import { resolveReadOpenFlags } from "./read-open-flags.js";
12
12
  import { inspectPathPermissions, isGroupReadable, isGroupWritable, isWorldReadable, isWorldWritable, modeBits, } from "./permissions.js";
13
13
  import { inspectFileIdentity } from "./strict-file-identity.js";
14
+ import { scheduleTimeout } from "./timing.js";
14
15
  function isAbsolutePathname(value) {
15
16
  return (path.isAbsolute(value) ||
16
17
  (process.platform === "win32" &&
@@ -160,12 +161,12 @@ async function readHandleWithTimeout(handle, timeoutMs, maxBytes) {
160
161
  if (timeoutMs === undefined || !Number.isFinite(timeoutMs) || timeoutMs <= 0) {
161
162
  return await read();
162
163
  }
163
- let timeout;
164
+ let cancelTimeout;
164
165
  try {
165
166
  return await Promise.race([
166
167
  read(),
167
168
  new Promise((_resolve, reject) => {
168
- timeout = setTimeout(() => {
169
+ cancelTimeout = scheduleTimeout(() => {
169
170
  void handle.close().catch(() => undefined);
170
171
  reject(new FsSafeError("timeout", `secure file read timed out after ${timeoutMs}ms`));
171
172
  }, timeoutMs);
@@ -173,8 +174,7 @@ async function readHandleWithTimeout(handle, timeoutMs, maxBytes) {
173
174
  ]);
174
175
  }
175
176
  finally {
176
- if (timeout)
177
- clearTimeout(timeout);
177
+ cancelTimeout?.();
178
178
  }
179
179
  }
180
180
  export async function readSecureFile(options) {
@@ -1 +1 @@
1
- {"version":3,"file":"sidecar-lock-acquire.d.ts","sourceRoot":"","sources":["../src/sidecar-lock-acquire.ts"],"names":[],"mappings":"AAQA,OAAO,EAA6B,KAAK,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC1F,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAC;AAS3C,OAAO,EAWL,KAAK,mBAAmB,EACzB,MAAM,2BAA2B,CAAC;AACnC,OAAO,KAAK,EAAE,yBAAyB,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAG5F,KAAK,iBAAiB,GAAG,IAAI,CAAC,gBAAgB,EAAE,IAAI,GAAG,OAAO,GAAG,MAAM,GAAG,WAAW,CAAC,CAAC;AAEvF,MAAM,MAAM,eAAe,GAAG;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,MAAM,EAAE,iBAAiB,CAAC;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,mBAAmB,CAAC;IAC9B,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC,cAAc,CAAC,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/B,QAAQ,CAAC,EAAE,IAAI,CAAC;IAChB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,YAAY,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC;IACxC,eAAe,CAAC,EAAE,MAAM,CAAC,OAAO,CAAC;CAClC,CAAC;AAEF,KAAK,6BAA6B,GAAG;IACnC,IAAI,EAAE,GAAG,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;IACnC,aAAa,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC3B,2BAA2B,IAAI,IAAI,CAAC;IACpC,iBAAiB,CAAC,oBAAoB,EAAE,MAAM,EAAE,IAAI,EAAE,eAAe,GAAG,iBAAiB,CAAC;IAC1F,eAAe,CACb,oBAAoB,EAAE,MAAM,EAC5B,IAAI,EAAE,eAAe,EACrB,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE,GAC5B,OAAO,CAAC,OAAO,CAAC,CAAC;CACrB,CAAC;AAqBF,wBAAsB,kBAAkB,CAAC,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/E,OAAO,EAAE,yBAAyB,CAAC,QAAQ,CAAC,EAC5C,OAAO,EAAE,6BAA6B,GACrC,OAAO,CAAC,iBAAiB,CAAC,CAqT5B"}
1
+ {"version":3,"file":"sidecar-lock-acquire.d.ts","sourceRoot":"","sources":["../src/sidecar-lock-acquire.ts"],"names":[],"mappings":"AAQA,OAAO,EAA6B,KAAK,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC1F,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAC;AAS3C,OAAO,EAWL,KAAK,mBAAmB,EACzB,MAAM,2BAA2B,CAAC;AACnC,OAAO,KAAK,EAAE,yBAAyB,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAI5F,KAAK,iBAAiB,GAAG,IAAI,CAAC,gBAAgB,EAAE,IAAI,GAAG,OAAO,GAAG,MAAM,GAAG,WAAW,CAAC,CAAC;AAEvF,MAAM,MAAM,eAAe,GAAG;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,MAAM,EAAE,iBAAiB,CAAC;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,mBAAmB,CAAC;IAC9B,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC,cAAc,CAAC,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/B,QAAQ,CAAC,EAAE,IAAI,CAAC;IAChB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,YAAY,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC;IACxC,eAAe,CAAC,EAAE,MAAM,CAAC,OAAO,CAAC;CAClC,CAAC;AAEF,KAAK,6BAA6B,GAAG;IACnC,IAAI,EAAE,GAAG,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;IACnC,aAAa,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC3B,2BAA2B,IAAI,IAAI,CAAC;IACpC,iBAAiB,CAAC,oBAAoB,EAAE,MAAM,EAAE,IAAI,EAAE,eAAe,GAAG,iBAAiB,CAAC;IAC1F,eAAe,CACb,oBAAoB,EAAE,MAAM,EAC5B,IAAI,EAAE,eAAe,EACrB,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE,GAC5B,OAAO,CAAC,OAAO,CAAC,CAAC;CACrB,CAAC;AAqBF,wBAAsB,kBAAkB,CAAC,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/E,OAAO,EAAE,yBAAyB,CAAC,QAAQ,CAAC,EAC5C,OAAO,EAAE,6BAA6B,GACrC,OAAO,CAAC,iBAAiB,CAAC,CAqT5B"}
@@ -10,6 +10,7 @@ import { createNativeExclusiveFile } from "./native-operations.js";
10
10
  import { computeSidecarLockDelayMs, defaultSidecarLockShouldReclaim, isTransientLockFileDenial, maxTransientLockDenials, validateSidecarLockRetryOptions, validateSidecarLockTimeoutMs, } from "./sidecar-lock-policy.js";
11
11
  import { readSidecarLockRawSnapshot, parseSidecarLockSnapshot, relativeSidecarLockPath, releaseSidecarReclaimGuard, removeSidecarLockIfUnchanged, removeStaleSidecarLockIfAllowed, serializeSidecarLockPayload, sidecarLockSnapshotStillPresent, sidecarReclaimGuardExists, tryAcquireSidecarReclaimGuard, } from "./sidecar-lock-reclaim.js";
12
12
  import { createSuppressedError } from "./suppressed-error.js";
13
+ import { sleep } from "./timing.js";
13
14
  async function resolveNormalizedTargetPath(targetPath, lockRoot) {
14
15
  const resolved = path.resolve(targetPath);
15
16
  const dir = path.dirname(resolved);
@@ -86,7 +87,7 @@ export async function acquireSidecarLock(options, context) {
86
87
  : Math.max(0, options.timeoutMs - elapsed);
87
88
  const delay = Math.min(computeSidecarLockDelayMs(retry, attempt), remaining);
88
89
  attempt += 1;
89
- await new Promise((resolve) => setTimeout(resolve, delay));
90
+ await sleep(delay);
90
91
  };
91
92
  // Waiting can fail on the caller's own retry or deadline limits. Classifying
92
93
  // a denial as contention must not cost them the original diagnosis, so hand
@@ -1 +1 @@
1
- {"version":3,"file":"sidecar-lock-policy.d.ts","sourceRoot":"","sources":["../src/sidecar-lock-policy.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,yBAAyB,CAAC;AAQvE,wBAAgB,+BAA+B,CAAC,KAAK,EAAE,uBAAuB,GAAG,IAAI,CAkBpF;AAED,wBAAgB,4BAA4B,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAGhF;AAED,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,uBAAuB,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAQjG;AASD,eAAO,MAAM,uBAAuB,IAAI,CAAC;AAEzC,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAGnF;AAED,wBAAgB,6BAA6B,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAU7E;AAED,wBAAsB,+BAA+B,CAAC,MAAM,EAAE;IAC5D,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;CACf,GAAG,OAAO,CAAC,OAAO,CAAC,CAQnB"}
1
+ {"version":3,"file":"sidecar-lock-policy.d.ts","sourceRoot":"","sources":["../src/sidecar-lock-policy.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,yBAAyB,CAAC;AAQvE,wBAAgB,+BAA+B,CAAC,KAAK,EAAE,uBAAuB,GAAG,IAAI,CAkBpF;AAED,wBAAgB,4BAA4B,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAGhF;AAED,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,uBAAuB,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAUjG;AASD,eAAO,MAAM,uBAAuB,IAAI,CAAC;AAEzC,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAGnF;AAED,wBAAgB,6BAA6B,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAU7E;AAED,wBAAsB,+BAA+B,CAAC,MAAM,EAAE;IAC5D,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;CACf,GAAG,OAAO,CAAC,OAAO,CAAC,CAQnB"}
@@ -32,7 +32,9 @@ export function computeSidecarLockDelayMs(retry, attempt) {
32
32
  const minTimeout = retry.minTimeout ?? 50;
33
33
  const maxTimeout = retry.maxTimeout ?? 1000;
34
34
  const factor = retry.factor ?? 1;
35
- const base = Math.min(maxTimeout, Math.max(minTimeout, minTimeout * factor ** attempt));
35
+ // Zero times an overflowing power is NaN, which makes Atomics.wait unbounded.
36
+ const scaled = minTimeout === 0 ? 0 : minTimeout * factor ** attempt;
37
+ const base = Math.min(maxTimeout, Math.max(minTimeout, scaled));
36
38
  const jitter = retry.randomize ? 1 + Math.random() : 1;
37
39
  return Math.min(maxTimeout, Math.round(base * jitter));
38
40
  }
@@ -1 +1 @@
1
- {"version":3,"file":"temp-cleanup.d.ts","sourceRoot":"","sources":["../src/temp-cleanup.ts"],"names":[],"mappings":"AACA,OAAO,EAA8B,KAAK,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAEvF,MAAM,MAAM,uBAAuB,GAAG,gBAAgB,CAAC;AAEvD,MAAM,MAAM,oBAAoB,GAAG,CAAC,MAAM,IAAI,CAAC,GAAG;IAChD,WAAW,CAAC,QAAQ,EAAE,gBAAgB,GAAG,IAAI,CAAC;CAC/C,CAAC;AAkDF,wBAAgB,uBAAuB,CACrC,QAAQ,EAAE,MAAM,EAChB,OAAO,CAAC,EAAE;IACR,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,QAAQ,CAAC,EAAE,gBAAgB,CAAC;IAC5B,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,WAAW,CAAC,EAAE,MAAM,OAAO,CAAC;CAC7B,GACA,oBAAoB,CA2BtB;AAED,wBAAgB,mCAAmC,IAAI,IAAI,CAE1D;AAED,wBAAgB,kCAAkC,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAUzE"}
1
+ {"version":3,"file":"temp-cleanup.d.ts","sourceRoot":"","sources":["../src/temp-cleanup.ts"],"names":[],"mappings":"AACA,OAAO,EAA8B,KAAK,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAEvF,MAAM,MAAM,uBAAuB,GAAG,gBAAgB,CAAC;AAEvD,MAAM,MAAM,oBAAoB,GAAG,CAAC,MAAM,IAAI,CAAC,GAAG;IAChD,WAAW,CAAC,QAAQ,EAAE,gBAAgB,GAAG,IAAI,CAAC;CAC/C,CAAC;AAmDF,wBAAgB,uBAAuB,CACrC,QAAQ,EAAE,MAAM,EAChB,OAAO,CAAC,EAAE;IACR,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,QAAQ,CAAC,EAAE,gBAAgB,CAAC;IAC5B,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,WAAW,CAAC,EAAE,MAAM,OAAO,CAAC;CAC7B,GACA,oBAAoB,CA2BtB;AAED,wBAAgB,mCAAmC,IAAI,IAAI,CAE1D;AAED,wBAAgB,kCAAkC,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAUzE"}
@@ -11,8 +11,9 @@ function pathStillMatchesReceipt(entry) {
11
11
  return (!entry.singleLinkFile || (current.isFile() && current.nlink === 1n)) &&
12
12
  sameFileIdentityForCleanup(current, entry.identity);
13
13
  }
14
- catch (error) {
15
- return !entry.singleLinkFile && error.code === "ENOENT";
14
+ catch {
15
+ // A missing pathname grants no authority over an entry created after this lookup.
16
+ return false;
16
17
  }
17
18
  }
18
19
  function removeRegisteredPathSync(entry) {
package/dist/timing.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ export declare function scheduleTimeout(callback: () => void, ms: number): () => void;
1
2
  export declare function sleep(ms: number): Promise<void>;
2
3
  export declare function sleepSync(ms: number): void;
3
4
  export declare function withTimeout<T>(promise: Promise<T>, timeoutMs: number, labelOrOptions?: string | {
@@ -1 +1 @@
1
- {"version":3,"file":"timing.d.ts","sourceRoot":"","sources":["../src/timing.ts"],"names":[],"mappings":"AAAA,wBAAgB,KAAK,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAE/C;AAED,wBAAgB,SAAS,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAK1C;AAED,wBAAsB,WAAW,CAAC,CAAC,EACjC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,EACnB,SAAS,EAAE,MAAM,EACjB,cAAc,GAAE,MAAM,GAAG;IAAE,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,KAAK,CAAA;CAErF,GACA,OAAO,CAAC,CAAC,CAAC,CA6BZ"}
1
+ {"version":3,"file":"timing.d.ts","sourceRoot":"","sources":["../src/timing.ts"],"names":[],"mappings":"AAEA,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,IAAI,EAAE,EAAE,EAAE,MAAM,GAAG,MAAM,IAAI,CAgB5E;AAED,wBAAgB,KAAK,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAE/C;AAED,wBAAgB,SAAS,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAK1C;AAED,wBAAsB,WAAW,CAAC,CAAC,EACjC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,EACnB,SAAS,EAAE,MAAM,EACjB,cAAc,GAAE,MAAM,GAAG;IAAE,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,KAAK,CAAA;CAErF,GACA,OAAO,CAAC,CAAC,CAAC,CA2BZ"}
package/dist/timing.js CHANGED
@@ -1,5 +1,26 @@
1
+ const MAX_TIMER_DELAY_MS = 2 ** 31 - 1;
2
+ export function scheduleTimeout(callback, ms) {
3
+ let timer;
4
+ if (Number.isFinite(ms) && ms > MAX_TIMER_DELAY_MS) {
5
+ const startedAt = performance.now();
6
+ const tick = () => {
7
+ const remaining = ms - (performance.now() - startedAt);
8
+ if (remaining <= 0)
9
+ callback();
10
+ else
11
+ timer = setTimeout(tick, Math.min(remaining, MAX_TIMER_DELAY_MS));
12
+ };
13
+ // Node clamps overflowing delays to 1 ms. Rearm bounded timers against a
14
+ // monotonic clock so clock changes and event-loop stalls cannot shorten them.
15
+ timer = setTimeout(tick, MAX_TIMER_DELAY_MS);
16
+ }
17
+ else {
18
+ timer = setTimeout(callback, ms);
19
+ }
20
+ return () => clearTimeout(timer);
21
+ }
1
22
  export function sleep(ms) {
2
- return new Promise((resolve) => setTimeout(resolve, ms));
23
+ return new Promise((resolve) => { scheduleTimeout(resolve, ms); });
3
24
  }
4
25
  export function sleepSync(ms) {
5
26
  if (ms <= 0) {
@@ -16,12 +37,12 @@ export async function withTimeout(promise, timeoutMs, labelOrOptions = {
16
37
  const options = typeof labelOrOptions === "string" ? { label: labelOrOptions } : labelOrOptions;
17
38
  const createError = options.createError ??
18
39
  (() => new Error(options.message ?? `${options.label ?? "operation"} timed out after ${timeoutMs}ms`));
19
- let timeoutId;
40
+ let cancelTimeout;
20
41
  try {
21
42
  return await Promise.race([
22
43
  promise,
23
44
  new Promise((_, reject) => {
24
- timeoutId = setTimeout(() => {
45
+ cancelTimeout = scheduleTimeout(() => {
25
46
  try {
26
47
  reject(createError());
27
48
  }
@@ -33,8 +54,6 @@ export async function withTimeout(promise, timeoutMs, labelOrOptions = {
33
54
  ]);
34
55
  }
35
56
  finally {
36
- if (timeoutId) {
37
- clearTimeout(timeoutId);
38
- }
57
+ cancelTimeout?.();
39
58
  }
40
59
  }
package/dist/trash.js CHANGED
@@ -18,7 +18,7 @@ function isTrashDestinationCollision(error) {
18
18
  return Boolean(code && TRASH_DESTINATION_COLLISION_CODES.has(code));
19
19
  }
20
20
  function isSameOrChildPath(candidate, parent) {
21
- return candidate === parent || candidate.startsWith(`${parent}${path.sep}`);
21
+ return candidate === parent || candidate.startsWith(parent.endsWith(path.sep) ? parent : `${parent}${path.sep}`);
22
22
  }
23
23
  function resolveAllowedTrashRoots(allowedRoots) {
24
24
  const roots = [...(allowedRoots ?? [os.homedir(), os.tmpdir()])].flatMap((root) => {
@@ -107,7 +107,7 @@ function resolveContainedPath(root, leaf) {
107
107
  return resolvedPath;
108
108
  }
109
109
  function reserveTrashDestination(trashDir, base, timestamp) {
110
- const containerPrefix = resolveContainedPath(trashDir, `${base}-${timestamp}-`);
110
+ const containerPrefix = resolveContainedPath(trashDir, `.fs-safe-trash-${timestamp}-`);
111
111
  const container = fs.mkdtempSync(containerPrefix);
112
112
  const resolvedContainer = path.resolve(container);
113
113
  const resolvedTrashDir = path.resolve(trashDir);
@@ -1 +1 @@
1
- {"version":3,"file":"walk.d.ts","sourceRoot":"","sources":["../src/walk.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,SAAS,CAAC;AAI7B,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,WAAW,GAAG,SAAS,GAAG,OAAO,CAAC;AACvE,MAAM,MAAM,iBAAiB,GAAG,MAAM,GAAG,QAAQ,GAAG,SAAS,CAAC;AAE9D,MAAM,MAAM,kBAAkB,GAAG;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,aAAa,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG;IACjC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,iBAAiB,CAAC;IAC7B,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,kBAAkB,KAAK,OAAO,CAAC;IACjD,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,kBAAkB,KAAK,OAAO,CAAC;CAClD,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,OAAO,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG;IAChC,OAAO,EAAE,kBAAkB,EAAE,CAAC;IAC9B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,SAAS,EAAE,OAAO,CAAC;IAGnB,UAAU,CAAC,EAAE,oBAAoB,EAAE,CAAC;CACrC,CAAC;AAEF,KAAK,+BAA+B,GAAG,mBAAmB,GAAG;IAC3D,UAAU,EAAE,oBAAoB,EAAE,CAAC;CACpC,CAAC;AA+FF,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,oBAAyB,GACjC,+BAA+B,CAyDjC;AAED,wBAAsB,aAAa,CACjC,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,oBAAyB,GACjC,OAAO,CAAC,+BAA+B,CAAC,CAyD1C"}
1
+ {"version":3,"file":"walk.d.ts","sourceRoot":"","sources":["../src/walk.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,SAAS,CAAC;AAI7B,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,WAAW,GAAG,SAAS,GAAG,OAAO,CAAC;AACvE,MAAM,MAAM,iBAAiB,GAAG,MAAM,GAAG,QAAQ,GAAG,SAAS,CAAC;AAE9D,MAAM,MAAM,kBAAkB,GAAG;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,aAAa,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG;IACjC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,iBAAiB,CAAC;IAC7B,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,kBAAkB,KAAK,OAAO,CAAC;IACjD,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,kBAAkB,KAAK,OAAO,CAAC;CAClD,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,OAAO,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG;IAChC,OAAO,EAAE,kBAAkB,EAAE,CAAC;IAC9B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,SAAS,EAAE,OAAO,CAAC;IAGnB,UAAU,CAAC,EAAE,oBAAoB,EAAE,CAAC;CACrC,CAAC;AAEF,KAAK,+BAA+B,GAAG,mBAAmB,GAAG;IAC3D,UAAU,EAAE,oBAAoB,EAAE,CAAC;CACpC,CAAC;AAgFF,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,oBAAyB,GACjC,+BAA+B,CAyDjC;AAED,wBAAsB,aAAa,CACjC,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,oBAAyB,GACjC,OAAO,CAAC,+BAA+B,CAAC,CAyD1C"}
package/dist/walk.js CHANGED
@@ -27,7 +27,7 @@ function shouldStop(result, options) {
27
27
  return options.maxEntries !== undefined && result.scannedEntryCount >= Math.max(0, options.maxEntries);
28
28
  }
29
29
  function buildEntry(params) {
30
- const fullPath = path.join(params.dir, params.dirent.name);
30
+ const fullPath = params.fullPath;
31
31
  const relativePath = path.relative(params.rootDir, fullPath) || params.dirent.name;
32
32
  return {
33
33
  name: params.dirent.name,
@@ -47,27 +47,7 @@ function recordFailedDir(result, root, dir, depth, error) {
47
47
  error,
48
48
  });
49
49
  }
50
- function resolveSyncKind(fullPath, dirent, symlinks) {
51
- const kind = kindForDirent(dirent);
52
- if (kind !== "symlink")
53
- return kind;
54
- if (symlinks === "skip")
55
- return null;
56
- if (symlinks === "include")
57
- return "symlink";
58
- try {
59
- const stat = fsSync.statSync(fullPath);
60
- if (stat.isDirectory())
61
- return "directory";
62
- if (stat.isFile())
63
- return "file";
64
- }
65
- catch {
66
- return null;
67
- }
68
- return "other";
69
- }
70
- async function resolveAsyncKind(fullPath, dirent, symlinks) {
50
+ function resolveKind(fullPath, dirent, symlinks) {
71
51
  const kind = kindForDirent(dirent);
72
52
  if (kind !== "symlink")
73
53
  return kind;
@@ -127,10 +107,10 @@ export function walkDirectorySync(rootDir, options = {}) {
127
107
  }
128
108
  result.scannedEntryCount += 1;
129
109
  const fullPath = path.join(dir, dirent.name);
130
- const kind = resolveSyncKind(fullPath, dirent, symlinks);
110
+ const kind = resolveKind(fullPath, dirent, symlinks);
131
111
  if (!kind)
132
112
  continue;
133
- const entry = buildEntry({ rootDir: root, dir, dirent, depth, kind });
113
+ const entry = buildEntry({ rootDir: root, fullPath, dirent, depth, kind });
134
114
  if (options.include?.(entry) ?? true) {
135
115
  result.entries.push(entry);
136
116
  }
@@ -186,10 +166,10 @@ export async function walkDirectory(rootDir, options = {}) {
186
166
  }
187
167
  result.scannedEntryCount += 1;
188
168
  const fullPath = path.join(dir, dirent.name);
189
- const kind = await resolveAsyncKind(fullPath, dirent, symlinks);
169
+ const kind = resolveKind(fullPath, dirent, symlinks);
190
170
  if (!kind)
191
171
  continue;
192
- const entry = buildEntry({ rootDir: root, dir, dirent, depth, kind });
172
+ const entry = buildEntry({ rootDir: root, fullPath, dirent, depth, kind });
193
173
  if (options.include?.(entry) ?? true) {
194
174
  result.entries.push(entry);
195
175
  }
@@ -6,24 +6,21 @@ export type WindowsOwnerExec = (command: string, args: string[]) => Promise<{
6
6
  export type WindowsOwnerSummary = {
7
7
  sid?: string;
8
8
  currentUserSid?: string;
9
- principalSids?: Record<string, string>;
10
- principalTranslationFailed?: boolean;
9
+ daclPresent?: boolean;
10
+ aces?: WindowsOwnerAce[];
11
+ aclError?: string;
11
12
  remote?: boolean;
12
13
  trusted?: boolean;
13
14
  error?: string;
14
15
  errorDetail?: PermissionCommandFailure;
15
16
  errorCause?: unknown;
16
17
  };
17
- export declare function resolveWindowsPrincipalSids(params: {
18
- principals: string[];
19
- known?: Record<string, string>;
20
- env?: NodeJS.ProcessEnv;
21
- exec: WindowsOwnerExec;
22
- }): Promise<Record<string, string>>;
23
- export declare function resolveWindowsCurrentUserSid(params: {
24
- env?: NodeJS.ProcessEnv;
25
- exec: WindowsOwnerExec;
26
- }): Promise<string | null>;
18
+ export type WindowsOwnerAce = {
19
+ sid: string;
20
+ mask: number;
21
+ deny: boolean;
22
+ inheritOnly: boolean;
23
+ };
27
24
  export declare function inspectWindowsOwner(params: {
28
25
  targetPath: string;
29
26
  env?: NodeJS.ProcessEnv;
@@ -1 +1 @@
1
- {"version":3,"file":"windows-owner.d.ts","sourceRoot":"","sources":["../src/windows-owner.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,wBAAwB,EAC9B,MAAM,sBAAsB,CAAC;AAG9B,MAAM,MAAM,gBAAgB,GAAG,CAC7B,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EAAE,KACX,OAAO,CAAC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAEjD,MAAM,MAAM,mBAAmB,GAAG;IAChC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACvC,0BAA0B,CAAC,EAAE,OAAO,CAAC;IACrC,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,wBAAwB,CAAC;IACvC,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB,CAAC;AA4DF,wBAAsB,2BAA2B,CAAC,MAAM,EAAE;IACxD,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/B,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,IAAI,EAAE,gBAAgB,CAAC;CACxB,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAyBlC;AAED,wBAAsB,4BAA4B,CAAC,MAAM,EAAE;IACzD,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,IAAI,EAAE,gBAAgB,CAAC;CACxB,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAWzB;AAED,wBAAsB,mBAAmB,CAAC,MAAM,EAAE;IAChD,UAAU,EAAE,MAAM,CAAC;IACnB,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,IAAI,EAAE,gBAAgB,CAAC;CACxB,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAkD/B"}
1
+ {"version":3,"file":"windows-owner.d.ts","sourceRoot":"","sources":["../src/windows-owner.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,wBAAwB,EAC9B,MAAM,sBAAsB,CAAC;AAG9B,MAAM,MAAM,gBAAgB,GAAG,CAC7B,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EAAE,KACX,OAAO,CAAC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAEjD,MAAM,MAAM,mBAAmB,GAAG;IAChC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,IAAI,CAAC,EAAE,eAAe,EAAE,CAAC;IACzB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,wBAAwB,CAAC;IACvC,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,OAAO,CAAC;IACd,WAAW,EAAE,OAAO,CAAC;CACtB,CAAC;AAqDF,wBAAsB,mBAAmB,CAAC,MAAM,EAAE;IAChD,UAAU,EAAE,MAAM,CAAC;IACnB,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,IAAI,EAAE,gBAAgB,CAAC;CACxB,GAAG,OAAO,CAAC,mBAAmB,CAAC,CA+C/B"}