@openclaw/fs-safe 0.8.6 → 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 (246) hide show
  1. package/CHANGELOG.md +93 -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-durability.d.ts +24 -0
  11. package/dist/archive-durability.d.ts.map +1 -0
  12. package/dist/archive-durability.js +180 -0
  13. package/dist/archive-gzip-tail.d.ts +2 -0
  14. package/dist/archive-gzip-tail.d.ts.map +1 -1
  15. package/dist/archive-gzip-tail.js +22 -4
  16. package/dist/archive-input.js +4 -4
  17. package/dist/archive-kind.d.ts.map +1 -1
  18. package/dist/archive-kind.js +3 -2
  19. package/dist/archive-merge.d.ts +1 -0
  20. package/dist/archive-merge.d.ts.map +1 -1
  21. package/dist/archive-merge.js +42 -9
  22. package/dist/archive-native.d.ts +1 -0
  23. package/dist/archive-native.d.ts.map +1 -1
  24. package/dist/archive-native.js +4 -2
  25. package/dist/archive-options.d.ts +2 -0
  26. package/dist/archive-options.d.ts.map +1 -1
  27. package/dist/archive-parser.wasm +0 -0
  28. package/dist/archive-read.d.ts.map +1 -1
  29. package/dist/archive-read.js +72 -70
  30. package/dist/archive-staging.d.ts +1 -1
  31. package/dist/archive-staging.d.ts.map +1 -1
  32. package/dist/archive-staging.js +24 -19
  33. package/dist/archive-tar-stream.d.ts +11 -4
  34. package/dist/archive-tar-stream.d.ts.map +1 -1
  35. package/dist/archive-tar-stream.js +20 -8
  36. package/dist/archive-tar-wasm.d.ts.map +1 -1
  37. package/dist/archive-tar-wasm.js +2 -1
  38. package/dist/archive.d.ts.map +1 -1
  39. package/dist/archive.js +9 -4
  40. package/dist/bounded-read.d.ts +7 -0
  41. package/dist/bounded-read.d.ts.map +1 -1
  42. package/dist/bounded-read.js +73 -43
  43. package/dist/clone-metadata.d.ts +19 -0
  44. package/dist/clone-metadata.d.ts.map +1 -0
  45. package/dist/clone-metadata.js +32 -0
  46. package/dist/copy-file-input.d.ts +23 -0
  47. package/dist/copy-file-input.d.ts.map +1 -0
  48. package/dist/copy-file-input.js +91 -0
  49. package/dist/copy-policy.d.ts +3 -0
  50. package/dist/copy-policy.d.ts.map +1 -0
  51. package/dist/copy-policy.js +8 -0
  52. package/dist/copy-publication.d.ts +15 -0
  53. package/dist/copy-publication.d.ts.map +1 -0
  54. package/dist/copy-publication.js +30 -0
  55. package/dist/copy-tree-portable.d.ts +9 -0
  56. package/dist/copy-tree-portable.d.ts.map +1 -0
  57. package/dist/copy-tree-portable.js +191 -0
  58. package/dist/copy.d.ts +16 -0
  59. package/dist/copy.d.ts.map +1 -0
  60. package/dist/copy.js +123 -0
  61. package/dist/directory-mode-node.d.ts.map +1 -1
  62. package/dist/directory-mode-node.js +4 -4
  63. package/dist/durability.d.ts +1 -1
  64. package/dist/durability.d.ts.map +1 -1
  65. package/dist/error-detail.d.ts.map +1 -1
  66. package/dist/error-detail.js +4 -1
  67. package/dist/file-hash.d.ts +6 -2
  68. package/dist/file-hash.d.ts.map +1 -1
  69. package/dist/file-hash.js +49 -12
  70. package/dist/file-store-sync-write.d.ts +1 -0
  71. package/dist/file-store-sync-write.d.ts.map +1 -1
  72. package/dist/file-store-sync-write.js +9 -6
  73. package/dist/file-store.d.ts +4 -0
  74. package/dist/file-store.d.ts.map +1 -1
  75. package/dist/file-store.js +10 -1
  76. package/dist/filename.d.ts.map +1 -1
  77. package/dist/filename.js +2 -12
  78. package/dist/fs.d.ts.map +1 -1
  79. package/dist/fs.js +1 -2
  80. package/dist/guarded-mkdir.d.ts +1 -0
  81. package/dist/guarded-mkdir.d.ts.map +1 -1
  82. package/dist/guarded-mkdir.js +1 -0
  83. package/dist/guarded-mutation.d.ts +2 -0
  84. package/dist/guarded-mutation.d.ts.map +1 -1
  85. package/dist/guarded-mutation.js +9 -5
  86. package/dist/home-dir.d.ts.map +1 -1
  87. package/dist/home-dir.js +9 -7
  88. package/dist/index.d.ts +1 -1
  89. package/dist/index.d.ts.map +1 -1
  90. package/dist/install-path.d.ts.map +1 -1
  91. package/dist/install-path.js +9 -13
  92. package/dist/json-document-store.d.ts +3 -0
  93. package/dist/json-document-store.d.ts.map +1 -1
  94. package/dist/json-document-store.js +1 -0
  95. package/dist/json-durable-queue-directory.js +3 -3
  96. package/dist/json-durable-queue-ownership.js +3 -2
  97. package/dist/json-durable-queue.d.ts.map +1 -1
  98. package/dist/json-durable-queue.js +31 -27
  99. package/dist/local-roots.d.ts.map +1 -1
  100. package/dist/local-roots.js +22 -24
  101. package/dist/move-path-cleanup.d.ts.map +1 -1
  102. package/dist/move-path-cleanup.js +4 -3
  103. package/dist/move-path-stage.d.ts +8 -0
  104. package/dist/move-path-stage.d.ts.map +1 -0
  105. package/dist/move-path-stage.js +55 -0
  106. package/dist/move-path.d.ts.map +1 -1
  107. package/dist/move-path.js +55 -47
  108. package/dist/mutation-authority.d.ts +8 -0
  109. package/dist/mutation-authority.d.ts.map +1 -0
  110. package/dist/mutation-authority.js +36 -0
  111. package/dist/native-binding.d.ts +24 -1
  112. package/dist/native-binding.d.ts.map +1 -1
  113. package/dist/native-operations.d.ts +1 -1
  114. package/dist/native-operations.d.ts.map +1 -1
  115. package/dist/native-operations.js +11 -5
  116. package/dist/native-pinned-write-windows.d.ts.map +1 -1
  117. package/dist/native-pinned-write-windows.js +18 -7
  118. package/dist/native-pinned-write.d.ts.map +1 -1
  119. package/dist/native-pinned-write.js +1 -0
  120. package/dist/native-staged-file.d.ts +3 -3
  121. package/dist/native-staged-file.d.ts.map +1 -1
  122. package/dist/native-staged-file.js +35 -8
  123. package/dist/opened-file-failure.d.ts +1 -1
  124. package/dist/opened-file-failure.d.ts.map +1 -1
  125. package/dist/opened-file-failure.js +3 -2
  126. package/dist/path.d.ts.map +1 -1
  127. package/dist/path.js +3 -1
  128. package/dist/permissions-windows.d.ts.map +1 -1
  129. package/dist/permissions-windows.js +38 -48
  130. package/dist/permissions.js +3 -3
  131. package/dist/pinned-operation.js +1 -1
  132. package/dist/pinned-write.d.ts +5 -1
  133. package/dist/pinned-write.d.ts.map +1 -1
  134. package/dist/pinned-write.js +50 -30
  135. package/dist/positional-read.d.ts +9 -0
  136. package/dist/positional-read.d.ts.map +1 -0
  137. package/dist/positional-read.js +36 -0
  138. package/dist/private-temp-workspace.d.ts.map +1 -1
  139. package/dist/private-temp-workspace.js +9 -3
  140. package/dist/publish-copy-stage.d.ts +13 -0
  141. package/dist/publish-copy-stage.d.ts.map +1 -0
  142. package/dist/publish-copy-stage.js +47 -0
  143. package/dist/publish-file.d.ts.map +1 -1
  144. package/dist/publish-file.js +26 -25
  145. package/dist/replace-file-copy-fallback.d.ts.map +1 -1
  146. package/dist/replace-file-copy-fallback.js +30 -17
  147. package/dist/replace-file-copy-source.d.ts.map +1 -1
  148. package/dist/replace-file-copy-source.js +10 -5
  149. package/dist/replace-file-temp-owner.d.ts.map +1 -1
  150. package/dist/replace-file-temp-owner.js +6 -2
  151. package/dist/replace-file.d.ts.map +1 -1
  152. package/dist/replace-file.js +8 -3
  153. package/dist/root-context.d.ts +3 -0
  154. package/dist/root-context.d.ts.map +1 -1
  155. package/dist/root-context.js +51 -26
  156. package/dist/root-directory-list.d.ts +24 -0
  157. package/dist/root-directory-list.d.ts.map +1 -0
  158. package/dist/root-directory-list.js +201 -0
  159. package/dist/root-errors.d.ts +1 -0
  160. package/dist/root-errors.d.ts.map +1 -1
  161. package/dist/root-errors.js +3 -0
  162. package/dist/root-file.d.ts +2 -0
  163. package/dist/root-file.d.ts.map +1 -1
  164. package/dist/root-file.js +12 -4
  165. package/dist/root-impl.d.ts +6 -50
  166. package/dist/root-impl.d.ts.map +1 -1
  167. package/dist/root-impl.js +342 -208
  168. package/dist/root-options.d.ts +66 -0
  169. package/dist/root-options.d.ts.map +1 -0
  170. package/dist/root-options.js +18 -0
  171. package/dist/root-path-existing.d.ts +5 -0
  172. package/dist/root-path-existing.d.ts.map +1 -1
  173. package/dist/root-path-existing.js +56 -1
  174. package/dist/root-path.d.ts +1 -0
  175. package/dist/root-path.d.ts.map +1 -1
  176. package/dist/root-path.js +74 -162
  177. package/dist/root-symlink-policy.d.ts +13 -0
  178. package/dist/root-symlink-policy.d.ts.map +1 -0
  179. package/dist/root-symlink-policy.js +34 -0
  180. package/dist/root-walk.d.ts +5 -4
  181. package/dist/root-walk.d.ts.map +1 -1
  182. package/dist/root-walk.js +99 -50
  183. package/dist/root-write-mode.d.ts.map +1 -1
  184. package/dist/root-write-mode.js +1 -4
  185. package/dist/root.d.ts +3 -1
  186. package/dist/root.d.ts.map +1 -1
  187. package/dist/secret-file.d.ts +1 -0
  188. package/dist/secret-file.d.ts.map +1 -1
  189. package/dist/secret-file.js +1 -0
  190. package/dist/secret-read-async.js +5 -5
  191. package/dist/secure-file.d.ts +1 -1
  192. package/dist/secure-file.d.ts.map +1 -1
  193. package/dist/secure-file.js +23 -13
  194. package/dist/sibling-staged-file.js +7 -7
  195. package/dist/sibling-temp.d.ts.map +1 -1
  196. package/dist/sibling-temp.js +16 -7
  197. package/dist/sidecar-lock-acquire.d.ts +1 -1
  198. package/dist/sidecar-lock-acquire.d.ts.map +1 -1
  199. package/dist/sidecar-lock-acquire.js +6 -4
  200. package/dist/sidecar-lock-policy.d.ts.map +1 -1
  201. package/dist/sidecar-lock-policy.js +5 -3
  202. package/dist/sidecar-lock-reclaim.d.ts.map +1 -1
  203. package/dist/sidecar-lock-reclaim.js +16 -4
  204. package/dist/sidecar-lock.d.ts.map +1 -1
  205. package/dist/sidecar-lock.js +2 -1
  206. package/dist/temp-cleanup.d.ts.map +1 -1
  207. package/dist/temp-cleanup.js +3 -2
  208. package/dist/temp-target.d.ts.map +1 -1
  209. package/dist/temp-target.js +9 -4
  210. package/dist/timing.d.ts +1 -0
  211. package/dist/timing.d.ts.map +1 -1
  212. package/dist/timing.js +25 -6
  213. package/dist/trash.js +2 -2
  214. package/dist/walk.d.ts.map +1 -1
  215. package/dist/walk.js +7 -27
  216. package/dist/windows-owner.d.ts +9 -12
  217. package/dist/windows-owner.d.ts.map +1 -1
  218. package/dist/windows-owner.js +24 -58
  219. package/dist/write-file-handle.d.ts +6 -0
  220. package/dist/write-file-handle.d.ts.map +1 -0
  221. package/dist/write-file-handle.js +25 -0
  222. package/dist/write-open-flags.d.ts.map +1 -1
  223. package/dist/write-open-flags.js +1 -2
  224. package/docs/advanced.md +18 -4
  225. package/docs/archive.md +57 -9
  226. package/docs/atomic.md +7 -0
  227. package/docs/contributing.md +7 -0
  228. package/docs/copy.md +86 -0
  229. package/docs/durability.md +17 -2
  230. package/docs/errors.md +1 -1
  231. package/docs/file-store.md +48 -3
  232. package/docs/json-store.md +10 -0
  233. package/docs/local-roots.md +8 -1
  234. package/docs/native.md +14 -1
  235. package/docs/permissions.md +20 -10
  236. package/docs/positional-read.md +63 -0
  237. package/docs/reading.md +6 -0
  238. package/docs/root.md +168 -6
  239. package/docs/secret-file.md +6 -0
  240. package/docs/security-model.md +14 -0
  241. package/docs/sidecar-lock.md +2 -0
  242. package/docs/timing.md +2 -0
  243. package/docs/types.md +18 -11
  244. package/docs/walk.md +54 -5
  245. package/docs/writing.md +25 -5
  246. package/package.json +16 -11
@@ -0,0 +1,191 @@
1
+ import { setMaxListeners } from "node:events";
2
+ import fs from "node:fs";
3
+ import fsp, {} from "node:fs/promises";
4
+ import path from "node:path";
5
+ import { FsSafeError } from "./errors.js";
6
+ import { assertStagedDirectoryCurrent, exactIdentityMatches, openStagedDirectory, } from "./staged-directory.js";
7
+ /** Byte-copy adapter for the shared immutable-source, caller-owned namespace contract. */
8
+ export async function copyOwnedTree(source, destination, options) {
9
+ options.signal?.throwIfAborted();
10
+ const cancellation = new AbortController();
11
+ const signal = cancellation.signal;
12
+ setMaxListeners(options.concurrency, signal);
13
+ const abort = () => cancellation.abort(options.signal?.reason);
14
+ options.signal?.addEventListener("abort", abort, { once: true });
15
+ const pending = new Set();
16
+ const buffers = [];
17
+ const settle = async () => {
18
+ await Promise.all(pending);
19
+ };
20
+ async function schedule(operation) {
21
+ signal.throwIfAborted();
22
+ const task = operation()
23
+ .catch((error) => {
24
+ cancellation.abort(error);
25
+ })
26
+ .finally(() => {
27
+ pending.delete(task);
28
+ });
29
+ pending.add(task);
30
+ if (pending.size >= options.concurrency)
31
+ await Promise.race(pending);
32
+ signal.throwIfAborted();
33
+ }
34
+ async function copyFile(from, to, stat) {
35
+ const input = await fsp.open(from, fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW | fs.constants.O_NONBLOCK);
36
+ let output;
37
+ try {
38
+ const opened = await input.stat({ bigint: true });
39
+ if (!opened.isFile() || !exactIdentityMatches(stat, opened)) {
40
+ throw new FsSafeError("path-mismatch", "copy source changed while opening");
41
+ }
42
+ output = await fsp.open(to, "wx", 0o600);
43
+ if (options.copyFileContents) {
44
+ // napi-rs owns onabort. Each admitted file gets a separate signal so
45
+ // concurrent native copies cannot replace one another's cancellation.
46
+ const fileCancellation = new AbortController();
47
+ const abortFile = () => fileCancellation.abort();
48
+ signal.addEventListener("abort", abortFile, { once: true });
49
+ try {
50
+ signal.throwIfAborted();
51
+ await options.copyFileContents(input.fd, output.fd, fileCancellation.signal);
52
+ }
53
+ finally {
54
+ signal.removeEventListener("abort", abortFile);
55
+ }
56
+ }
57
+ else {
58
+ const buffer = buffers.pop() ??
59
+ Buffer.allocUnsafe(process.platform === "win32" ? 1024 * 1024 : 128 * 1024);
60
+ try {
61
+ let position = 0;
62
+ while (true) {
63
+ signal?.throwIfAborted();
64
+ const { bytesRead } = await input.read(buffer, 0, buffer.length, position);
65
+ if (bytesRead === 0)
66
+ break;
67
+ let written = 0;
68
+ while (written < bytesRead) {
69
+ signal?.throwIfAborted();
70
+ const { bytesWritten } = await output.write(buffer, written, bytesRead - written, position + written);
71
+ if (bytesWritten === 0)
72
+ throw new Error("copy write made no progress");
73
+ written += bytesWritten;
74
+ }
75
+ position += bytesRead;
76
+ }
77
+ }
78
+ finally {
79
+ buffers.push(buffer);
80
+ }
81
+ }
82
+ signal.throwIfAborted();
83
+ if (process.platform !== "win32")
84
+ await output.chmod(Number(stat.mode & 4095n));
85
+ await output.utimes(stat.atime, stat.mtime);
86
+ }
87
+ catch (error) {
88
+ cancellation.abort(error);
89
+ throw error;
90
+ }
91
+ finally {
92
+ try {
93
+ await output?.close();
94
+ }
95
+ finally {
96
+ await input.close();
97
+ }
98
+ }
99
+ }
100
+ async function copyDirectory(from, to) {
101
+ signal?.throwIfAborted();
102
+ const stat = await fsp.lstat(from, { bigint: true });
103
+ // Exclusive admission prevents merging a pre-existing directory, including
104
+ // any destination left by an unsuccessful native clone.
105
+ await fsp.mkdir(to, { mode: 0o700 });
106
+ const target = openStagedDirectory(to);
107
+ let original;
108
+ try {
109
+ original = openStagedDirectory(from);
110
+ if (!exactIdentityMatches(stat, original.receipt.identity)) {
111
+ throw new FsSafeError("path-mismatch", "copy source directory changed while opening");
112
+ }
113
+ for (const entry of await fsp.readdir(from, { withFileTypes: true })) {
114
+ signal?.throwIfAborted();
115
+ assertStagedDirectoryCurrent(target.receipt);
116
+ assertStagedDirectoryCurrent(original.receipt);
117
+ const childSource = path.join(from, entry.name);
118
+ const childTarget = path.join(to, entry.name);
119
+ const child = await fsp.lstat(childSource, { bigint: true });
120
+ if (child.isDirectory()) {
121
+ await copyDirectory(childSource, childTarget);
122
+ }
123
+ else if (child.isFile()) {
124
+ await schedule(() => copyFile(childSource, childTarget, child));
125
+ }
126
+ else if (child.isSymbolicLink()) {
127
+ const link = process.platform === "win32"
128
+ ? await fsp.readlink(childSource)
129
+ : await fsp.readlink(childSource, { encoding: "buffer" });
130
+ let type;
131
+ if (process.platform === "win32") {
132
+ // Older Node releases infer link type from the destination, where a
133
+ // relative directory target may not exist yet. Inspect the source.
134
+ // lstat does not expose the Windows link's directory attribute, so
135
+ // an unresolved source cannot be recreated with a proven type.
136
+ try {
137
+ type = (await fsp.stat(childSource)).isDirectory() ? "dir" : "file";
138
+ }
139
+ catch (error) {
140
+ if (error instanceof Error &&
141
+ "code" in error &&
142
+ (error.code === "ENOENT" || error.code === "ENOTDIR" || error.code === "ELOOP")) {
143
+ throw new FsSafeError("unsupported-platform", "byte copying cannot determine the type of an unresolved Windows symbolic link", { cause: error });
144
+ }
145
+ throw error;
146
+ }
147
+ }
148
+ signal?.throwIfAborted();
149
+ await fsp.symlink(link, childTarget, type);
150
+ }
151
+ else {
152
+ throw new FsSafeError("not-file", "tree copying does not support special files");
153
+ }
154
+ }
155
+ await settle();
156
+ signal.throwIfAborted();
157
+ assertStagedDirectoryCurrent(original.receipt);
158
+ assertStagedDirectoryCurrent(target.receipt);
159
+ if (process.platform !== "win32")
160
+ fs.fchmodSync(target.fd, Number(stat.mode & 4095n));
161
+ await fsp.utimes(to, stat.atime, stat.mtime);
162
+ assertStagedDirectoryCurrent(target.receipt);
163
+ }
164
+ catch (error) {
165
+ cancellation.abort(error);
166
+ throw error;
167
+ }
168
+ finally {
169
+ // Directory descriptors and destination ownership outlive every admitted
170
+ // write, including when traversal, a sibling copy, or cancellation fails.
171
+ await settle();
172
+ try {
173
+ if (original)
174
+ fs.closeSync(original.fd);
175
+ }
176
+ finally {
177
+ fs.closeSync(target.fd);
178
+ }
179
+ }
180
+ }
181
+ try {
182
+ await copyDirectory(source.receipt.realPath, destination);
183
+ }
184
+ catch (error) {
185
+ options.signal?.throwIfAborted();
186
+ throw error;
187
+ }
188
+ finally {
189
+ options.signal?.removeEventListener("abort", abort);
190
+ }
191
+ }
package/dist/copy.d.ts ADDED
@@ -0,0 +1,16 @@
1
+ import { type CopyCloneMode } from "./copy-policy.js";
2
+ export { readCloneFileMetadata, type CloneFileMetadata } from "./clone-metadata.js";
3
+ export type { CopyCloneMode } from "./copy-policy.js";
4
+ export type TreeCloneBackend = "apfs" | "btrfs" | "refs" | "xfs";
5
+ export type CopyTreeOptions = {
6
+ clone?: CopyCloneMode;
7
+ signal?: AbortSignal;
8
+ concurrency?: number;
9
+ };
10
+ /** Inspect an existing real directory without creating probe files. */
11
+ export declare function probeTreeClone(parentPath: string): TreeCloneBackend | undefined;
12
+ /** Create an empty clone source: a Btrfs subvolume or an ordinary supported directory. */
13
+ export declare function createCloneSource(destination: string, options?: Pick<CopyTreeOptions, "signal">): Promise<void>;
14
+ /** Copy an immutable, caller-owned tree, preferring native cloning unless configured otherwise. */
15
+ export declare function copyTree(source: string, destination: string, options?: CopyTreeOptions): Promise<void>;
16
+ //# sourceMappingURL=copy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"copy.d.ts","sourceRoot":"","sources":["../src/copy.ts"],"names":[],"mappings":"AAGA,OAAO,EAAwB,KAAK,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAK5E,OAAO,EAAE,qBAAqB,EAAE,KAAK,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACpF,YAAY,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAEtD,MAAM,MAAM,gBAAgB,GAAG,MAAM,GAAG,OAAO,GAAG,MAAM,GAAG,KAAK,CAAC;AACjE,MAAM,MAAM,eAAe,GAAG;IAC5B,KAAK,CAAC,EAAE,aAAa,CAAC;IACtB,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF,uEAAuE;AACvE,wBAAgB,cAAc,CAAC,UAAU,EAAE,MAAM,GAAG,gBAAgB,GAAG,SAAS,CAS/E;AAED,0FAA0F;AAC1F,wBAAsB,iBAAiB,CACrC,WAAW,EAAE,MAAM,EACnB,OAAO,GAAE,IAAI,CAAC,eAAe,EAAE,QAAQ,CAAM,GAC5C,OAAO,CAAC,IAAI,CAAC,CAEf;AAED,mGAAmG;AACnG,wBAAsB,QAAQ,CAC5B,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,MAAM,EACnB,OAAO,GAAE,eAAoB,GAC5B,OAAO,CAAC,IAAI,CAAC,CAIf"}
package/dist/copy.js ADDED
@@ -0,0 +1,123 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { assertAbsolutePathInput } from "./absolute-path.js";
4
+ import { resolveCopyCloneMode } from "./copy-policy.js";
5
+ import { copyOwnedTree } from "./copy-tree-portable.js";
6
+ import { FsSafeError } from "./errors.js";
7
+ import { getNativeBinding, requireNativeBinding } from "./native.js";
8
+ import { assertStagedDirectoryCurrent, openStagedDirectory } from "./staged-directory.js";
9
+ export { readCloneFileMetadata } from "./clone-metadata.js";
10
+ /** Inspect an existing real directory without creating probe files. */
11
+ export function probeTreeClone(parentPath) {
12
+ const native = getNativeBinding();
13
+ if (!native)
14
+ return undefined;
15
+ const parent = openStagedDirectory(assertAbsolutePathInput(parentPath));
16
+ try {
17
+ return native.probeTreeClone(parent.fd) ?? undefined;
18
+ }
19
+ finally {
20
+ fs.closeSync(parent.fd);
21
+ }
22
+ }
23
+ /** Create an empty clone source: a Btrfs subvolume or an ordinary supported directory. */
24
+ export async function createCloneSource(destination, options = {}) {
25
+ await materializeTree(undefined, destination, options, "always");
26
+ }
27
+ /** Copy an immutable, caller-owned tree, preferring native cloning unless configured otherwise. */
28
+ export async function copyTree(source, destination, options = {}) {
29
+ options.signal?.throwIfAborted();
30
+ const policy = resolveCopyCloneMode(options.clone, "auto");
31
+ await materializeTree(source, destination, options, policy);
32
+ }
33
+ function cloneUnavailable(error) {
34
+ if (error instanceof FsSafeError && error.code === "unsupported-platform")
35
+ return true;
36
+ if (!(error instanceof Error) || !("code" in error))
37
+ return false;
38
+ // ENOTSUP also describes unsupported contents (for example named streams).
39
+ // Only capability failures may select a byte copy, never lossy source errors.
40
+ return error.code === "CLONE_UNAVAILABLE" || error.code === "EXDEV" || error.code === "ENOSYS";
41
+ }
42
+ async function materializeTree(source, destination, options, policy) {
43
+ options.signal?.throwIfAborted();
44
+ const concurrency = options.concurrency ?? 16;
45
+ if (!Number.isInteger(concurrency) || concurrency < 1 || concurrency > 32) {
46
+ throw new FsSafeError("invalid-path", "clone concurrency must be an integer between 1 and 32");
47
+ }
48
+ const native = policy === "always"
49
+ ? requireNativeBinding()
50
+ : policy === "auto"
51
+ ? getNativeBinding()
52
+ : undefined;
53
+ const target = assertAbsolutePathInput(destination);
54
+ const name = path.basename(target);
55
+ if (!name)
56
+ throw new FsSafeError("invalid-path", "clone destination must name a child directory");
57
+ const parent = openStagedDirectory(path.dirname(target));
58
+ let original;
59
+ try {
60
+ const supported = native?.probeTreeClone(parent.fd);
61
+ if (!supported && policy === "always") {
62
+ throw new FsSafeError("unsupported-platform", "destination filesystem does not support tree cloning");
63
+ }
64
+ if (source !== undefined) {
65
+ original = openStagedDirectory(assertAbsolutePathInput(source));
66
+ const relative = path.relative(original.receipt.realPath, path.join(parent.receipt.realPath, name));
67
+ if (relative === "" ||
68
+ (!path.isAbsolute(relative) && relative !== ".." && !relative.startsWith(`..${path.sep}`))) {
69
+ throw new FsSafeError("invalid-path", "clone destination must be outside the source tree");
70
+ }
71
+ assertStagedDirectoryCurrent(original.receipt);
72
+ }
73
+ assertStagedDirectoryCurrent(parent.receipt);
74
+ options.signal?.throwIfAborted();
75
+ let cloned = false;
76
+ // napi-rs uses the supplied signal's onabort property. Give it a private
77
+ // signal so caller handlers and other admitted native operations stay intact.
78
+ const cancellation = options.signal && supported ? new AbortController() : undefined;
79
+ const abort = () => cancellation?.abort();
80
+ options.signal?.addEventListener("abort", abort, { once: true });
81
+ try {
82
+ if (native && supported) {
83
+ await native.cloneTree(original?.fd ?? null, parent.fd, name, concurrency, cancellation?.signal);
84
+ cloned = true;
85
+ }
86
+ }
87
+ catch (error) {
88
+ options.signal?.throwIfAborted();
89
+ if (policy !== "auto" || !cloneUnavailable(error)) {
90
+ if (error instanceof Error && "code" in error && error.code === "CLONE_UNAVAILABLE") {
91
+ throw new FsSafeError("unsupported-platform", error.message, { cause: error });
92
+ }
93
+ throw error;
94
+ }
95
+ }
96
+ finally {
97
+ options.signal?.removeEventListener("abort", abort);
98
+ }
99
+ options.signal?.throwIfAborted();
100
+ assertStagedDirectoryCurrent(parent.receipt);
101
+ if (original)
102
+ assertStagedDirectoryCurrent(original.receipt);
103
+ if (!cloned && original) {
104
+ await copyOwnedTree(original, target, {
105
+ signal: options.signal,
106
+ concurrency: options.concurrency ?? (process.platform === "win32" ? 4 : 1),
107
+ copyFileContents: process.platform === "win32" ? native?.copyFileContents : undefined,
108
+ });
109
+ options.signal?.throwIfAborted();
110
+ assertStagedDirectoryCurrent(parent.receipt);
111
+ assertStagedDirectoryCurrent(original.receipt);
112
+ }
113
+ }
114
+ finally {
115
+ try {
116
+ if (original)
117
+ fs.closeSync(original.fd);
118
+ }
119
+ finally {
120
+ fs.closeSync(parent.fd);
121
+ }
122
+ }
123
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"directory-mode-node.d.ts","sourceRoot":"","sources":["../src/directory-mode-node.ts"],"names":[],"mappings":"AAAA,OAAO,EAAa,KAAK,WAAW,EAAE,MAAM,SAAS,CAAC;AAKtD,OAAO,EAA0C,KAAK,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAW5G,2FAA2F;AAC3F,wBAAsB,uBAAuB,CAC3C,OAAO,EAAE,MAAM,EACf,OAAO,GAAE;IAAE,gBAAgB,CAAC,EAAE,WAAW,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAO,GAClE,OAAO,CAAC,kBAAkB,CAAC,CAsE7B"}
1
+ {"version":3,"file":"directory-mode-node.d.ts","sourceRoot":"","sources":["../src/directory-mode-node.ts"],"names":[],"mappings":"AAAA,OAAe,EAAa,KAAK,WAAW,EAAE,MAAM,SAAS,CAAC;AAK9D,OAAO,EAA0C,KAAK,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAW5G,2FAA2F;AAC3F,wBAAsB,uBAAuB,CAC3C,OAAO,EAAE,MAAM,EACf,OAAO,GAAE;IAAE,gBAAgB,CAAC,EAAE,WAAW,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAO,GAClE,OAAO,CAAC,kBAAkB,CAAC,CAsE7B"}
@@ -1,4 +1,4 @@
1
- import { constants } from "node:fs";
1
+ import fsSync, { constants } from "node:fs";
2
2
  import fs from "node:fs/promises";
3
3
  import { FsSafeError } from "./errors.js";
4
4
  import { inspectDirectoryIdentity } from "./directory-guard.js";
@@ -49,7 +49,7 @@ export async function pinNodeDirectoryForMode(dirPath, options = {}) {
49
49
  });
50
50
  try {
51
51
  const inspect = async () => {
52
- const opened = await inspectFileIdentity(() => handle.stat({ bigint: true }), expected);
52
+ const opened = await inspectFileIdentity(() => fsSync.fstatSync(handle.fd, { bigint: true }), expected);
53
53
  assertOwnedDirectory(expected, opened);
54
54
  assertOwner(opened);
55
55
  await inspectDirectoryIdentity(dirPath, expected);
@@ -63,8 +63,8 @@ export async function pinNodeDirectoryForMode(dirPath, options = {}) {
63
63
  if (namespace.type !== 0x9fa0n) {
64
64
  throw new FsSafeError("path-mismatch", "directory mode requires a trusted procfs fd namespace");
65
65
  }
66
- const opened = await inspectFileIdentity(() => handle.stat({ bigint: true }), expected);
67
- const followed = await inspectFileIdentity(() => fs.stat(procPath, { bigint: true }), expected);
66
+ const opened = await inspectFileIdentity(() => fsSync.fstatSync(handle.fd, { bigint: true }), expected);
67
+ const followed = await inspectFileIdentity(() => fsSync.statSync(procPath, { bigint: true }), expected);
68
68
  assertOwnedDirectory(opened, followed);
69
69
  assertOwner(opened);
70
70
  assertOwner(followed);
@@ -1,4 +1,4 @@
1
1
  export { ensureDurableDirectory, pinDirectory, syncDirectory, syncDirectoryBestEffort, syncDirectoryBestEffortSync, syncDirectorySync, type DirectoryReceipt, type DirectorySyncOutcome, type DurableDirectoryReceipt, type EnsureDurableDirectoryOptions, type PinnedDirectory, } from "./directory-durability.js";
2
2
  export { isHardlinkFallbackError, publishFileExclusive, type PublishFileExclusiveResult, type PublishFileExclusiveStrategy, type PublishFileExclusiveCleanup, type PublishFileExclusiveDirectorySyncFailure, type PublishFileExclusiveFailureDetails, type PublishFileExclusiveFailurePhase, type PublishFileExclusiveSyncFailurePolicy, } from "./publish-file.js";
3
- export { sha256File, type Sha256FileInput, type Sha256FileResult, } from "./file-hash.js";
3
+ export { sha256File, type Sha256FileInput, type Sha256FileOptions, type Sha256FileResult, } from "./file-hash.js";
4
4
  //# sourceMappingURL=durability.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"durability.d.ts","sourceRoot":"","sources":["../src/durability.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,sBAAsB,EACtB,YAAY,EACZ,aAAa,EACb,uBAAuB,EACvB,2BAA2B,EAC3B,iBAAiB,EACjB,KAAK,gBAAgB,EACrB,KAAK,oBAAoB,EACzB,KAAK,uBAAuB,EAC5B,KAAK,6BAA6B,EAClC,KAAK,eAAe,GACrB,MAAM,2BAA2B,CAAC;AACnC,OAAO,EACL,uBAAuB,EACvB,oBAAoB,EACpB,KAAK,0BAA0B,EAC/B,KAAK,4BAA4B,EACjC,KAAK,2BAA2B,EAChC,KAAK,wCAAwC,EAC7C,KAAK,kCAAkC,EACvC,KAAK,gCAAgC,EACrC,KAAK,qCAAqC,GAC3C,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACL,UAAU,EACV,KAAK,eAAe,EACpB,KAAK,gBAAgB,GACtB,MAAM,gBAAgB,CAAC"}
1
+ {"version":3,"file":"durability.d.ts","sourceRoot":"","sources":["../src/durability.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,sBAAsB,EACtB,YAAY,EACZ,aAAa,EACb,uBAAuB,EACvB,2BAA2B,EAC3B,iBAAiB,EACjB,KAAK,gBAAgB,EACrB,KAAK,oBAAoB,EACzB,KAAK,uBAAuB,EAC5B,KAAK,6BAA6B,EAClC,KAAK,eAAe,GACrB,MAAM,2BAA2B,CAAC;AACnC,OAAO,EACL,uBAAuB,EACvB,oBAAoB,EACpB,KAAK,0BAA0B,EAC/B,KAAK,4BAA4B,EACjC,KAAK,2BAA2B,EAChC,KAAK,wCAAwC,EAC7C,KAAK,kCAAkC,EACvC,KAAK,gCAAgC,EACrC,KAAK,qCAAqC,GAC3C,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACL,UAAU,EACV,KAAK,eAAe,EACpB,KAAK,iBAAiB,EACtB,KAAK,gBAAgB,GACtB,MAAM,gBAAgB,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"error-detail.d.ts","sourceRoot":"","sources":["../src/error-detail.ts"],"names":[],"mappings":"AAKA,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAIvD;AAED,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAI/C"}
1
+ {"version":3,"file":"error-detail.d.ts","sourceRoot":"","sources":["../src/error-detail.ts"],"names":[],"mappings":"AAMA,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAIvD;AAED,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAM/C"}
@@ -1,10 +1,13 @@
1
1
  import os from "node:os";
2
+ import path from "node:path";
2
3
  const UNSAFE_ERROR_DETAIL_CHARACTERS = /[\u0000-\u001f\u007f-\u009f\u2028\u2029]/gu;
3
4
  export function formatErrorDetail(value) {
4
5
  return value.replace(UNSAFE_ERROR_DETAIL_CHARACTERS, (character) => `\\u${character.charCodeAt(0).toString(16).padStart(4, "0")}`);
5
6
  }
6
7
  export function shortPath(value) {
7
8
  const home = os.homedir();
8
- const shortened = value.startsWith(home) ? `~${value.slice(home.length)}` : value;
9
+ const prefix = home.endsWith(path.sep) ? home : `${home}${path.sep}`;
10
+ const shortened = value === home ? "~"
11
+ : value.startsWith(prefix) ? `~${path.sep}${value.slice(prefix.length)}` : value;
9
12
  return formatErrorDetail(shortened);
10
13
  }
@@ -1,10 +1,14 @@
1
1
  import type { FileHandle } from "node:fs/promises";
2
2
  import { type NativeBinding } from "./native.js";
3
3
  export type Sha256FileInput = string | FileHandle;
4
+ export type Sha256FileOptions = {
5
+ maxBytes?: number;
6
+ signal?: AbortSignal;
7
+ };
4
8
  export type Sha256FileResult = {
5
9
  bytes: number;
6
10
  digest: string;
7
11
  };
8
- export declare function hashFileHandle(handle: FileHandle, native?: NativeBinding | undefined): Promise<Sha256FileResult>;
9
- export declare function sha256File(input: Sha256FileInput): Promise<Sha256FileResult>;
12
+ export declare function hashFileHandle(handle: FileHandle, native?: NativeBinding | undefined, { maxBytes, signal }?: Sha256FileOptions): Promise<Sha256FileResult>;
13
+ export declare function sha256File(input: Sha256FileInput, options?: Sha256FileOptions): Promise<Sha256FileResult>;
10
14
  //# sourceMappingURL=file-hash.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"file-hash.d.ts","sourceRoot":"","sources":["../src/file-hash.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAGnD,OAAO,EAAoB,KAAK,aAAa,EAAE,MAAM,aAAa,CAAC;AAInE,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,UAAU,CAAC;AAElD,MAAM,MAAM,gBAAgB,GAAG;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,wBAAsB,cAAc,CAClC,MAAM,EAAE,UAAU,EAClB,MAAM,GAAE,aAAa,GAAG,SAA8B,GACrD,OAAO,CAAC,gBAAgB,CAAC,CAoB3B;AA+CD,wBAAsB,UAAU,CAAC,KAAK,EAAE,eAAe,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAElF"}
1
+ {"version":3,"file":"file-hash.d.ts","sourceRoot":"","sources":["../src/file-hash.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAInD,OAAO,EAAoB,KAAK,aAAa,EAAE,MAAM,aAAa,CAAC;AAInE,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,UAAU,CAAC;AAElD,MAAM,MAAM,iBAAiB,GAAG;IAC9B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,wBAAsB,cAAc,CAClC,MAAM,EAAE,UAAU,EAClB,MAAM,GAAE,aAAa,GAAG,SAA8B,EACtD,EAAE,QAAmB,EAAE,MAAM,EAAE,GAAE,iBAAsB,GACtD,OAAO,CAAC,gBAAgB,CAAC,CAoD3B;AAoDD,wBAAsB,UAAU,CAC9B,KAAK,EAAE,eAAe,EACtB,OAAO,GAAE,iBAAsB,GAC9B,OAAO,CAAC,gBAAgB,CAAC,CAM3B"}
package/dist/file-hash.js CHANGED
@@ -1,22 +1,53 @@
1
1
  import { createHash } from "node:crypto";
2
+ import fsSync from "node:fs";
2
3
  import fs from "node:fs/promises";
4
+ import { normalizeMaxBytes } from "./byte-budget.js";
3
5
  import { FsSafeError } from "./errors.js";
4
6
  import { getNativeBinding } from "./native.js";
5
7
  import { resolveReadOpenFlags } from "./read-open-flags.js";
6
8
  import { inspectFileIdentity } from "./strict-file-identity.js";
7
- export async function hashFileHandle(handle, native = getNativeBinding()) {
8
- const stat = await handle.stat();
9
+ export async function hashFileHandle(handle, native = getNativeBinding(), { maxBytes = Infinity, signal } = {}) {
10
+ signal?.throwIfAborted();
11
+ const stat = fsSync.fstatSync(handle.fd);
9
12
  if (!stat.isFile()) {
10
13
  throw new FsSafeError("not-file", "SHA-256 input is not a regular file");
11
14
  }
15
+ if (stat.size > maxBytes) {
16
+ throw new FsSafeError("too-large", `SHA-256 input exceeds ${maxBytes} bytes`);
17
+ }
12
18
  if (native) {
13
- return await native.sha256File(handle.fd);
19
+ // A completed N-API task can mask later aborts on the same signal.
20
+ const nativeSignal = signal ? AbortSignal.any([signal]) : undefined;
21
+ try {
22
+ const result = await native.sha256File(handle.fd, Number.isFinite(maxBytes) ? maxBytes : undefined, nativeSignal);
23
+ signal?.throwIfAborted();
24
+ return result;
25
+ }
26
+ catch (error) {
27
+ // N-API settles only after compute stops; never race descriptor cleanup.
28
+ signal?.throwIfAborted();
29
+ if (error?.code === "too-large") {
30
+ throw new FsSafeError("too-large", `SHA-256 input exceeds ${maxBytes} bytes`, { cause: error });
31
+ }
32
+ throw error;
33
+ }
34
+ finally {
35
+ // Node retains composite signals while N-API's abort listener is attached.
36
+ if (nativeSignal)
37
+ nativeSignal.onabort = null;
38
+ }
14
39
  }
15
40
  const hash = createHash("sha256");
16
- const buffer = Buffer.allocUnsafe(64 * 1024);
41
+ const buffer = Buffer.allocUnsafe(Math.min(256 * 1024, Math.max(64 * 1024, stat.size)));
17
42
  let position = 0;
18
43
  while (true) {
19
- const { bytesRead } = await handle.read(buffer, 0, buffer.length, position);
44
+ signal?.throwIfAborted();
45
+ const length = Math.min(buffer.length, maxBytes - position + 1);
46
+ const { bytesRead } = await handle.read(buffer, 0, length, position);
47
+ signal?.throwIfAborted();
48
+ if (bytesRead > maxBytes - position) {
49
+ throw new FsSafeError("too-large", `SHA-256 input exceeds ${maxBytes} bytes`);
50
+ }
20
51
  if (bytesRead === 0) {
21
52
  return { bytes: position, digest: hash.digest("hex") };
22
53
  }
@@ -24,9 +55,9 @@ export async function hashFileHandle(handle, native = getNativeBinding()) {
24
55
  position += bytesRead;
25
56
  }
26
57
  }
27
- async function hashPath(filePath) {
58
+ async function hashPath(filePath, options) {
28
59
  const before = await inspectFileIdentity(async () => {
29
- const stat = await fs.lstat(filePath, { bigint: true });
60
+ const stat = fsSync.lstatSync(filePath, { bigint: true });
30
61
  if (stat.isSymbolicLink()) {
31
62
  throw new FsSafeError("symlink", "SHA-256 path must not be a symbolic link");
32
63
  }
@@ -37,6 +68,7 @@ async function hashPath(filePath) {
37
68
  });
38
69
  let handle;
39
70
  try {
71
+ options.signal?.throwIfAborted();
40
72
  handle = await fs.open(filePath, resolveReadOpenFlags());
41
73
  }
42
74
  catch (error) {
@@ -48,26 +80,31 @@ async function hashPath(filePath) {
48
80
  throw error;
49
81
  }
50
82
  try {
83
+ options.signal?.throwIfAborted();
51
84
  const opened = await inspectFileIdentity(async () => {
52
- const stat = await handle.stat({ bigint: true });
85
+ const stat = fsSync.fstatSync(handle.fd, { bigint: true });
53
86
  if (!stat.isFile()) {
54
87
  throw new FsSafeError("not-file", "SHA-256 path is not a regular file");
55
88
  }
56
89
  return stat;
57
90
  }, before);
58
91
  await inspectFileIdentity(async () => {
59
- const stat = await fs.lstat(filePath, { bigint: true });
92
+ const stat = fsSync.lstatSync(filePath, { bigint: true });
60
93
  if (stat.isSymbolicLink() || !stat.isFile()) {
61
94
  throw new FsSafeError("path-mismatch", "SHA-256 path changed while opening");
62
95
  }
63
96
  return stat;
64
97
  }, opened);
65
- return await hashFileHandle(handle);
98
+ return await hashFileHandle(handle, getNativeBinding(), options);
66
99
  }
67
100
  finally {
68
101
  await handle.close().catch(() => undefined);
69
102
  }
70
103
  }
71
- export async function sha256File(input) {
72
- return typeof input === "string" ? await hashPath(input) : await hashFileHandle(input);
104
+ export async function sha256File(input, options = {}) {
105
+ options.signal?.throwIfAborted();
106
+ const normalized = { maxBytes: normalizeMaxBytes(options.maxBytes), signal: options.signal };
107
+ return typeof input === "string"
108
+ ? await hashPath(input, normalized)
109
+ : await hashFileHandle(input, getNativeBinding(), normalized);
73
110
  }
@@ -3,6 +3,7 @@ export declare function writeFileSyncAtomic(params: {
3
3
  filePath: string;
4
4
  content: string | Uint8Array;
5
5
  privateMode: boolean;
6
+ durable: boolean;
6
7
  dirMode: number;
7
8
  mode: number;
8
9
  }): string;
@@ -1 +1 @@
1
- {"version":3,"file":"file-store-sync-write.d.ts","sourceRoot":"","sources":["../src/file-store-sync-write.ts"],"names":[],"mappings":"AAwBA,wBAAgB,mBAAmB,CAAC,MAAM,EAAE;IAC1C,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,GAAG,UAAU,CAAC;IAC7B,WAAW,EAAE,OAAO,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;CACd,GAAG,MAAM,CA6GT"}
1
+ {"version":3,"file":"file-store-sync-write.d.ts","sourceRoot":"","sources":["../src/file-store-sync-write.ts"],"names":[],"mappings":"AAwBA,wBAAgB,mBAAmB,CAAC,MAAM,EAAE;IAC1C,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,GAAG,UAAU,CAAC;IAC7B,WAAW,EAAE,OAAO,CAAC;IACrB,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;CACd,GAAG,MAAM,CA+GT"}
@@ -64,7 +64,8 @@ export function writeFileSyncAtomic(params) {
64
64
  if (!opened.isFile() || opened.nlink > 1) {
65
65
  throw new FsSafeError("path-mismatch", "store temp is not an owned regular file");
66
66
  }
67
- fs.fsyncSync(descriptor);
67
+ if (params.durable)
68
+ fs.fsyncSync(descriptor);
68
69
  return opened;
69
70
  }
70
71
  finally {
@@ -105,11 +106,13 @@ export function writeFileSyncAtomic(params) {
105
106
  }
106
107
  if (parentGuard) {
107
108
  assertSyncDirectoryGuard(parentGuard);
108
- syncDirectorySync({
109
- path: parentGuard.dir,
110
- realPath: parentGuard.realPath,
111
- identity: parentGuard.stat,
112
- }, { label: "store parent" });
109
+ if (params.durable) {
110
+ syncDirectorySync({
111
+ path: parentGuard.dir,
112
+ realPath: parentGuard.realPath,
113
+ identity: parentGuard.stat,
114
+ }, { label: "store parent" });
115
+ }
113
116
  assertSyncDirectoryGuard(parentGuard);
114
117
  }
115
118
  return filePath;
@@ -6,11 +6,15 @@ import { type OpenResult, type ReadResult, type Root, type RootReadOptions } fro
6
6
  export type FileStoreOptions = {
7
7
  rootDir: string;
8
8
  private?: boolean;
9
+ /** Default for writes, streams, and copies; true syncs file and parent where supported. */
10
+ durable?: boolean;
9
11
  dirMode?: number;
10
12
  mode?: number;
11
13
  maxBytes?: number;
12
14
  };
13
15
  export type FileStoreWriteOptions = {
16
+ /** Override store durability; defaults to the store option, then true. */
17
+ durable?: boolean;
14
18
  dirMode?: number;
15
19
  mode?: number;
16
20
  maxBytes?: number;
@@ -1 +1 @@
1
- {"version":3,"file":"file-store.d.ts","sourceRoot":"","sources":["../src/file-store.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAK5C,OAAO,EAA4B,KAAK,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AAC7F,YAAY,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AAQnE,OAAO,EAAmB,KAAK,oBAAoB,EAAE,KAAK,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAItG,OAAO,EAAQ,KAAK,UAAU,EAAE,KAAK,UAAU,EAAE,KAAK,IAAI,EAAE,KAAK,eAAe,EAAE,MAAM,WAAW,CAAC;AAMpG,MAAM,MAAM,gBAAgB,GAAG;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,qBAAqB,GAAG;IAClC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG,eAAe,GAAG;IAAE,QAAQ,CAAC,EAAE,cAAc,CAAA;CAAE,CAAC;AAEnF,MAAM,MAAM,SAAS,GAAG;IACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,IAAI,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAAC;IACnC,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACtB,KAAK,CACH,YAAY,EAAE,MAAM,EACpB,IAAI,EAAE,MAAM,GAAG,UAAU,EACzB,OAAO,CAAC,EAAE,qBAAqB,GAC9B,OAAO,CAAC,MAAM,CAAC,CAAC;IACnB,WAAW,CACT,YAAY,EAAE,MAAM,EACpB,MAAM,EAAE,QAAQ,EAChB,OAAO,CAAC,EAAE,qBAAqB,GAC9B,OAAO,CAAC,MAAM,CAAC,CAAC;IACnB,MAAM,CACJ,YAAY,EAAE,MAAM,EACpB,UAAU,EAAE,MAAM,EAClB,OAAO,CAAC,EAAE,qBAAqB,GAC9B,OAAO,CAAC,MAAM,CAAC,CAAC;IACnB,IAAI,CAAC,YAAY,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,eAAe,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;IAC3E,IAAI,CAAC,YAAY,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,eAAe,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;IAC3E,SAAS,CAAC,YAAY,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,eAAe,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAC5E,QAAQ,CACN,YAAY,EAAE,MAAM,EACpB,OAAO,CAAC,EAAE,oBAAoB,GAC7B,OAAO,CAAC,MAAM,CAAC,CAAC;IACnB,gBAAgB,CAAC,YAAY,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,oBAAoB,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC/F,QAAQ,CAAC,CAAC,GAAG,OAAO,EAAE,YAAY,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,oBAAoB,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IACxF,gBAAgB,CAAC,CAAC,GAAG,OAAO,EAC1B,YAAY,EAAE,MAAM,EACpB,OAAO,CAAC,EAAE,oBAAoB,GAC7B,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;IACrB,MAAM,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5C,MAAM,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC/C,SAAS,CACP,YAAY,EAAE,MAAM,EACpB,IAAI,EAAE,MAAM,GAAG,UAAU,EACzB,OAAO,CAAC,EAAE,qBAAqB,GAC9B,OAAO,CAAC,MAAM,CAAC,CAAC;IACnB,SAAS,CACP,YAAY,EAAE,MAAM,EACpB,IAAI,EAAE,OAAO,EACb,OAAO,CAAC,EAAE,qBAAqB,GAAG;QAAE,eAAe,CAAC,EAAE,OAAO,CAAA;KAAE,GAC9D,OAAO,CAAC,MAAM,CAAC,CAAC;IACnB,IAAI,CAAC,CAAC,GAAG,OAAO,EAAE,YAAY,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,oBAAoB,GAAG,SAAS,CAAC,CAAC,CAAC,CAAC;IACtF,YAAY,CAAC,OAAO,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC7D,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAC1B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,IAAI,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAAC;IACnC,gBAAgB,CAAC,YAAY,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,MAAM,GAAG,IAAI,CAAC;IACvF,gBAAgB,CAAC,CAAC,GAAG,OAAO,EAAE,YAAY,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,CAAC,GAAG,IAAI,CAAC;IAC/F,KAAK,CAAC,YAAY,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,UAAU,EAAE,OAAO,CAAC,EAAE,qBAAqB,GAAG,MAAM,CAAC;IAChG,SAAS,CAAC,YAAY,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,UAAU,EAAE,OAAO,CAAC,EAAE,qBAAqB,GAAG,MAAM,CAAC;IACpG,SAAS,CACP,YAAY,EAAE,MAAM,EACpB,IAAI,EAAE,OAAO,EACb,OAAO,CAAC,EAAE,qBAAqB,GAAG;QAAE,eAAe,CAAC,EAAE,OAAO,CAAA;KAAE,GAC9D,MAAM,CAAC;CACX,CAAC;AAmHF,wBAAgB,SAAS,CAAC,OAAO,EAAE,gBAAgB,GAAG,SAAS,CA0M9D;AAED,wBAAgB,aAAa,CAAC,OAAO,EAAE,gBAAgB,GAAG,aAAa,CAsEtE"}
1
+ {"version":3,"file":"file-store.d.ts","sourceRoot":"","sources":["../src/file-store.ts"],"names":[],"mappings":"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"}