@openclaw/fs-safe 0.4.7 → 0.5.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 (239) hide show
  1. package/CHANGELOG.md +45 -0
  2. package/README.md +82 -43
  3. package/dist/archive-entry.d.ts.map +1 -1
  4. package/dist/archive-entry.js +5 -4
  5. package/dist/archive-errors.d.ts +11 -0
  6. package/dist/archive-errors.d.ts.map +1 -0
  7. package/dist/archive-errors.js +16 -0
  8. package/dist/archive-input.d.ts +12 -0
  9. package/dist/archive-input.d.ts.map +1 -0
  10. package/dist/archive-input.js +72 -0
  11. package/dist/archive-kind.d.ts +1 -1
  12. package/dist/archive-kind.d.ts.map +1 -1
  13. package/dist/archive-kind.js +18 -0
  14. package/dist/archive-limits.d.ts +10 -0
  15. package/dist/archive-limits.d.ts.map +1 -1
  16. package/dist/archive-limits.js +18 -0
  17. package/dist/archive-native.d.ts +18 -0
  18. package/dist/archive-native.d.ts.map +1 -0
  19. package/dist/archive-native.js +115 -0
  20. package/dist/archive-options.d.ts +21 -0
  21. package/dist/archive-options.d.ts.map +1 -0
  22. package/dist/archive-options.js +1 -0
  23. package/dist/archive-policy.d.ts +20 -0
  24. package/dist/archive-policy.d.ts.map +1 -0
  25. package/dist/archive-policy.js +29 -0
  26. package/dist/archive-read.d.ts +6 -0
  27. package/dist/archive-read.d.ts.map +1 -0
  28. package/dist/archive-read.js +200 -0
  29. package/dist/archive-staging.d.ts +2 -5
  30. package/dist/archive-staging.d.ts.map +1 -1
  31. package/dist/archive-staging.js +19 -10
  32. package/dist/archive-tar-meta.d.ts +6 -0
  33. package/dist/archive-tar-meta.d.ts.map +1 -0
  34. package/dist/archive-tar-meta.js +143 -0
  35. package/dist/archive-tar-runtime.d.ts +43 -0
  36. package/dist/archive-tar-runtime.d.ts.map +1 -0
  37. package/dist/archive-tar-runtime.js +8 -0
  38. package/dist/archive-tar.d.ts +5 -1
  39. package/dist/archive-tar.d.ts.map +1 -1
  40. package/dist/archive-tar.js +24 -6
  41. package/dist/archive-zip-entry.d.ts +15 -0
  42. package/dist/archive-zip-entry.d.ts.map +1 -0
  43. package/dist/archive-zip-entry.js +17 -0
  44. package/dist/archive.d.ts +7 -17
  45. package/dist/archive.d.ts.map +1 -1
  46. package/dist/archive.js +111 -108
  47. package/dist/atomic.d.ts +1 -1
  48. package/dist/atomic.d.ts.map +1 -1
  49. package/dist/config.d.ts +1 -1
  50. package/dist/config.d.ts.map +1 -1
  51. package/dist/config.js +1 -1
  52. package/dist/containment.d.ts +2 -0
  53. package/dist/containment.d.ts.map +1 -0
  54. package/dist/containment.js +1 -0
  55. package/dist/durability.d.ts +2 -0
  56. package/dist/durability.d.ts.map +1 -1
  57. package/dist/durability.js +2 -0
  58. package/dist/errors.d.ts +4 -1
  59. package/dist/errors.d.ts.map +1 -1
  60. package/dist/errors.js +2 -0
  61. package/dist/file-hash.d.ts +10 -0
  62. package/dist/file-hash.d.ts.map +1 -0
  63. package/dist/file-hash.js +73 -0
  64. package/dist/file-lock-sync.d.ts +35 -0
  65. package/dist/file-lock-sync.d.ts.map +1 -0
  66. package/dist/file-lock-sync.js +233 -0
  67. package/dist/file-lock.d.ts +2 -0
  68. package/dist/file-lock.d.ts.map +1 -1
  69. package/dist/file-lock.js +1 -0
  70. package/dist/filename.d.ts.map +1 -1
  71. package/dist/filename.js +4 -1
  72. package/dist/guarded-mkdir.d.ts +7 -1
  73. package/dist/guarded-mkdir.d.ts.map +1 -1
  74. package/dist/guarded-mkdir.js +45 -3
  75. package/dist/index.d.ts +4 -3
  76. package/dist/index.d.ts.map +1 -1
  77. package/dist/index.js +1 -1
  78. package/dist/json-document-store.d.ts.map +1 -1
  79. package/dist/json-document-store.js +30 -15
  80. package/dist/move-path.d.ts.map +1 -1
  81. package/dist/move-path.js +101 -17
  82. package/dist/native/darwin-arm64/fs-safe-native.node +0 -0
  83. package/dist/native/darwin-x64/fs-safe-native.node +0 -0
  84. package/dist/native/linux-arm64-gnu/fs-safe-native.node +0 -0
  85. package/dist/native/linux-arm64-musl/fs-safe-native.node +0 -0
  86. package/dist/native/linux-x64-gnu/fs-safe-native.node +0 -0
  87. package/dist/native/linux-x64-musl/fs-safe-native.node +0 -0
  88. package/dist/native/win32-x64-msvc/fs-safe-native.node +0 -0
  89. package/dist/native-binding.d.ts +79 -0
  90. package/dist/native-binding.d.ts.map +1 -0
  91. package/dist/native-binding.js +1 -0
  92. package/dist/native-config.d.ts +17 -0
  93. package/dist/native-config.d.ts.map +1 -0
  94. package/dist/native-config.js +71 -0
  95. package/dist/native-operations.d.ts +21 -0
  96. package/dist/native-operations.d.ts.map +1 -0
  97. package/dist/native-operations.js +115 -0
  98. package/dist/native-pinned-write.d.ts +5 -0
  99. package/dist/native-pinned-write.d.ts.map +1 -0
  100. package/dist/native-pinned-write.js +115 -0
  101. package/dist/native.d.ts +14 -0
  102. package/dist/native.d.ts.map +1 -0
  103. package/dist/native.js +204 -0
  104. package/dist/output-sibling.d.ts +8 -0
  105. package/dist/output-sibling.d.ts.map +1 -0
  106. package/dist/output-sibling.js +122 -0
  107. package/dist/output.d.ts +2 -0
  108. package/dist/output.d.ts.map +1 -1
  109. package/dist/output.js +32 -5
  110. package/dist/owner-dacl.d.ts +31 -0
  111. package/dist/owner-dacl.d.ts.map +1 -0
  112. package/dist/owner-dacl.js +27 -0
  113. package/dist/path-policy.js +1 -1
  114. package/dist/permissions-public.d.ts +2 -0
  115. package/dist/permissions-public.d.ts.map +1 -1
  116. package/dist/permissions-public.js +2 -0
  117. package/dist/permissions.d.ts.map +1 -1
  118. package/dist/permissions.js +6 -0
  119. package/dist/pinned-operation.d.ts +2 -0
  120. package/dist/pinned-operation.d.ts.map +1 -0
  121. package/dist/pinned-operation.js +35 -0
  122. package/dist/pinned-write.d.ts +3 -16
  123. package/dist/pinned-write.d.ts.map +1 -1
  124. package/dist/pinned-write.js +20 -85
  125. package/dist/private-directory.d.ts +5 -0
  126. package/dist/private-directory.d.ts.map +1 -0
  127. package/dist/private-directory.js +13 -0
  128. package/dist/private-temp-workspace.d.ts +6 -2
  129. package/dist/private-temp-workspace.d.ts.map +1 -1
  130. package/dist/private-temp-workspace.js +40 -13
  131. package/dist/publish-file-failure.d.ts +27 -0
  132. package/dist/publish-file-failure.d.ts.map +1 -0
  133. package/dist/publish-file-failure.js +21 -0
  134. package/dist/publish-file.d.ts +21 -0
  135. package/dist/publish-file.d.ts.map +1 -0
  136. package/dist/publish-file.js +351 -0
  137. package/dist/read-opened-file.d.ts +3 -0
  138. package/dist/read-opened-file.d.ts.map +1 -1
  139. package/dist/read-opened-file.js +1 -0
  140. package/dist/replace-file-copy-fallback.d.ts +34 -0
  141. package/dist/replace-file-copy-fallback.d.ts.map +1 -0
  142. package/dist/replace-file-copy-fallback.js +339 -0
  143. package/dist/replace-file.d.ts +6 -2
  144. package/dist/replace-file.d.ts.map +1 -1
  145. package/dist/replace-file.js +39 -90
  146. package/dist/root-context.d.ts +4 -1
  147. package/dist/root-context.d.ts.map +1 -1
  148. package/dist/root-context.js +20 -1
  149. package/dist/root-impl.d.ts +5 -0
  150. package/dist/root-impl.d.ts.map +1 -1
  151. package/dist/root-impl.js +128 -225
  152. package/dist/root-path-existing.d.ts +3 -0
  153. package/dist/root-path-existing.d.ts.map +1 -0
  154. package/dist/root-path-existing.js +69 -0
  155. package/dist/root-path.d.ts +1 -2
  156. package/dist/root-path.d.ts.map +1 -1
  157. package/dist/root-path.js +67 -81
  158. package/dist/root-walk.d.ts +41 -0
  159. package/dist/root-walk.d.ts.map +1 -0
  160. package/dist/root-walk.js +114 -0
  161. package/dist/root.d.ts +2 -0
  162. package/dist/root.d.ts.map +1 -1
  163. package/dist/secret-file.d.ts +5 -2
  164. package/dist/secret-file.d.ts.map +1 -1
  165. package/dist/secret-file.js +20 -2
  166. package/dist/secret-read-async.d.ts +4 -0
  167. package/dist/secret-read-async.d.ts.map +1 -0
  168. package/dist/secret-read-async.js +95 -0
  169. package/dist/secret.d.ts +2 -1
  170. package/dist/secret.d.ts.map +1 -1
  171. package/dist/secret.js +2 -1
  172. package/dist/sibling-temp.d.ts.map +1 -1
  173. package/dist/sibling-temp.js +1 -0
  174. package/dist/sidecar-lock-handle.d.ts +20 -0
  175. package/dist/sidecar-lock-handle.d.ts.map +1 -0
  176. package/dist/sidecar-lock-handle.js +28 -0
  177. package/dist/sidecar-lock-policy.d.ts +10 -0
  178. package/dist/sidecar-lock-policy.d.ts.map +1 -0
  179. package/dist/sidecar-lock-policy.js +29 -0
  180. package/dist/sidecar-lock-reclaim.d.ts +22 -6
  181. package/dist/sidecar-lock-reclaim.d.ts.map +1 -1
  182. package/dist/sidecar-lock-reclaim.js +106 -20
  183. package/dist/sidecar-lock-types.d.ts +56 -0
  184. package/dist/sidecar-lock-types.d.ts.map +1 -0
  185. package/dist/sidecar-lock-types.js +1 -0
  186. package/dist/sidecar-lock.d.ts +2 -45
  187. package/dist/sidecar-lock.d.ts.map +1 -1
  188. package/dist/sidecar-lock.js +94 -55
  189. package/dist/temp-cleanup.d.ts +7 -1
  190. package/dist/temp-cleanup.d.ts.map +1 -1
  191. package/dist/temp-cleanup.js +35 -5
  192. package/dist/temp.d.ts +2 -1
  193. package/dist/temp.d.ts.map +1 -1
  194. package/dist/test-hooks.d.ts +3 -0
  195. package/dist/test-hooks.d.ts.map +1 -1
  196. package/dist/windows-permissions-native.d.ts +9 -0
  197. package/dist/windows-permissions-native.d.ts.map +1 -0
  198. package/dist/windows-permissions-native.js +31 -0
  199. package/docs/archive.md +131 -13
  200. package/docs/atomic.md +30 -1
  201. package/docs/config.md +51 -21
  202. package/docs/durability.md +211 -1
  203. package/docs/errors.md +36 -6
  204. package/docs/filename.md +0 -0
  205. package/docs/index.md +11 -8
  206. package/docs/install.md +27 -26
  207. package/docs/json-store.md +18 -3
  208. package/docs/migrating-to-0.5.md +196 -0
  209. package/docs/native-helper.md +85 -0
  210. package/docs/native.md +134 -0
  211. package/docs/output.md +37 -11
  212. package/docs/path.md +1 -1
  213. package/docs/permissions.md +84 -1
  214. package/docs/quickstart.md +5 -2
  215. package/docs/reading.md +4 -4
  216. package/docs/root.md +31 -15
  217. package/docs/secret-file.md +50 -1
  218. package/docs/security-model.md +29 -8
  219. package/docs/sidecar-lock.md +92 -7
  220. package/docs/temp.md +24 -1
  221. package/docs/test-hooks.md +22 -2
  222. package/docs/testing.md +18 -7
  223. package/docs/types.md +3 -1
  224. package/docs/walk.md +55 -0
  225. package/docs/writing.md +3 -3
  226. package/package.json +8 -2
  227. package/dist/pinned-helper.d.ts +0 -9
  228. package/dist/pinned-helper.d.ts.map +0 -1
  229. package/dist/pinned-helper.js +0 -19
  230. package/dist/pinned-path.d.ts +0 -7
  231. package/dist/pinned-path.d.ts.map +0 -1
  232. package/dist/pinned-path.js +0 -21
  233. package/dist/pinned-python-config.d.ts +0 -9
  234. package/dist/pinned-python-config.d.ts.map +0 -1
  235. package/dist/pinned-python-config.js +0 -38
  236. package/dist/pinned-python.d.ts +0 -12
  237. package/dist/pinned-python.d.ts.map +0 -1
  238. package/dist/pinned-python.js +0 -688
  239. package/docs/python-helper.md +0 -103
package/dist/archive.js CHANGED
@@ -1,22 +1,30 @@
1
- import { constants as fsConstants } from "node:fs";
1
+ import fsSync, { constants as fsConstants } from "node:fs";
2
2
  import fs from "node:fs/promises";
3
3
  import path from "node:path";
4
4
  import { Readable } from "node:stream";
5
5
  import { pipeline } from "node:stream/promises";
6
6
  import { resolveArchiveOutputPath, stripArchivePath, validateArchiveEntryPath, } from "./archive-entry.js";
7
7
  import { createPipelineTimeoutError, waitForDeadline, withExtractionDeadline, } from "./archive-deadline.js";
8
- import { writeFileHandleFully } from "./archive-file-io.js";
9
- import { ARCHIVE_LIMIT_ERROR_CODE, ArchiveLimitError, assertArchiveEntryCountWithinLimit, createByteBudgetTracker, createExtractBudgetTransform, resolveExtractLimits, } from "./archive-limits.js";
8
+ import { assertArchiveEntryCountWithinLimit, assertArchiveEntryPathComponentsWithinLimit, createByteBudgetTracker, createExtractBudgetTransform, resolveExtractLimits, } from "./archive-limits.js";
10
9
  import { resolveArchiveKind } from "./archive-kind.js";
11
10
  import { mergeExtractedTreeIntoDestination, prepareArchiveDestinationDir, prepareArchiveOutputPath, withStagedArchiveDestination, } from "./archive-staging.js";
12
11
  import { createTarEntryPreflightChecker, readTarEntryInfo, } from "./archive-tar.js";
13
12
  import { loadZipArchiveWithPreflight } from "./archive-zip-preflight.js";
14
- import { sameFileIdentity } from "./file-identity.js";
13
+ import { isZipSymlinkEntry, zipEntryDeclaredSize, zipEntryMode, } from "./archive-zip-entry.js";
14
+ import { FsSafeError } from "./errors.js";
15
+ import { ArchiveSecurityError } from "./archive-errors.js";
16
+ import { extractNativeArchive } from "./archive-native.js";
17
+ import { stageArchiveFileForExtraction } from "./archive-input.js";
18
+ import { getNativeBinding } from "./native.js";
19
+ import { resolveArchiveEntryMode, shouldExtractArchiveEntry, } from "./archive-policy.js";
20
+ import { importOptionalTar } from "./archive-tar-runtime.js";
21
+ import { preflightTarMetadata } from "./archive-tar-meta.js";
15
22
  import { writeSiblingTempFile } from "./sibling-temp.js";
16
- import { tempFile } from "./temp-target.js";
17
23
  export { isWindowsDrivePath, normalizeArchiveEntryPath, resolveArchiveOutputPath, stripArchivePath, validateArchiveEntryPath, } from "./archive-entry.js";
18
24
  export { resolveArchiveKind, resolvePackedRootDir } from "./archive-kind.js";
19
- export { ARCHIVE_LIMIT_ERROR_CODE, ArchiveLimitError, DEFAULT_MAX_ARCHIVE_BYTES_ZIP, DEFAULT_MAX_ENTRIES, DEFAULT_MAX_EXTRACTED_BYTES, DEFAULT_MAX_ENTRY_BYTES, } from "./archive-limits.js";
25
+ export { readArchiveEntry } from "./archive-read.js";
26
+ export { ARCHIVE_LIMIT_ERROR_CODE, ArchiveLimitError, DEFAULT_MAX_ARCHIVE_BYTES_ZIP, DEFAULT_MAX_ENTRIES, DEFAULT_MAX_EXTRACTED_BYTES, DEFAULT_MAX_ENTRY_BYTES, DEFAULT_MAX_META_ENTRY_BYTES, DEFAULT_MAX_ENTRY_PATH_COMPONENTS, } from "./archive-limits.js";
27
+ export { ArchiveFormatError } from "./archive-errors.js";
20
28
  export { ArchiveSecurityError } from "./archive-staging.js";
21
29
  export { createArchiveSymlinkTraversalError, mergeExtractedTreeIntoDestination, prepareArchiveDestinationDir, prepareArchiveOutputPath, withStagedArchiveDestination, } from "./archive-staging.js";
22
30
  export { createTarEntryPreflightChecker } from "./archive-tar.js";
@@ -26,91 +34,6 @@ const OPEN_WRITE_CREATE_FLAGS = fsConstants.O_WRONLY |
26
34
  fsConstants.O_CREAT |
27
35
  fsConstants.O_EXCL |
28
36
  (SUPPORTS_NOFOLLOW ? fsConstants.O_NOFOLLOW : 0);
29
- const ZIP_UNIX_FILE_TYPE_MASK = 0o170000;
30
- const ZIP_UNIX_SYMLINK_TYPE = 0o120000;
31
- function isZipSymlinkEntry(entry) {
32
- return (typeof entry.unixPermissions === "number" &&
33
- (entry.unixPermissions & ZIP_UNIX_FILE_TYPE_MASK) === ZIP_UNIX_SYMLINK_TYPE);
34
- }
35
- function zipEntryFileMode(entry) {
36
- if (typeof entry.unixPermissions !== "number") {
37
- return undefined;
38
- }
39
- const mode = entry.unixPermissions & 0o777;
40
- return mode === 0 ? undefined : mode;
41
- }
42
- async function cleanupStagedArchiveFile(staged) {
43
- if (staged) {
44
- await staged.cleanup().catch(() => undefined);
45
- }
46
- }
47
- async function closeFileHandle(handle) {
48
- if (handle) {
49
- await handle.close().catch(() => undefined);
50
- }
51
- }
52
- async function stageArchiveFileForExtraction(params) {
53
- params.deadline.check();
54
- const sourcePath = path.resolve(params.archivePath);
55
- const initialStat = await fs.lstat(sourcePath);
56
- if (initialStat.isSymbolicLink() || !initialStat.isFile()) {
57
- throw new Error(`archive is not a regular file: ${params.archivePath}`);
58
- }
59
- if (initialStat.size > params.limits.maxArchiveBytes) {
60
- throw new ArchiveLimitError(ARCHIVE_LIMIT_ERROR_CODE.ARCHIVE_SIZE_EXCEEDS_LIMIT);
61
- }
62
- const noFollow = process.platform !== "win32" && "O_NOFOLLOW" in fsConstants ? fsConstants.O_NOFOLLOW : 0;
63
- const handle = await fs.open(sourcePath, fsConstants.O_RDONLY | noFollow);
64
- let staged;
65
- let output;
66
- try {
67
- staged = await tempFile({
68
- prefix: "fs-safe-archive-input",
69
- fileName: path.basename(sourcePath),
70
- });
71
- const openedStat = await handle.stat();
72
- const pathStat = await fs.lstat(sourcePath);
73
- if (!openedStat.isFile() ||
74
- pathStat.isSymbolicLink() ||
75
- !pathStat.isFile() ||
76
- !sameFileIdentity(initialStat, openedStat) ||
77
- !sameFileIdentity(pathStat, openedStat)) {
78
- throw new Error("archive changed during validation");
79
- }
80
- output = await fs.open(staged.path, OPEN_WRITE_CREATE_FLAGS, 0o600);
81
- const buffer = Buffer.allocUnsafe(64 * 1024);
82
- let written = 0;
83
- while (true) {
84
- params.deadline.check();
85
- const { bytesRead } = await handle.read(buffer, 0, buffer.length, null);
86
- if (bytesRead === 0) {
87
- break;
88
- }
89
- written += bytesRead;
90
- if (written > params.limits.maxArchiveBytes) {
91
- throw new ArchiveLimitError(ARCHIVE_LIMIT_ERROR_CODE.ARCHIVE_SIZE_EXCEEDS_LIMIT);
92
- }
93
- await writeFileHandleFully({
94
- handle: output,
95
- buffer,
96
- bytes: bytesRead,
97
- deadline: params.deadline,
98
- });
99
- params.deadline.check();
100
- }
101
- await output.close();
102
- output = undefined;
103
- return staged;
104
- }
105
- catch (err) {
106
- await closeFileHandle(output);
107
- await cleanupStagedArchiveFile(staged);
108
- throw err;
109
- }
110
- finally {
111
- await closeFileHandle(handle);
112
- }
113
- }
114
37
  async function readZipEntryStream(entry) {
115
38
  if (typeof entry.nodeStream === "function") {
116
39
  return entry.nodeStream();
@@ -150,7 +73,7 @@ async function writeZipFileEntry(params) {
150
73
  dir: path.dirname(destinationPath),
151
74
  tempPrefix: `.${path.basename(destinationPath)}.fs-safe-archive`,
152
75
  chmodDir: false,
153
- mode: zipEntryFileMode(params.entry),
76
+ mode: params.mode,
154
77
  writeTemp: async (tempPath) => {
155
78
  tempHandle = await fs.open(tempPath, OPEN_WRITE_CREATE_FLAGS, 0o666);
156
79
  const writable = tempHandle.createWriteStream();
@@ -218,6 +141,18 @@ async function extractZip(params) {
218
141
  if (!output) {
219
142
  continue;
220
143
  }
144
+ assertArchiveEntryPathComponentsWithinLimit(output.relPath, limits);
145
+ const isSymlink = isZipSymlinkEntry(entry);
146
+ const entryKind = isSymlink ? "symlink" : entry.dir ? "directory" : "file";
147
+ const entrySize = zipEntryDeclaredSize(entry);
148
+ if (!shouldExtractArchiveEntry({
149
+ filter: params.entryFilter,
150
+ onFiltered: params.onFiltered,
151
+ entry: { path: entry.name, kind: entryKind, size: entrySize },
152
+ })) {
153
+ continue;
154
+ }
155
+ const mode = zipEntryMode(entry, params.entryModes);
221
156
  await prepareZipOutputPath({
222
157
  destinationDir: stagingRealDir,
223
158
  destinationRealDir: stagingRealDir,
@@ -227,16 +162,18 @@ async function extractZip(params) {
227
162
  isDirectory: entry.dir,
228
163
  });
229
164
  if (entry.dir) {
165
+ await fs.chmod(output.outPath, mode);
230
166
  continue;
231
167
  }
232
- if (isZipSymlinkEntry(entry)) {
233
- throw new Error(`zip entry is a link: ${entry.name}`);
168
+ if (isSymlink) {
169
+ throw new ArchiveSecurityError("entry-link", `zip entry is a link: ${entry.name}`);
234
170
  }
235
171
  await writeZipFileEntry({
236
172
  entry,
237
173
  outPath: output.outPath,
238
174
  budget,
239
175
  deadline: params.deadline,
176
+ mode,
240
177
  });
241
178
  }
242
179
  params.deadline.check();
@@ -259,6 +196,25 @@ export async function extractArchive(params) {
259
196
  throw new Error(`unsupported archive: ${params.archivePath}`);
260
197
  }
261
198
  const label = kind === "zip" ? "extract zip" : "extract tar";
199
+ const native = getNativeBinding();
200
+ if (native) {
201
+ await withExtractionDeadline(params.timeoutMs, label, async (deadline) => extractNativeArchive({
202
+ binding: native,
203
+ archivePath: params.archivePath,
204
+ destDir: params.destDir,
205
+ kind,
206
+ stripComponents: params.stripComponents,
207
+ limits: params.limits,
208
+ deadline,
209
+ entryModes: params.entryModes,
210
+ entryFilter: params.entryFilter,
211
+ onFiltered: params.onFiltered,
212
+ }));
213
+ return;
214
+ }
215
+ if (kind === "tar-zstd" || kind === "tar-bzip2") {
216
+ throw new FsSafeError("helper-unavailable", `${kind} archives require a supported bundled native binding`);
217
+ }
262
218
  if (kind === "tar") {
263
219
  await withExtractionDeadline(params.timeoutMs, label, async (deadline) => {
264
220
  const tar = await importOptionalTar();
@@ -269,6 +225,12 @@ export async function extractArchive(params) {
269
225
  deadline,
270
226
  });
271
227
  try {
228
+ await preflightTarMetadata({
229
+ archivePath: stagedArchive.path,
230
+ maxMetaEntryBytes: limits.maxMetaEntryBytes,
231
+ signal: deadline.signal,
232
+ });
233
+ deadline.check();
272
234
  const destinationRealDir = await prepareArchiveDestinationDir(params.destDir);
273
235
  await withStagedArchiveDestination({
274
236
  destinationRealDir,
@@ -278,23 +240,56 @@ export async function extractArchive(params) {
278
240
  rootDir: destinationRealDir,
279
241
  stripComponents: params.stripComponents,
280
242
  limits,
243
+ entryFilter: params.entryFilter,
244
+ onFiltered: params.onFiltered,
281
245
  });
246
+ const acceptedEntries = [];
282
247
  // A canonical cwd is not enough here: tar can still follow
283
248
  // pre-existing child symlinks in the live destination tree.
284
249
  // Extract into a private staging dir first, then merge through
285
250
  // the same safe-open boundary checks used by direct file writes.
286
- await tar.x({
287
- file: stagedArchive.path,
251
+ const extractor = tar.x({
288
252
  cwd: stagingDir,
289
253
  strip: Math.max(0, Math.floor(params.stripComponents ?? 0)),
290
254
  gzip: params.tarGzip,
291
255
  signal: deadline.signal,
292
256
  preservePaths: false,
257
+ noChmod: true,
258
+ preserveOwner: false,
293
259
  strict: true,
260
+ maxMetaEntrySize: limits.maxMetaEntryBytes,
261
+ filter(_entryPath, entry) {
262
+ try {
263
+ const info = readTarEntryInfo(entry);
264
+ const accepted = checkTarEntrySafety(info);
265
+ if (accepted) {
266
+ const relPath = stripArchivePath(info.path, Math.max(0, Math.floor(params.stripComponents ?? 0)));
267
+ if (relPath) {
268
+ acceptedEntries.push({
269
+ path: relPath,
270
+ mode: resolveArchiveEntryMode({
271
+ kind: info.type === "Directory" || info.type === "GNUDumpDir"
272
+ ? "directory"
273
+ : "file",
274
+ archivedMode: info.mode,
275
+ policy: params.entryModes,
276
+ }),
277
+ });
278
+ }
279
+ }
280
+ return accepted;
281
+ }
282
+ catch (error) {
283
+ // Abort through the parser so pipeline tears down both the
284
+ // archive reader and unpacker instead of leaving a paused
285
+ // stream behind after a policy rejection.
286
+ this.abort(error instanceof Error ? error : new Error(String(error)));
287
+ return false;
288
+ }
289
+ },
294
290
  onReadEntry(entry) {
295
291
  try {
296
292
  deadline.check();
297
- checkTarEntrySafety(readTarEntryInfo(entry));
298
293
  }
299
294
  catch (err) {
300
295
  const error = err instanceof Error ? err : new Error(String(err));
@@ -305,6 +300,22 @@ export async function extractArchive(params) {
305
300
  }
306
301
  },
307
302
  });
303
+ try {
304
+ await pipeline(fsSync.createReadStream(stagedArchive.path), extractor, {
305
+ signal: deadline.signal,
306
+ });
307
+ }
308
+ catch (error) {
309
+ throw createPipelineTimeoutError(error, deadline);
310
+ }
311
+ for (const accepted of acceptedEntries) {
312
+ const outputPath = resolveArchiveOutputPath({
313
+ rootDir: stagingDir,
314
+ relPath: accepted.path,
315
+ originalPath: accepted.path,
316
+ });
317
+ await fs.chmod(outputPath, accepted.mode);
318
+ }
308
319
  deadline.check();
309
320
  await mergeExtractedTreeIntoDestination({
310
321
  sourceDir: stagingDir,
@@ -327,16 +338,8 @@ export async function extractArchive(params) {
327
338
  stripComponents: params.stripComponents,
328
339
  limits: params.limits,
329
340
  deadline,
341
+ entryModes: params.entryModes,
342
+ entryFilter: params.entryFilter,
343
+ onFiltered: params.onFiltered,
330
344
  }));
331
345
  }
332
- async function importOptionalTar() {
333
- try {
334
- return await import("tar");
335
- }
336
- catch (err) {
337
- throw missingOptionalArchiveDependencyError("tar", err);
338
- }
339
- }
340
- function missingOptionalArchiveDependencyError(packageName, cause) {
341
- return new Error(`Optional archive dependency "${packageName}" is not installed. Install it to use TAR archive helpers from @openclaw/fs-safe/archive.`, { cause });
342
- }
package/dist/atomic.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export { replaceFileAtomic, replaceFileAtomicSync, type ReplaceFileAtomicFileSystem, type ReplaceFileAtomicOptions, type ReplaceFileAtomicResult, type ReplaceFileAtomicSyncFileSystem, type ReplaceFileAtomicSyncOptions, } from "./replace-file.js";
1
+ export { replaceFileAtomic, replaceFileAtomicSync, type ReplaceFileAtomicFileSystem, type ReplaceFileAtomicOptions, type ReplaceFileAtomicRestoreCleanup, type ReplaceFileAtomicRestoreFailureDetails, type ReplaceFileAtomicResult, type ReplaceFileCopyFallbackRestorePolicy, type ReplaceFileDestinationHardlinkPolicy, type ReplaceFileAtomicSyncFileSystem, type ReplaceFileAtomicSyncOptions, } from "./replace-file.js";
2
2
  export { writeTextAtomic, type WriteTextAtomicOptions } from "./text-atomic.js";
3
3
  export { replaceDirectoryAtomic, type ReplaceDirectoryAtomicOptions } from "./replace-directory.js";
4
4
  export { movePathWithCopyFallback, type MovePathWithCopyFallbackOptions } from "./move-path.js";
@@ -1 +1 @@
1
- {"version":3,"file":"atomic.d.ts","sourceRoot":"","sources":["../src/atomic.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,iBAAiB,EACjB,qBAAqB,EACrB,KAAK,2BAA2B,EAChC,KAAK,wBAAwB,EAC7B,KAAK,uBAAuB,EAC5B,KAAK,+BAA+B,EACpC,KAAK,4BAA4B,GAClC,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,eAAe,EAAE,KAAK,sBAAsB,EAAE,MAAM,kBAAkB,CAAC;AAChF,OAAO,EAAE,sBAAsB,EAAE,KAAK,6BAA6B,EAAE,MAAM,wBAAwB,CAAC;AACpG,OAAO,EAAE,wBAAwB,EAAE,KAAK,+BAA+B,EAAE,MAAM,gBAAgB,CAAC"}
1
+ {"version":3,"file":"atomic.d.ts","sourceRoot":"","sources":["../src/atomic.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,iBAAiB,EACjB,qBAAqB,EACrB,KAAK,2BAA2B,EAChC,KAAK,wBAAwB,EAC7B,KAAK,+BAA+B,EACpC,KAAK,sCAAsC,EAC3C,KAAK,uBAAuB,EAC5B,KAAK,oCAAoC,EACzC,KAAK,oCAAoC,EACzC,KAAK,+BAA+B,EACpC,KAAK,4BAA4B,GAClC,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,eAAe,EAAE,KAAK,sBAAsB,EAAE,MAAM,kBAAkB,CAAC;AAChF,OAAO,EAAE,sBAAsB,EAAE,KAAK,6BAA6B,EAAE,MAAM,wBAAwB,CAAC;AACpG,OAAO,EAAE,wBAAwB,EAAE,KAAK,+BAA+B,EAAE,MAAM,gBAAgB,CAAC"}
package/dist/config.d.ts CHANGED
@@ -1,3 +1,3 @@
1
- export { configureFsSafePython, getFsSafePythonConfig, type FsSafePythonConfig, type FsSafePythonMode, } from "./pinned-python-config.js";
1
+ export { configureFsSafePython, configureFsSafeNative, getFsSafeNativeConfig, type FsSafePythonConfig, type FsSafeNativeConfig, type FsSafeNativeMode, } from "./native-config.js";
2
2
  export { configureFsSafeLocks, getFsSafeLockConfig, type FsSafeLockConfig, } from "./lock-config.js";
3
3
  //# sourceMappingURL=config.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,qBAAqB,EACrB,qBAAqB,EACrB,KAAK,kBAAkB,EACvB,KAAK,gBAAgB,GACtB,MAAM,2BAA2B,CAAC;AACnC,OAAO,EACL,oBAAoB,EACpB,mBAAmB,EACnB,KAAK,gBAAgB,GACtB,MAAM,kBAAkB,CAAC"}
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,qBAAqB,EACrB,qBAAqB,EACrB,qBAAqB,EACrB,KAAK,kBAAkB,EACvB,KAAK,kBAAkB,EACvB,KAAK,gBAAgB,GACtB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EACL,oBAAoB,EACpB,mBAAmB,EACnB,KAAK,gBAAgB,GACtB,MAAM,kBAAkB,CAAC"}
package/dist/config.js CHANGED
@@ -1,2 +1,2 @@
1
- export { configureFsSafePython, getFsSafePythonConfig, } from "./pinned-python-config.js";
1
+ export { configureFsSafePython, configureFsSafeNative, getFsSafeNativeConfig, } from "./native-config.js";
2
2
  export { configureFsSafeLocks, getFsSafeLockConfig, } from "./lock-config.js";
@@ -0,0 +1,2 @@
1
+ export type ContainmentGuarantee = "kernel-atomic" | "best-effort";
2
+ //# sourceMappingURL=containment.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"containment.d.ts","sourceRoot":"","sources":["../src/containment.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,oBAAoB,GAAG,eAAe,GAAG,aAAa,CAAC"}
@@ -0,0 +1 @@
1
+ export {};
@@ -1,2 +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
+ 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";
2
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"}
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 +1,3 @@
1
1
  export { ensureDurableDirectory, pinDirectory, syncDirectory, syncDirectoryBestEffort, syncDirectoryBestEffortSync, syncDirectorySync, } from "./directory-durability.js";
2
+ export { isHardlinkFallbackError, publishFileExclusive, } from "./publish-file.js";
3
+ export { sha256File, } from "./file-hash.js";
package/dist/errors.d.ts CHANGED
@@ -1,11 +1,14 @@
1
- export type FsSafeErrorCode = "already-exists" | "denied-path" | "device-path" | "hardlink" | "helper-failed" | "helper-unavailable" | "invalid-path" | "insecure-permissions" | "not-empty" | "not-file" | "not-found" | "not-owned" | "not-removable" | "outside-workspace" | "path-alias" | "path-mismatch" | "permission-unverified" | "symlink" | "timeout" | "too-large" | "unsupported-platform";
1
+ export type FsSafeErrorCode = "already-exists" | "denied-path" | "device-path" | "hardlink" | "helper-failed" | "helper-unavailable" | "invalid-path" | "insecure-permissions" | "not-empty" | "not-file" | "not-found" | "not-owned" | "not-removable" | "outside-workspace" | "path-alias" | "path-mismatch" | "permission-unverified" | "secret-exists" | "store-reentrant-update" | "symlink" | "timeout" | "too-large" | "unsupported-platform";
2
2
  export type FsSafeErrorCategory = "policy" | "operational";
3
+ export type FsSafeErrorDetails = Readonly<Record<string, unknown>>;
3
4
  export declare function categorizeFsSafeError(code: FsSafeErrorCode): FsSafeErrorCategory;
4
5
  export declare class FsSafeError extends Error {
5
6
  readonly code: FsSafeErrorCode;
6
7
  readonly category: FsSafeErrorCategory;
8
+ readonly details?: FsSafeErrorDetails;
7
9
  constructor(code: FsSafeErrorCode, message: string, options?: {
8
10
  cause?: unknown;
11
+ details?: FsSafeErrorDetails;
9
12
  });
10
13
  }
11
14
  //# sourceMappingURL=errors.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,eAAe,GACvB,gBAAgB,GAChB,aAAa,GACb,aAAa,GACb,UAAU,GACV,eAAe,GACf,oBAAoB,GACpB,cAAc,GACd,sBAAsB,GACtB,WAAW,GACX,UAAU,GACV,WAAW,GACX,WAAW,GACX,eAAe,GACf,mBAAmB,GACnB,YAAY,GACZ,eAAe,GACf,uBAAuB,GACvB,SAAS,GACT,SAAS,GACT,WAAW,GACX,sBAAsB,CAAC;AAE3B,MAAM,MAAM,mBAAmB,GAAG,QAAQ,GAAG,aAAa,CAAC;AAU3D,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,eAAe,GAAG,mBAAmB,CAEhF;AAED,qBAAa,WAAY,SAAQ,KAAK;IACpC,QAAQ,CAAC,IAAI,EAAE,eAAe,CAAC;IAC/B,QAAQ,CAAC,QAAQ,EAAE,mBAAmB,CAAC;IAEvC,YAAY,IAAI,EAAE,eAAe,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,GAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAO,EAKpF;CACF"}
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,eAAe,GACvB,gBAAgB,GAChB,aAAa,GACb,aAAa,GACb,UAAU,GACV,eAAe,GACf,oBAAoB,GACpB,cAAc,GACd,sBAAsB,GACtB,WAAW,GACX,UAAU,GACV,WAAW,GACX,WAAW,GACX,eAAe,GACf,mBAAmB,GACnB,YAAY,GACZ,eAAe,GACf,uBAAuB,GACvB,eAAe,GACf,wBAAwB,GACxB,SAAS,GACT,SAAS,GACT,WAAW,GACX,sBAAsB,CAAC;AAE3B,MAAM,MAAM,mBAAmB,GAAG,QAAQ,GAAG,aAAa,CAAC;AAC3D,MAAM,MAAM,kBAAkB,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;AAUnE,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,eAAe,GAAG,mBAAmB,CAEhF;AAED,qBAAa,WAAY,SAAQ,KAAK;IACpC,QAAQ,CAAC,IAAI,EAAE,eAAe,CAAC;IAC/B,QAAQ,CAAC,QAAQ,EAAE,mBAAmB,CAAC;IACvC,QAAQ,CAAC,OAAO,CAAC,EAAE,kBAAkB,CAAC;IAEtC,YACE,IAAI,EAAE,eAAe,EACrB,OAAO,EAAE,MAAM,EACf,OAAO,GAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAC;QAAC,OAAO,CAAC,EAAE,kBAAkB,CAAA;KAAO,EAOhE;CACF"}
package/dist/errors.js CHANGED
@@ -11,10 +11,12 @@ export function categorizeFsSafeError(code) {
11
11
  export class FsSafeError extends Error {
12
12
  code;
13
13
  category;
14
+ details;
14
15
  constructor(code, message, options = {}) {
15
16
  super(message, options);
16
17
  this.name = "FsSafeError";
17
18
  this.code = code;
18
19
  this.category = categorizeFsSafeError(code);
20
+ this.details = options.details;
19
21
  }
20
22
  }
@@ -0,0 +1,10 @@
1
+ import type { FileHandle } from "node:fs/promises";
2
+ import { type NativeBinding } from "./native.js";
3
+ export type Sha256FileInput = string | FileHandle;
4
+ export type Sha256FileResult = {
5
+ bytes: number;
6
+ digest: string;
7
+ };
8
+ export declare function hashFileHandle(handle: FileHandle, native?: NativeBinding | undefined): Promise<Sha256FileResult>;
9
+ export declare function sha256File(input: Sha256FileInput): Promise<Sha256FileResult>;
10
+ //# sourceMappingURL=file-hash.d.ts.map
@@ -0,0 +1 @@
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;AAEnE,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;AAcF,wBAAsB,cAAc,CAClC,MAAM,EAAE,UAAU,EAClB,MAAM,GAAE,aAAa,GAAG,SAA8B,GACrD,OAAO,CAAC,gBAAgB,CAAC,CAoB3B;AAsCD,wBAAsB,UAAU,CAAC,KAAK,EAAE,eAAe,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAElF"}
@@ -0,0 +1,73 @@
1
+ import { createHash } from "node:crypto";
2
+ import fsSync from "node:fs";
3
+ import fs from "node:fs/promises";
4
+ import { FsSafeError } from "./errors.js";
5
+ import { sameFileIdentity } from "./file-identity.js";
6
+ import { getNativeBinding } from "./native.js";
7
+ function readFlags() {
8
+ return (fsSync.constants.O_RDONLY |
9
+ (process.platform !== "win32" && typeof fsSync.constants.O_NOFOLLOW === "number"
10
+ ? fsSync.constants.O_NOFOLLOW
11
+ : 0) |
12
+ (process.platform !== "win32" && typeof fsSync.constants.O_NONBLOCK === "number"
13
+ ? fsSync.constants.O_NONBLOCK
14
+ : 0));
15
+ }
16
+ export async function hashFileHandle(handle, native = getNativeBinding()) {
17
+ const stat = await handle.stat();
18
+ if (!stat.isFile()) {
19
+ throw new FsSafeError("not-file", "SHA-256 input is not a regular file");
20
+ }
21
+ if (native) {
22
+ return await native.sha256File(handle.fd);
23
+ }
24
+ const hash = createHash("sha256");
25
+ const buffer = Buffer.allocUnsafe(64 * 1024);
26
+ let position = 0;
27
+ while (true) {
28
+ const { bytesRead } = await handle.read(buffer, 0, buffer.length, position);
29
+ if (bytesRead === 0) {
30
+ return { bytes: position, digest: hash.digest("hex") };
31
+ }
32
+ hash.update(buffer.subarray(0, bytesRead));
33
+ position += bytesRead;
34
+ }
35
+ }
36
+ async function hashPath(filePath) {
37
+ const before = await fs.lstat(filePath);
38
+ if (before.isSymbolicLink()) {
39
+ throw new FsSafeError("symlink", "SHA-256 path must not be a symbolic link");
40
+ }
41
+ if (!before.isFile()) {
42
+ throw new FsSafeError("not-file", "SHA-256 path is not a regular file");
43
+ }
44
+ let handle;
45
+ try {
46
+ handle = await fs.open(filePath, readFlags());
47
+ }
48
+ catch (error) {
49
+ if (error?.code === "ELOOP") {
50
+ throw new FsSafeError("symlink", "SHA-256 path must not be a symbolic link", {
51
+ cause: error,
52
+ });
53
+ }
54
+ throw error;
55
+ }
56
+ try {
57
+ const opened = await handle.stat();
58
+ const current = await fs.lstat(filePath);
59
+ if (!opened.isFile()) {
60
+ throw new FsSafeError("not-file", "SHA-256 path is not a regular file");
61
+ }
62
+ if (current.isSymbolicLink() || !current.isFile() || !sameFileIdentity(opened, current)) {
63
+ throw new FsSafeError("path-mismatch", "SHA-256 path changed while opening");
64
+ }
65
+ return await hashFileHandle(handle);
66
+ }
67
+ finally {
68
+ await handle.close().catch(() => undefined);
69
+ }
70
+ }
71
+ export async function sha256File(input) {
72
+ return typeof input === "string" ? await hashPath(input) : await hashFileHandle(input);
73
+ }
@@ -0,0 +1,35 @@
1
+ import type { Root } from "./root-impl.js";
2
+ import { type SidecarLockStaleSnapshot } from "./sidecar-lock-reclaim.js";
3
+ import type { SidecarLockCompromisedInfo, SidecarLockRetryOptions, SidecarLockStaleRecovery } from "./sidecar-lock-types.js";
4
+ export type FileLockSyncAcquireOptions<TPayload extends Record<string, unknown>> = {
5
+ lockPath?: string;
6
+ staleMs?: number;
7
+ timeoutMs?: number;
8
+ retry?: SidecarLockRetryOptions;
9
+ staleRecovery?: SidecarLockStaleRecovery;
10
+ reentrantOwner?: string;
11
+ payload: () => TPayload;
12
+ shouldReclaim?: (params: {
13
+ lockPath: string;
14
+ normalizedTargetPath: string;
15
+ payload: unknown;
16
+ staleMs: number;
17
+ nowMs: number;
18
+ heldByThisProcess: false;
19
+ }) => boolean;
20
+ shouldRemoveStaleLock?: (snapshot: SidecarLockStaleSnapshot) => boolean;
21
+ parsePayload?: (raw: string) => unknown;
22
+ lockRoot?: Root;
23
+ onCompromised?: (info: SidecarLockCompromisedInfo) => void;
24
+ compromiseCheckIntervalMs?: number;
25
+ };
26
+ export type FileLockSyncHandle = {
27
+ lockPath: string;
28
+ normalizedTargetPath: string;
29
+ verifyStillHeld(): boolean;
30
+ release(): void;
31
+ [Symbol.dispose](): void;
32
+ };
33
+ export declare function acquireFileLockSync<TPayload extends Record<string, unknown>>(targetPath: string, options: FileLockSyncAcquireOptions<TPayload>): FileLockSyncHandle;
34
+ export declare function withFileLockSync<T, TPayload extends Record<string, unknown>>(targetPath: string, options: FileLockSyncAcquireOptions<TPayload>, fn: () => T): T;
35
+ //# sourceMappingURL=file-lock-sync.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-lock-sync.d.ts","sourceRoot":"","sources":["../src/file-lock-sync.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EAOL,KAAK,wBAAwB,EAC9B,MAAM,2BAA2B,CAAC;AAKnC,OAAO,KAAK,EACV,0BAA0B,EAC1B,uBAAuB,EACvB,wBAAwB,EACzB,MAAM,yBAAyB,CAAC;AAEjC,MAAM,MAAM,0BAA0B,CAAC,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,IAAI;IACjF,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,uBAAuB,CAAC;IAChC,aAAa,CAAC,EAAE,wBAAwB,CAAC;IACzC,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,OAAO,EAAE,MAAM,QAAQ,CAAC;IACxB,aAAa,CAAC,EAAE,CAAC,MAAM,EAAE;QACvB,QAAQ,EAAE,MAAM,CAAC;QACjB,oBAAoB,EAAE,MAAM,CAAC;QAC7B,OAAO,EAAE,OAAO,CAAC;QACjB,OAAO,EAAE,MAAM,CAAC;QAChB,KAAK,EAAE,MAAM,CAAC;QACd,iBAAiB,EAAE,KAAK,CAAC;KAC1B,KAAK,OAAO,CAAC;IACd,qBAAqB,CAAC,EAAE,CAAC,QAAQ,EAAE,wBAAwB,KAAK,OAAO,CAAC;IACxE,YAAY,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC;IACxC,QAAQ,CAAC,EAAE,IAAI,CAAC;IAChB,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE,0BAA0B,KAAK,IAAI,CAAC;IAC3D,yBAAyB,CAAC,EAAE,MAAM,CAAC;CACpC,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,QAAQ,EAAE,MAAM,CAAC;IACjB,oBAAoB,EAAE,MAAM,CAAC;IAC7B,eAAe,IAAI,OAAO,CAAC;IAC3B,OAAO,IAAI,IAAI,CAAC;IAChB,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,IAAI,CAAC;CAC1B,CAAC;AAiGF,wBAAgB,mBAAmB,CAAC,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC1E,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,0BAA0B,CAAC,QAAQ,CAAC,GAC5C,kBAAkB,CA6IpB;AAED,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC1E,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,0BAA0B,CAAC,QAAQ,CAAC,EAC7C,EAAE,EAAE,MAAM,CAAC,GACV,CAAC,CAOH"}