@openclaw/fs-safe 0.14.0 → 0.15.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 (235) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/README.md +10 -4
  3. package/dist/advanced.d.ts +1 -1
  4. package/dist/advanced.d.ts.map +1 -1
  5. package/dist/advanced.js +1 -1
  6. package/dist/archive-merge.d.ts.map +1 -1
  7. package/dist/archive-merge.js +113 -46
  8. package/dist/archive-read.d.ts.map +1 -1
  9. package/dist/archive-read.js +4 -4
  10. package/dist/archive-zip-directory.d.ts +4 -0
  11. package/dist/archive-zip-directory.d.ts.map +1 -1
  12. package/dist/archive-zip-directory.js +2 -0
  13. package/dist/archive-zip-entry.d.ts +6 -2
  14. package/dist/archive-zip-entry.d.ts.map +1 -1
  15. package/dist/archive-zip-entry.js +23 -8
  16. package/dist/archive-zip-integrity.d.ts.map +1 -1
  17. package/dist/archive-zip-integrity.js +3 -4
  18. package/dist/archive-zip-loader.d.ts.map +1 -1
  19. package/dist/archive-zip-loader.js +107 -31
  20. package/dist/archive-zip-names.d.ts +1 -0
  21. package/dist/archive-zip-names.d.ts.map +1 -1
  22. package/dist/archive-zip-names.js +6 -0
  23. package/dist/archive.js +6 -5
  24. package/dist/bounded-read-stream.d.ts +0 -1
  25. package/dist/bounded-read-stream.d.ts.map +1 -1
  26. package/dist/bounded-read-stream.js +0 -6
  27. package/dist/copy-publication.d.ts +6 -0
  28. package/dist/copy-publication.d.ts.map +1 -1
  29. package/dist/copy-publication.js +3 -0
  30. package/dist/copy-tree-portable.d.ts.map +1 -1
  31. package/dist/copy-tree-portable.js +44 -24
  32. package/dist/copy.d.ts.map +1 -1
  33. package/dist/copy.js +29 -11
  34. package/dist/directory-mode-owner.js +5 -5
  35. package/dist/file-handle-transfer.d.ts +2 -0
  36. package/dist/file-handle-transfer.d.ts.map +1 -1
  37. package/dist/file-handle-transfer.js +57 -2
  38. package/dist/file-identity.d.ts.map +1 -1
  39. package/dist/file-identity.js +18 -4
  40. package/dist/file-lock-sync-admission.d.ts +19 -0
  41. package/dist/file-lock-sync-admission.d.ts.map +1 -0
  42. package/dist/file-lock-sync-admission.js +93 -0
  43. package/dist/file-lock-sync-root-acquire.d.ts +4 -0
  44. package/dist/file-lock-sync-root-acquire.d.ts.map +1 -0
  45. package/dist/file-lock-sync-root-acquire.js +370 -0
  46. package/dist/file-lock-sync-root-arbitration.d.ts +18 -0
  47. package/dist/file-lock-sync-root-arbitration.d.ts.map +1 -0
  48. package/dist/file-lock-sync-root-arbitration.js +66 -0
  49. package/dist/file-lock-sync-root-held.d.ts +34 -0
  50. package/dist/file-lock-sync-root-held.d.ts.map +1 -0
  51. package/dist/file-lock-sync-root-held.js +393 -0
  52. package/dist/file-lock-sync-root-io.d.ts +44 -0
  53. package/dist/file-lock-sync-root-io.d.ts.map +1 -0
  54. package/dist/file-lock-sync-root-io.js +209 -0
  55. package/dist/file-lock-sync-root-mutation.d.ts +17 -0
  56. package/dist/file-lock-sync-root-mutation.d.ts.map +1 -0
  57. package/dist/file-lock-sync-root-mutation.js +277 -0
  58. package/dist/file-lock-sync-root-options.d.ts +20 -0
  59. package/dist/file-lock-sync-root-options.d.ts.map +1 -0
  60. package/dist/file-lock-sync-root-options.js +58 -0
  61. package/dist/file-lock-sync-root-registration.d.ts +2 -0
  62. package/dist/file-lock-sync-root-registration.d.ts.map +1 -0
  63. package/dist/file-lock-sync-root-registration.js +90 -0
  64. package/dist/file-lock-sync-root.d.ts +36 -0
  65. package/dist/file-lock-sync-root.d.ts.map +1 -0
  66. package/dist/file-lock-sync-root.js +361 -0
  67. package/dist/file-lock-sync-stale-admission.d.ts +24 -0
  68. package/dist/file-lock-sync-stale-admission.d.ts.map +1 -0
  69. package/dist/file-lock-sync-stale-admission.js +205 -0
  70. package/dist/file-lock-sync.d.ts.map +1 -1
  71. package/dist/file-lock-sync.js +245 -205
  72. package/dist/file-store-prune.d.ts.map +1 -1
  73. package/dist/file-store-prune.js +5 -1
  74. package/dist/file-store-sync-write.js +3 -3
  75. package/dist/file-store.d.ts.map +1 -1
  76. package/dist/file-store.js +47 -12
  77. package/dist/json-document-store.d.ts.map +1 -1
  78. package/dist/json-document-store.js +22 -15
  79. package/dist/json-durable-queue-ownership.d.ts +0 -1
  80. package/dist/json-durable-queue-ownership.d.ts.map +1 -1
  81. package/dist/json-durable-queue-ownership.js +0 -6
  82. package/dist/native-binding.d.ts +2 -0
  83. package/dist/native-binding.d.ts.map +1 -1
  84. package/dist/native-parent-admission.d.ts +3 -2
  85. package/dist/native-parent-admission.d.ts.map +1 -1
  86. package/dist/native-parent-admission.js +24 -5
  87. package/dist/native-pinned-write-windows.d.ts.map +1 -1
  88. package/dist/native-pinned-write-windows.js +7 -7
  89. package/dist/native-pinned-write.d.ts.map +1 -1
  90. package/dist/native-pinned-write.js +7 -11
  91. package/dist/native-policy-parent-windows.d.ts +14 -0
  92. package/dist/native-policy-parent-windows.d.ts.map +1 -0
  93. package/dist/native-policy-parent-windows.js +200 -0
  94. package/dist/native-rename-outcome.d.ts +4 -0
  95. package/dist/native-rename-outcome.d.ts.map +1 -0
  96. package/dist/native-rename-outcome.js +8 -0
  97. package/dist/native-staged-file.d.ts +2 -2
  98. package/dist/native-staged-file.d.ts.map +1 -1
  99. package/dist/native-staged-file.js +42 -40
  100. package/dist/output.d.ts.map +1 -1
  101. package/dist/output.js +12 -8
  102. package/dist/path-prefix.d.ts.map +1 -1
  103. package/dist/path-prefix.js +30 -8
  104. package/dist/path-suffix-aliases.d.ts +2 -0
  105. package/dist/path-suffix-aliases.d.ts.map +1 -1
  106. package/dist/path-suffix-aliases.js +25 -17
  107. package/dist/permission-exec.d.ts +2 -0
  108. package/dist/permission-exec.d.ts.map +1 -1
  109. package/dist/permission-exec.js +150 -21
  110. package/dist/permissions-windows.js +1 -1
  111. package/dist/pinned-mutation-admission.d.ts.map +1 -1
  112. package/dist/pinned-mutation-admission.js +10 -5
  113. package/dist/pinned-mutation-observation.d.ts +0 -1
  114. package/dist/pinned-mutation-observation.d.ts.map +1 -1
  115. package/dist/pinned-mutation-observation.js +0 -19
  116. package/dist/pinned-mutation-shared-route.d.ts +1 -0
  117. package/dist/pinned-mutation-shared-route.d.ts.map +1 -1
  118. package/dist/pinned-mutation-shared-route.js +1 -1
  119. package/dist/pinned-write-types.d.ts +2 -0
  120. package/dist/pinned-write-types.d.ts.map +1 -1
  121. package/dist/private-temp-workspace.d.ts.map +1 -1
  122. package/dist/private-temp-workspace.js +75 -121
  123. package/dist/regular-file.d.ts.map +1 -1
  124. package/dist/regular-file.js +1 -15
  125. package/dist/replace-directory.d.ts.map +1 -1
  126. package/dist/replace-directory.js +256 -18
  127. package/dist/replace-file-copy-fallback.d.ts.map +1 -1
  128. package/dist/replace-file-copy-fallback.js +62 -70
  129. package/dist/replace-file-copy-source.d.ts.map +1 -1
  130. package/dist/replace-file-copy-source.js +10 -12
  131. package/dist/replace-file-temp-owner.d.ts +5 -2
  132. package/dist/replace-file-temp-owner.d.ts.map +1 -1
  133. package/dist/replace-file-temp-owner.js +65 -30
  134. package/dist/replace-file.js +6 -6
  135. package/dist/retained-directory-replacement.d.ts +26 -0
  136. package/dist/retained-directory-replacement.d.ts.map +1 -0
  137. package/dist/retained-directory-replacement.js +193 -0
  138. package/dist/root-boundary.d.ts +1 -0
  139. package/dist/root-boundary.d.ts.map +1 -1
  140. package/dist/root-boundary.js +4 -0
  141. package/dist/root-context.d.ts +0 -8
  142. package/dist/root-context.d.ts.map +1 -1
  143. package/dist/root-context.js +0 -3
  144. package/dist/root-create-input.d.ts +6 -0
  145. package/dist/root-create-input.d.ts.map +1 -1
  146. package/dist/root-create-input.js +5 -1
  147. package/dist/root-directory-list.d.ts +1 -0
  148. package/dist/root-directory-list.d.ts.map +1 -1
  149. package/dist/root-directory-list.js +1 -0
  150. package/dist/root-impl.d.ts.map +1 -1
  151. package/dist/root-impl.js +18 -10
  152. package/dist/root-move-noreplace.d.ts.map +1 -1
  153. package/dist/root-move-noreplace.js +2 -2
  154. package/dist/root-path-errors.d.ts +1 -0
  155. package/dist/root-path-errors.d.ts.map +1 -1
  156. package/dist/root-path-errors.js +11 -2
  157. package/dist/root-path-existing.d.ts.map +1 -1
  158. package/dist/root-path-existing.js +11 -35
  159. package/dist/root-path.js +1 -13
  160. package/dist/root-remove.d.ts +1 -0
  161. package/dist/root-remove.d.ts.map +1 -1
  162. package/dist/root-remove.js +4 -0
  163. package/dist/root-walk.d.ts +1 -1
  164. package/dist/root-walk.d.ts.map +1 -1
  165. package/dist/root-walk.js +17 -2
  166. package/dist/root-write-admission.d.ts +0 -2
  167. package/dist/root-write-admission.d.ts.map +1 -1
  168. package/dist/root-write-admission.js +1 -15
  169. package/dist/root-write-complete-parent.d.ts.map +1 -1
  170. package/dist/root-write-complete-parent.js +7 -23
  171. package/dist/root-write-verification.d.ts.map +1 -1
  172. package/dist/root-write-verification.js +29 -42
  173. package/dist/secret-file.d.ts.map +1 -1
  174. package/dist/secret-file.js +2 -24
  175. package/dist/secret-read-async.d.ts.map +1 -1
  176. package/dist/secret-read-async.js +3 -24
  177. package/dist/secret-read-policy.d.ts +6 -2
  178. package/dist/secret-read-policy.d.ts.map +1 -1
  179. package/dist/secret-read-policy.js +26 -2
  180. package/dist/sibling-temp.d.ts.map +1 -1
  181. package/dist/sibling-temp.js +23 -12
  182. package/dist/sidecar-lock-acquire.d.ts +2 -28
  183. package/dist/sidecar-lock-acquire.d.ts.map +1 -1
  184. package/dist/sidecar-lock-acquire.js +288 -199
  185. package/dist/sidecar-lock-admission-context.d.ts +19 -0
  186. package/dist/sidecar-lock-admission-context.d.ts.map +1 -0
  187. package/dist/sidecar-lock-admission-context.js +60 -0
  188. package/dist/sidecar-lock-admission-parser.d.ts +43 -0
  189. package/dist/sidecar-lock-admission-parser.d.ts.map +1 -0
  190. package/dist/sidecar-lock-admission-parser.js +113 -0
  191. package/dist/sidecar-lock-admission.d.ts +35 -0
  192. package/dist/sidecar-lock-admission.d.ts.map +1 -0
  193. package/dist/sidecar-lock-admission.js +7 -0
  194. package/dist/sidecar-lock-reclaim.d.ts +9 -4
  195. package/dist/sidecar-lock-reclaim.d.ts.map +1 -1
  196. package/dist/sidecar-lock-reclaim.js +80 -25
  197. package/dist/sidecar-lock-stale-admission.d.ts +39 -0
  198. package/dist/sidecar-lock-stale-admission.d.ts.map +1 -0
  199. package/dist/sidecar-lock-stale-admission.js +232 -0
  200. package/dist/sidecar-lock-target.d.ts +8 -0
  201. package/dist/sidecar-lock-target.d.ts.map +1 -0
  202. package/dist/sidecar-lock-target.js +55 -0
  203. package/dist/sidecar-lock.d.ts.map +1 -1
  204. package/dist/sidecar-lock.js +100 -16
  205. package/dist/temp-workspace-descriptor.d.ts.map +1 -1
  206. package/dist/temp-workspace-descriptor.js +9 -27
  207. package/dist/temp-workspace-owner.d.ts.map +1 -1
  208. package/dist/temp-workspace-owner.js +8 -8
  209. package/dist/walk.d.ts +5 -1
  210. package/dist/walk.d.ts.map +1 -1
  211. package/dist/walk.js +19 -6
  212. package/dist/windows-owner.d.ts.map +1 -1
  213. package/dist/windows-owner.js +2 -2
  214. package/docs/advanced.md +1 -1
  215. package/docs/archive.md +36 -9
  216. package/docs/atomic.md +85 -8
  217. package/docs/copy.md +35 -0
  218. package/docs/file-store.md +19 -0
  219. package/docs/json-store.md +5 -0
  220. package/docs/native-helper.md +10 -3
  221. package/docs/output.md +6 -0
  222. package/docs/path-prefix.md +10 -0
  223. package/docs/path-suffix-aliases.md +51 -6
  224. package/docs/permissions.md +13 -0
  225. package/docs/public-api.md +3 -2
  226. package/docs/root.md +22 -3
  227. package/docs/sidecar-lock.md +109 -4
  228. package/docs/staged-file.md +7 -3
  229. package/docs/temp.md +20 -3
  230. package/docs/walk.md +67 -1
  231. package/docs/writing.md +5 -1
  232. package/package.json +10 -10
  233. package/dist/darwin-acl.d.ts +0 -4
  234. package/dist/darwin-acl.d.ts.map +0 -1
  235. package/dist/darwin-acl.js +0 -24
@@ -0,0 +1,90 @@
1
+ import { FsSafeError } from "./errors.js";
2
+ import { createSuppressedError } from "./suppressed-error.js";
3
+ const ROOT_SYNC_CLEANUP_REGISTERING_KEY = Symbol.for("fsSafe.syncRootSidecarLockCleanupRegistering.v1");
4
+ const ROOT_SYNC_CLEANUP_REGISTERED_KEY = Symbol.for("fsSafe.syncRootSidecarLockCleanupRegistered.v1");
5
+ const ROOT_SYNC_CLEANUP_FAILED_KEY = Symbol.for("fsSafe.syncRootSidecarLockCleanupRegistrationFailed.v1");
6
+ const ROOT_SYNC_CLEANUP_HANDLER_KEY = Symbol.for("fsSafe.syncRootSidecarLockCleanupHandler.v1");
7
+ function unavailable(message) {
8
+ return new FsSafeError("helper-unavailable", message);
9
+ }
10
+ function exactListenerCount(listener) {
11
+ return process.listeners("exit").filter((candidate) => candidate === listener).length;
12
+ }
13
+ export function ensureFileLockSyncRootExitCleanupRegistered(cleanupHandler) {
14
+ const state = globalThis;
15
+ if (state[ROOT_SYNC_CLEANUP_REGISTERED_KEY]) {
16
+ if (typeof state[ROOT_SYNC_CLEANUP_HANDLER_KEY] === "function")
17
+ return;
18
+ state[ROOT_SYNC_CLEANUP_FAILED_KEY] = true;
19
+ throw unavailable("Root sidecar exit cleanup registration state is inconsistent");
20
+ }
21
+ if (state[ROOT_SYNC_CLEANUP_FAILED_KEY]) {
22
+ throw unavailable("Root sidecar exit cleanup registration previously failed");
23
+ }
24
+ if (state[ROOT_SYNC_CLEANUP_REGISTERING_KEY]) {
25
+ throw unavailable("Root sidecar exit cleanup registration is already in progress");
26
+ }
27
+ if (state[ROOT_SYNC_CLEANUP_HANDLER_KEY] !== undefined) {
28
+ state[ROOT_SYNC_CLEANUP_FAILED_KEY] = true;
29
+ throw unavailable("Root sidecar exit cleanup handler exists without registration");
30
+ }
31
+ state[ROOT_SYNC_CLEANUP_REGISTERING_KEY] = true;
32
+ let baselineCount;
33
+ let registrationAttempted = false;
34
+ let registrationStateAmbiguous = false;
35
+ try {
36
+ baselineCount = exactListenerCount(cleanupHandler);
37
+ if (baselineCount !== 0) {
38
+ registrationStateAmbiguous = true;
39
+ throw unavailable("Root sidecar exit cleanup listener exists without registration");
40
+ }
41
+ registrationAttempted = true;
42
+ process.on("exit", cleanupHandler);
43
+ if (exactListenerCount(cleanupHandler) !== baselineCount + 1) {
44
+ throw unavailable("Root sidecar exit cleanup listener was not registered exactly once");
45
+ }
46
+ // Do not expose a callable handler or report success until process.on has
47
+ // returned and the exact listener registration has been observed.
48
+ state[ROOT_SYNC_CLEANUP_HANDLER_KEY] = cleanupHandler;
49
+ state[ROOT_SYNC_CLEANUP_REGISTERED_KEY] = true;
50
+ state[ROOT_SYNC_CLEANUP_FAILED_KEY] = false;
51
+ state[ROOT_SYNC_CLEANUP_REGISTERING_KEY] = false;
52
+ }
53
+ catch (registrationError) {
54
+ let rollbackFailed = false;
55
+ let rollbackError;
56
+ if (registrationAttempted && baselineCount !== undefined) {
57
+ try {
58
+ let currentCount = exactListenerCount(cleanupHandler);
59
+ while (currentCount > baselineCount) {
60
+ process.removeListener("exit", cleanupHandler);
61
+ const nextCount = exactListenerCount(cleanupHandler);
62
+ if (nextCount >= currentCount) {
63
+ throw unavailable("Root sidecar exit cleanup listener rollback made no progress");
64
+ }
65
+ currentCount = nextCount;
66
+ }
67
+ if (currentCount !== baselineCount) {
68
+ throw unavailable("Root sidecar exit cleanup listener rollback was ambiguous");
69
+ }
70
+ }
71
+ catch (error) {
72
+ rollbackFailed = true;
73
+ rollbackError = error;
74
+ }
75
+ }
76
+ state[ROOT_SYNC_CLEANUP_HANDLER_KEY] = undefined;
77
+ state[ROOT_SYNC_CLEANUP_REGISTERED_KEY] = false;
78
+ state[ROOT_SYNC_CLEANUP_REGISTERING_KEY] = false;
79
+ if (registrationStateAmbiguous) {
80
+ state[ROOT_SYNC_CLEANUP_FAILED_KEY] = true;
81
+ throw registrationError;
82
+ }
83
+ if (rollbackFailed) {
84
+ state[ROOT_SYNC_CLEANUP_FAILED_KEY] = true;
85
+ throw createSuppressedError(registrationError, rollbackError, "Root sidecar exit cleanup registration and rollback both failed");
86
+ }
87
+ state[ROOT_SYNC_CLEANUP_FAILED_KEY] = false;
88
+ throw registrationError;
89
+ }
90
+ }
@@ -0,0 +1,36 @@
1
+ import { type RootContext } from "./root-context.js";
2
+ import type { Root, RootDefaults } from "./root-impl.js";
3
+ /** @internal Registers only instances constructed by RootHandle itself. */
4
+ export declare function registerFileLockSyncRootAdapter(handle: object, context: RootContext, defaults: RootDefaults): void;
5
+ export type FileLockSyncRootAuthority = Readonly<{
6
+ adapter: object;
7
+ context: RootContext;
8
+ assertBeforeMutation?: () => void;
9
+ denyMutations?: Readonly<{
10
+ paths?: readonly string[];
11
+ prefixes?: readonly string[];
12
+ }>;
13
+ hardlinks?: RootDefaults["hardlinks"];
14
+ mutationPolicy: RootPathResolutionPolicy;
15
+ readPolicy: RootPathResolutionPolicy;
16
+ policiesMatch: boolean;
17
+ }>;
18
+ export type FileLockSyncRootPath = Readonly<{
19
+ authority: FileLockSyncRootAuthority;
20
+ path: string;
21
+ relativePath: string;
22
+ }>;
23
+ export declare function captureFileLockSyncRootAuthority(lockRoot: Root): FileLockSyncRootAuthority;
24
+ export declare function invokeFileLockSyncRootMutationAuthority(authority: FileLockSyncRootAuthority): boolean;
25
+ export declare function assertFileLockSyncRootMutationAllowed(pathname: string, policy: FileLockSyncRootAuthority["denyMutations"], protectAncestors?: boolean): void;
26
+ type RootPathResolutionPolicy = Readonly<{
27
+ rejectSymlinks?: boolean;
28
+ rejectFinalSymlink?: boolean;
29
+ rejectUnresolvedSymlinks: true;
30
+ }>;
31
+ export declare function admitFileLockSyncRootPath(authority: FileLockSyncRootAuthority, requestedPath: string): FileLockSyncRootPath;
32
+ export declare function assertFileLockSyncRootResolvedPathCurrent(pathAuthority: FileLockSyncRootPath): void;
33
+ export declare function assertFileLockSyncRootPathsCurrent(paths: readonly FileLockSyncRootPath[], invokeAuthority?: boolean, pathsAlreadyCurrent?: boolean): void;
34
+ export declare function normalizeFileLockSyncTargetWithRoot(authority: FileLockSyncRootAuthority, resolvedTargetPath: string): string;
35
+ export {};
36
+ //# sourceMappingURL=file-lock-sync-root.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-lock-sync-root.d.ts","sourceRoot":"","sources":["../src/file-lock-sync-root.ts"],"names":[],"mappings":"AAQA,OAAO,EAEL,KAAK,WAAW,EACjB,MAAM,mBAAmB,CAAC;AAK3B,OAAO,KAAK,EAAE,IAAI,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAgBzD,2EAA2E;AAC3E,wBAAgB,+BAA+B,CAC7C,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,WAAW,EACpB,QAAQ,EAAE,YAAY,GACrB,IAAI,CAON;AAED,MAAM,MAAM,yBAAyB,GAAG,QAAQ,CAAC;IAC/C,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,WAAW,CAAC;IACrB,oBAAoB,CAAC,EAAE,MAAM,IAAI,CAAC;IAClC,aAAa,CAAC,EAAE,QAAQ,CAAC;QACvB,KAAK,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;QAC1B,QAAQ,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;KAC9B,CAAC,CAAC;IACH,SAAS,CAAC,EAAE,YAAY,CAAC,WAAW,CAAC,CAAC;IACtC,cAAc,EAAE,wBAAwB,CAAC;IACzC,UAAU,EAAE,wBAAwB,CAAC;IACrC,aAAa,EAAE,OAAO,CAAC;CACxB,CAAC,CAAC;AAEH,MAAM,MAAM,oBAAoB,GAAG,QAAQ,CAAC;IAC1C,SAAS,EAAE,yBAAyB,CAAC;IACrC,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,CAAC;CACtB,CAAC,CAAC;AAcH,wBAAgB,gCAAgC,CAC9C,QAAQ,EAAE,IAAI,GACb,yBAAyB,CAkD3B;AAED,wBAAgB,uCAAuC,CACrD,SAAS,EAAE,yBAAyB,GACnC,OAAO,CAOT;AAuDD,wBAAgB,qCAAqC,CACnD,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,yBAAyB,CAAC,eAAe,CAAC,EAClD,gBAAgB,UAAQ,GACvB,IAAI,CAsBN;AAED,KAAK,wBAAwB,GAAG,QAAQ,CAAC;IACvC,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,wBAAwB,EAAE,IAAI,CAAC;CAChC,CAAC,CAAC;AA6FH,wBAAgB,yBAAyB,CACvC,SAAS,EAAE,yBAAyB,EACpC,aAAa,EAAE,MAAM,GACpB,oBAAoB,CAetB;AAED,wBAAgB,yCAAyC,CACvD,aAAa,EAAE,oBAAoB,GAClC,IAAI,CASN;AAwBD,wBAAgB,kCAAkC,CAChD,KAAK,EAAE,SAAS,oBAAoB,EAAE,EACtC,eAAe,UAAQ,EACvB,mBAAmB,UAAQ,GAC1B,IAAI,CAuBN;AAwCD,wBAAgB,mCAAmC,CACjD,SAAS,EAAE,yBAAyB,EACpC,kBAAkB,EAAE,MAAM,GACzB,MAAM,CAeR"}
@@ -0,0 +1,361 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import {} from "./deny-mutations.js";
4
+ import { FsSafeError } from "./errors.js";
5
+ import { assertNoNulPathInput, isNotFoundPathError, isPathInside } from "./path.js";
6
+ import { resolveRootPathSync } from "./root-path.js";
7
+ import { isRootPathEscapeError } from "./root-path-errors.js";
8
+ import { admitPathInsideRoot } from "./root-boundary.js";
9
+ import { assertRootIdentityCurrentSync, } from "./root-context.js";
10
+ import { directoryComponentNotDirectoryError, outsideWorkspaceError, } from "./root-errors.js";
11
+ import { mutationSymlinkResolution, readSymlinkResolution, } from "./root-symlink-policy.js";
12
+ import { realpathSync } from "./realpath.js";
13
+ import { assertSynchronousCallbackResult } from "./mutation-authority.js";
14
+ import { assertNoWindowsPathAlias, pathForWindowsFilesystem, resolvePathPreservingWindowsRoot, } from "./windows-path-alias.js";
15
+ const retainedRootContexts = new WeakMap();
16
+ const retainedRootDefaults = new WeakMap();
17
+ /** @internal Registers only instances constructed by RootHandle itself. */
18
+ export function registerFileLockSyncRootAdapter(handle, context, defaults) {
19
+ const identity = context.rootIdentity;
20
+ if (typeof identity.dev !== "bigint" || typeof identity.ino !== "bigint")
21
+ return;
22
+ // Retain the already-created Root inputs directly. Registration adds no
23
+ // per-Root wrapper/context allocations for callers that never use sync locks.
24
+ retainedRootContexts.set(handle, context);
25
+ retainedRootDefaults.set(handle, defaults);
26
+ }
27
+ function snapshotPolicy(policy) {
28
+ if (policy === undefined)
29
+ return undefined;
30
+ const paths = policy.paths;
31
+ const prefixes = policy.prefixes;
32
+ return Object.freeze({
33
+ paths: paths === undefined ? undefined : Object.freeze([...paths]),
34
+ prefixes: prefixes === undefined ? undefined : Object.freeze([...prefixes]),
35
+ });
36
+ }
37
+ export function captureFileLockSyncRootAuthority(lockRoot) {
38
+ const adapter = lockRoot;
39
+ const retainedContext = retainedRootContexts.get(adapter);
40
+ const retainedDefaults = retainedRootDefaults.get(adapter);
41
+ if (!retainedContext || !retainedDefaults) {
42
+ throw new FsSafeError("helper-unavailable", "synchronous Root-backed file locks require a genuine fs-safe Root handle");
43
+ }
44
+ const identity = retainedContext.rootIdentity;
45
+ if (typeof identity.dev !== "bigint" || typeof identity.ino !== "bigint") {
46
+ throw new FsSafeError("helper-unavailable", "synchronous Root identity is unavailable");
47
+ }
48
+ const context = Object.freeze({
49
+ rootDir: retainedContext.rootDir,
50
+ rootGuard: retainedContext.rootGuard,
51
+ rootIdentity: Object.freeze({ dev: identity.dev, ino: identity.ino }),
52
+ rootReal: retainedContext.rootReal,
53
+ rootWithSep: retainedContext.rootWithSep,
54
+ });
55
+ const defaults = retainedDefaults;
56
+ const assertBeforeMutation = defaults.assertBeforeMutation;
57
+ const denyMutations = snapshotPolicy(defaults.denyMutations);
58
+ const hardlinks = defaults.hardlinks;
59
+ const mutationSymlinks = defaults.mutationSymlinks;
60
+ const symlinks = defaults.symlinks;
61
+ // Snapshot all defaults before the Root check and mutation-policy validation.
62
+ assertRootIdentityCurrentSync(context);
63
+ const mutation = mutationSymlinkResolution(mutationSymlinks);
64
+ const mutationPolicy = Object.freeze({
65
+ rejectSymlinks: mutation.rejectSymlinks,
66
+ rejectFinalSymlink: mutation.rejectFinalSymlink,
67
+ rejectUnresolvedSymlinks: true,
68
+ });
69
+ const readPolicy = Object.freeze({
70
+ ...readSymlinkResolution(symlinks),
71
+ rejectUnresolvedSymlinks: true,
72
+ });
73
+ return Object.freeze({
74
+ adapter,
75
+ context,
76
+ assertBeforeMutation,
77
+ denyMutations,
78
+ hardlinks,
79
+ mutationPolicy,
80
+ readPolicy,
81
+ policiesMatch: mutationPolicy.rejectSymlinks === readPolicy.rejectSymlinks &&
82
+ mutationPolicy.rejectFinalSymlink === readPolicy.rejectFinalSymlink,
83
+ });
84
+ }
85
+ export function invokeFileLockSyncRootMutationAuthority(authority) {
86
+ const assertion = authority.assertBeforeMutation;
87
+ if (!assertion)
88
+ return false;
89
+ // Match Root mutation semantics: callbacks are invoked unbound and a
90
+ // thenable result cannot authorize a synchronous filesystem mutation.
91
+ assertSynchronousCallbackResult(assertion(), "assertBeforeMutation");
92
+ return true;
93
+ }
94
+ function samePath(left, right) {
95
+ return path.relative(path.resolve(left), path.resolve(right)) === "";
96
+ }
97
+ function absoluteRootPath(authority, relativePath) {
98
+ const segments = relativePath.split(path.posix.sep);
99
+ const absolute = path.join(authority.context.rootReal, ...segments);
100
+ assertNoWindowsPathAlias(absolute, "filesystem", "sidecar lock path uses a Windows filesystem namespace alias");
101
+ return absolute;
102
+ }
103
+ function assertValidPolicyEntries(entries) {
104
+ const captured = [];
105
+ for (const entry of entries ?? []) {
106
+ if (!entry || !path.isAbsolute(entry)) {
107
+ throw new FsSafeError("invalid-path", "deny mutation paths must be non-empty absolute paths");
108
+ }
109
+ assertNoNulPathInput(entry, "deny mutation path contains a NUL byte");
110
+ assertNoWindowsPathAlias(entry, "filesystem", "deny mutation path uses a Windows filesystem namespace alias");
111
+ captured.push(resolvePathPreservingWindowsRoot(entry));
112
+ }
113
+ return captured;
114
+ }
115
+ function mutationComparablePaths(pathname) {
116
+ const resolved = resolvePathPreservingWindowsRoot(pathname);
117
+ assertNoWindowsPathAlias(resolved, "filesystem", "mutation path uses a Windows filesystem namespace alias");
118
+ let cursor = resolved;
119
+ const missing = [];
120
+ while (path.parse(cursor).root !== cursor) {
121
+ try {
122
+ fs.lstatSync(pathForWindowsFilesystem(cursor));
123
+ break;
124
+ }
125
+ catch (error) {
126
+ if (!isNotFoundPathError(error))
127
+ throw error;
128
+ missing.unshift(path.basename(cursor));
129
+ cursor = path.dirname(cursor);
130
+ }
131
+ }
132
+ // Deny policy is an authority boundary: unlike advisory key normalization,
133
+ // an ambiguous existing ancestor must not silently fall back to lexical form.
134
+ const canonicalAncestor = realpathSync.native(pathForWindowsFilesystem(cursor));
135
+ assertNoWindowsPathAlias(canonicalAncestor, "filesystem", "mutation path uses a Windows filesystem namespace alias");
136
+ const canonical = missing.length === 0
137
+ ? canonicalAncestor
138
+ : path.resolve(canonicalAncestor, ...missing);
139
+ assertNoWindowsPathAlias(canonical, "filesystem", "mutation path uses a Windows filesystem namespace alias");
140
+ return samePath(resolved, canonical) ? [resolved] : [resolved, canonical];
141
+ }
142
+ export function assertFileLockSyncRootMutationAllowed(pathname, policy, protectAncestors = false) {
143
+ if (!policy?.paths?.length && !policy?.prefixes?.length)
144
+ return;
145
+ const targets = mutationComparablePaths(pathname);
146
+ for (const entry of assertValidPolicyEntries(policy.paths)) {
147
+ for (const denied of mutationComparablePaths(entry)) {
148
+ for (const target of targets) {
149
+ if ((isPathInside(denied, target) && isPathInside(target, denied)) ||
150
+ (protectAncestors && isPathInside(target, denied))) {
151
+ throw new FsSafeError("denied-path", "path is denied by denyMutations policy");
152
+ }
153
+ }
154
+ }
155
+ }
156
+ for (const entry of assertValidPolicyEntries(policy.prefixes)) {
157
+ for (const denied of mutationComparablePaths(entry)) {
158
+ for (const target of targets) {
159
+ if (isPathInside(denied, target) || (protectAncestors && isPathInside(target, denied))) {
160
+ throw new FsSafeError("denied-path", "path is denied by denyMutations policy");
161
+ }
162
+ }
163
+ }
164
+ }
165
+ }
166
+ function resolveAdmittedPath(authority, absolutePath, policy, rootPath) {
167
+ const context = authority.context;
168
+ let resolved;
169
+ try {
170
+ resolved = resolveRootPathSync({
171
+ absolutePath,
172
+ // The shared resolver identity-gates ambiguous Windows matches against the
173
+ // original Root spelling before rebasing them to the retained canonical root.
174
+ rootPath,
175
+ rootCanonicalPath: context.rootReal,
176
+ rootIdentity: context.rootIdentity,
177
+ boundaryLabel: "sidecar lock root",
178
+ ...policy,
179
+ });
180
+ }
181
+ catch (error) {
182
+ if (isRootPathEscapeError(error)) {
183
+ throw new FsSafeError("outside-workspace", "sidecar lock path is outside lockRoot", { cause: error });
184
+ }
185
+ throw error;
186
+ }
187
+ const admitted = admitPathInsideRoot({
188
+ rootPath: context.rootReal,
189
+ candidatePath: resolved.canonicalPath,
190
+ rootIdentity: context.rootIdentity,
191
+ });
192
+ if (!admitted || admitted.relativePath === "" || samePath(admitted.path, context.rootReal)) {
193
+ throw outsideWorkspaceError();
194
+ }
195
+ return {
196
+ path: admitted.path,
197
+ relativePath: admitted.relativePath.split(path.sep).join(path.posix.sep),
198
+ };
199
+ }
200
+ function resolveBothPolicies(authority, absolutePath, rootPath) {
201
+ const { mutationPolicy, readPolicy, policiesMatch } = authority;
202
+ const mutation = resolveAdmittedPath(authority, absolutePath, mutationPolicy, rootPath);
203
+ const readable = policiesMatch
204
+ ? mutation
205
+ : resolveAdmittedPath(authority, absolutePath, readPolicy, rootPath);
206
+ if (!samePath(mutation.path, readable.path)) {
207
+ throw new FsSafeError("path-mismatch", "sidecar read and mutation paths resolve differently");
208
+ }
209
+ assertFileLockSyncRootMutationAllowed(mutation.path, authority.denyMutations);
210
+ return mutation;
211
+ }
212
+ function admitRequestedRootPrefix(authority, requestedPath) {
213
+ const context = authority.context;
214
+ const identityCache = process.platform === "win32" ? new Map() : undefined;
215
+ const original = admitPathInsideRoot({
216
+ rootPath: context.rootDir,
217
+ candidatePath: requestedPath,
218
+ rootIdentity: context.rootIdentity,
219
+ resolveCandidateRoot: context.rootDir !== context.rootReal,
220
+ identityCache,
221
+ });
222
+ if (original && original.relativePath !== "") {
223
+ return { path: original.path, rootPath: context.rootDir };
224
+ }
225
+ const canonical = context.rootDir === context.rootReal ? undefined : admitPathInsideRoot({
226
+ rootPath: context.rootReal,
227
+ candidatePath: requestedPath,
228
+ rootIdentity: context.rootIdentity,
229
+ identityCache,
230
+ });
231
+ if (!canonical || canonical.relativePath === "")
232
+ throw outsideWorkspaceError();
233
+ return { path: canonical.path, rootPath: context.rootReal };
234
+ }
235
+ export function admitFileLockSyncRootPath(authority, requestedPath) {
236
+ assertNoNulPathInput(requestedPath, "sidecar lock path contains a NUL byte");
237
+ const resolved = resolvePathPreservingWindowsRoot(requestedPath);
238
+ assertNoWindowsPathAlias(resolved, "filesystem", "sidecar lock path uses a Windows filesystem namespace alias");
239
+ assertRootIdentityCurrentSync(authority.context);
240
+ const prefixed = admitRequestedRootPrefix(authority, resolved);
241
+ const selected = resolveBothPolicies(authority, prefixed.path, prefixed.rootPath);
242
+ assertRootIdentityCurrentSync(authority.context);
243
+ const admitted = Object.freeze({
244
+ authority,
245
+ path: selected.path,
246
+ relativePath: selected.relativePath,
247
+ });
248
+ assertMissingParentMutationsAllowed(admitted);
249
+ return admitted;
250
+ }
251
+ export function assertFileLockSyncRootResolvedPathCurrent(pathAuthority) {
252
+ const resolved = resolveBothPolicies(pathAuthority.authority, absoluteRootPath(pathAuthority.authority, pathAuthority.relativePath), pathAuthority.authority.context.rootReal);
253
+ if (!samePath(resolved.path, pathAuthority.path)) {
254
+ throw new FsSafeError("path-mismatch", "sidecar lock path changed during operation");
255
+ }
256
+ }
257
+ function assertMissingParentMutationsAllowed(pathAuthority) {
258
+ const authority = pathAuthority.authority;
259
+ const context = authority.context;
260
+ const relativeParent = path.posix.dirname(pathAuthority.relativePath);
261
+ const relative = relativeParent === "." ? "" : relativeParent;
262
+ let current = context.rootReal;
263
+ let missing = false;
264
+ for (const segment of relative.split(path.posix.sep).filter(Boolean)) {
265
+ current = path.join(current, segment);
266
+ if (!missing) {
267
+ try {
268
+ const stat = fs.lstatSync(pathForWindowsFilesystem(current), { bigint: true });
269
+ if (stat.isSymbolicLink() || !stat.isDirectory())
270
+ throw directoryComponentNotDirectoryError();
271
+ }
272
+ catch (error) {
273
+ if (!isNotFoundPathError(error))
274
+ throw error;
275
+ missing = true;
276
+ }
277
+ }
278
+ if (missing)
279
+ assertFileLockSyncRootMutationAllowed(current, authority.denyMutations);
280
+ }
281
+ }
282
+ export function assertFileLockSyncRootPathsCurrent(paths, invokeAuthority = false, pathsAlreadyCurrent = false) {
283
+ const authority = paths[0]?.authority;
284
+ for (const pathAuthority of paths) {
285
+ if (authority && pathAuthority.authority !== authority) {
286
+ throw new FsSafeError("path-mismatch", "sidecar paths use different Root authority");
287
+ }
288
+ }
289
+ if (!pathsAlreadyCurrent) {
290
+ if (authority)
291
+ assertRootIdentityCurrentSync(authority.context);
292
+ for (const pathAuthority of paths) {
293
+ assertFileLockSyncRootResolvedPathCurrent(pathAuthority);
294
+ assertMissingParentMutationsAllowed(pathAuthority);
295
+ }
296
+ if (authority)
297
+ assertRootIdentityCurrentSync(authority.context);
298
+ }
299
+ if (invokeAuthority && authority && invokeFileLockSyncRootMutationAuthority(authority)) {
300
+ assertRootIdentityCurrentSync(authority.context);
301
+ for (const pathAuthority of paths) {
302
+ assertFileLockSyncRootResolvedPathCurrent(pathAuthority);
303
+ assertMissingParentMutationsAllowed(pathAuthority);
304
+ }
305
+ assertRootIdentityCurrentSync(authority.context);
306
+ }
307
+ }
308
+ function resolveTargetPathViaExistingAncestorSync(targetPath) {
309
+ const normalized = resolvePathPreservingWindowsRoot(targetPath);
310
+ const canonicalize = process.platform === "win32" ? realpathSync.native : realpathSync;
311
+ let cursor = normalized;
312
+ const missing = [];
313
+ while (true) {
314
+ try {
315
+ fs.lstatSync(pathForWindowsFilesystem(cursor));
316
+ try {
317
+ const canonicalAncestor = canonicalize(pathForWindowsFilesystem(cursor));
318
+ assertNoWindowsPathAlias(canonicalAncestor, "filesystem", "file-lock target ancestor uses a Windows filesystem namespace alias");
319
+ return missing.length === 0
320
+ ? resolvePathPreservingWindowsRoot(canonicalAncestor)
321
+ : resolvePathPreservingWindowsRoot(path.resolve(canonicalAncestor, ...missing));
322
+ }
323
+ catch (error) {
324
+ if (!isNotFoundPathError(error))
325
+ throw error;
326
+ // A dangling link is an existing lexical entry but not a canonical
327
+ // ancestor. Continue at its parent instead of abandoning native
328
+ // long/short-name normalization for the rest of the target path.
329
+ }
330
+ }
331
+ catch (error) {
332
+ if (!isNotFoundPathError(error))
333
+ throw error;
334
+ }
335
+ const parent = path.dirname(cursor);
336
+ if (parent === cursor) {
337
+ // Filesystem roots are expected to exist; preserve the underlying
338
+ // not-found diagnosis if a synthetic namespace violates that premise.
339
+ const canonicalAncestor = canonicalize(pathForWindowsFilesystem(cursor));
340
+ assertNoWindowsPathAlias(canonicalAncestor, "filesystem", "file-lock target ancestor uses a Windows filesystem namespace alias");
341
+ return missing.length === 0
342
+ ? resolvePathPreservingWindowsRoot(canonicalAncestor)
343
+ : resolvePathPreservingWindowsRoot(path.resolve(canonicalAncestor, ...missing));
344
+ }
345
+ missing.unshift(path.basename(cursor));
346
+ cursor = parent;
347
+ }
348
+ }
349
+ export function normalizeFileLockSyncTargetWithRoot(authority, resolvedTargetPath) {
350
+ assertNoNulPathInput(resolvedTargetPath, "file-lock target contains a NUL byte");
351
+ assertNoWindowsPathAlias(resolvedTargetPath, "filesystem", "file-lock target uses a Windows filesystem namespace alias");
352
+ assertRootIdentityCurrentSync(authority.context);
353
+ // Preserve POSIX's pathname-key behavior while making Windows resolve the
354
+ // complete existing prefix (including an existing 8.3-spelled filename).
355
+ const normalized = process.platform === "win32"
356
+ ? resolveTargetPathViaExistingAncestorSync(resolvedTargetPath)
357
+ : path.join(resolveTargetPathViaExistingAncestorSync(path.dirname(resolvedTargetPath)), path.basename(resolvedTargetPath));
358
+ assertNoWindowsPathAlias(normalized, "filesystem", "file-lock target uses a Windows filesystem namespace alias");
359
+ assertRootIdentityCurrentSync(authority.context);
360
+ return normalized;
361
+ }
@@ -0,0 +1,24 @@
1
+ import type { FileLockSyncAcquireOptions } from "./file-lock-sync.js";
2
+ import type { SidecarLockStaleRecovery } from "./sidecar-lock-types.js";
3
+ export type SyncStaleOptionsState<TPayload extends Record<string, unknown>> = {
4
+ shouldReclaimObserved?: boolean;
5
+ shouldReclaim?: FileLockSyncAcquireOptions<TPayload>["shouldReclaim"];
6
+ shouldRemoveObserved?: boolean;
7
+ shouldRemove?: FileLockSyncAcquireOptions<TPayload>["shouldRemoveStaleLock"];
8
+ };
9
+ /** Handle one exclusive-create conflict without surrendering canonical admission. */
10
+ export declare function handleSyncStaleAdmission<TPayload extends Record<string, unknown>>(params: {
11
+ hasToken(): boolean;
12
+ currentHeld(): unknown;
13
+ lockPath: string;
14
+ normalizedTargetPath: string;
15
+ options: FileLockSyncAcquireOptions<TPayload>;
16
+ parsePayload?: (raw: string) => unknown;
17
+ reclaimGuardPath: string;
18
+ retryLockFileDenial(error: unknown): boolean;
19
+ staleMs: number;
20
+ staleOptions: SyncStaleOptionsState<TPayload>;
21
+ staleRecovery?: SidecarLockStaleRecovery;
22
+ waitForRetry(): void;
23
+ }): void;
24
+ //# sourceMappingURL=file-lock-sync-stale-admission.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-lock-sync-stale-admission.d.ts","sourceRoot":"","sources":["../src/file-lock-sync-stale-admission.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,qBAAqB,CAAC;AAQtE,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,yBAAyB,CAAC;AAExE,MAAM,MAAM,qBAAqB,CAAC,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,IAAI;IAC5E,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC,aAAa,CAAC,EAAE,0BAA0B,CAAC,QAAQ,CAAC,CAAC,eAAe,CAAC,CAAC;IACtE,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,YAAY,CAAC,EAAE,0BAA0B,CAAC,QAAQ,CAAC,CAAC,uBAAuB,CAAC,CAAC;CAC9E,CAAC;AAEF,qFAAqF;AACrF,wBAAgB,wBAAwB,CAAC,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,EAAE;IACzF,QAAQ,IAAI,OAAO,CAAC;IACpB,WAAW,IAAI,OAAO,CAAC;IACvB,QAAQ,EAAE,MAAM,CAAC;IACjB,oBAAoB,EAAE,MAAM,CAAC;IAC7B,OAAO,EAAE,0BAA0B,CAAC,QAAQ,CAAC,CAAC;IAC9C,YAAY,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC;IACxC,gBAAgB,EAAE,MAAM,CAAC;IACzB,mBAAmB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC;IAC7C,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,qBAAqB,CAAC,QAAQ,CAAC,CAAC;IAC9C,aAAa,CAAC,EAAE,wBAAwB,CAAC;IACzC,YAAY,IAAI,IAAI,CAAC;CACtB,GAAG,IAAI,CAiLP"}