@openclaw/fs-safe 0.13.1 → 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 (256) hide show
  1. package/CHANGELOG.md +64 -1
  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-directory.d.ts.map +1 -1
  75. package/dist/file-store-sync-directory.js +14 -7
  76. package/dist/file-store-sync-write.js +3 -3
  77. package/dist/file-store.d.ts.map +1 -1
  78. package/dist/file-store.js +47 -12
  79. package/dist/guest-dispatch-python.js +1 -1
  80. package/dist/json-document-store.d.ts.map +1 -1
  81. package/dist/json-document-store.js +22 -15
  82. package/dist/json-durable-queue-ownership.d.ts +0 -1
  83. package/dist/json-durable-queue-ownership.d.ts.map +1 -1
  84. package/dist/json-durable-queue-ownership.js +0 -6
  85. package/dist/native-binding.d.ts +8 -0
  86. package/dist/native-binding.d.ts.map +1 -1
  87. package/dist/native-parent-admission.d.ts +3 -2
  88. package/dist/native-parent-admission.d.ts.map +1 -1
  89. package/dist/native-parent-admission.js +24 -5
  90. package/dist/native-pinned-write-windows.d.ts.map +1 -1
  91. package/dist/native-pinned-write-windows.js +7 -7
  92. package/dist/native-pinned-write.d.ts.map +1 -1
  93. package/dist/native-pinned-write.js +202 -172
  94. package/dist/native-policy-directory-observation.d.ts +12 -0
  95. package/dist/native-policy-directory-observation.d.ts.map +1 -0
  96. package/dist/native-policy-directory-observation.js +61 -0
  97. package/dist/native-policy-parent-windows.d.ts +14 -0
  98. package/dist/native-policy-parent-windows.d.ts.map +1 -0
  99. package/dist/native-policy-parent-windows.js +200 -0
  100. package/dist/native-rename-outcome.d.ts +4 -0
  101. package/dist/native-rename-outcome.d.ts.map +1 -0
  102. package/dist/native-rename-outcome.js +8 -0
  103. package/dist/native-staged-file.d.ts +2 -2
  104. package/dist/native-staged-file.d.ts.map +1 -1
  105. package/dist/native-staged-file.js +42 -40
  106. package/dist/output.d.ts.map +1 -1
  107. package/dist/output.js +12 -8
  108. package/dist/path-prefix.d.ts.map +1 -1
  109. package/dist/path-prefix.js +30 -8
  110. package/dist/path-suffix-aliases.d.ts +2 -0
  111. package/dist/path-suffix-aliases.d.ts.map +1 -1
  112. package/dist/path-suffix-aliases.js +25 -17
  113. package/dist/permission-exec.d.ts +2 -0
  114. package/dist/permission-exec.d.ts.map +1 -1
  115. package/dist/permission-exec.js +150 -21
  116. package/dist/permissions-windows.js +1 -1
  117. package/dist/pinned-mutation-admission.d.ts.map +1 -1
  118. package/dist/pinned-mutation-admission.js +23 -29
  119. package/dist/pinned-mutation-observation.d.ts +7 -2
  120. package/dist/pinned-mutation-observation.d.ts.map +1 -1
  121. package/dist/pinned-mutation-observation.js +81 -18
  122. package/dist/pinned-mutation-shared-route.d.ts +1 -0
  123. package/dist/pinned-mutation-shared-route.d.ts.map +1 -1
  124. package/dist/pinned-mutation-shared-route.js +1 -1
  125. package/dist/pinned-write-types.d.ts +2 -0
  126. package/dist/pinned-write-types.d.ts.map +1 -1
  127. package/dist/private-temp-workspace.d.ts.map +1 -1
  128. package/dist/private-temp-workspace.js +75 -121
  129. package/dist/regular-file.d.ts.map +1 -1
  130. package/dist/regular-file.js +1 -15
  131. package/dist/replace-directory.d.ts.map +1 -1
  132. package/dist/replace-directory.js +256 -18
  133. package/dist/replace-file-copy-fallback.d.ts.map +1 -1
  134. package/dist/replace-file-copy-fallback.js +62 -70
  135. package/dist/replace-file-copy-source.d.ts.map +1 -1
  136. package/dist/replace-file-copy-source.js +10 -12
  137. package/dist/replace-file-temp-owner.d.ts +5 -2
  138. package/dist/replace-file-temp-owner.d.ts.map +1 -1
  139. package/dist/replace-file-temp-owner.js +65 -30
  140. package/dist/replace-file.js +6 -6
  141. package/dist/retained-directory-replacement.d.ts +26 -0
  142. package/dist/retained-directory-replacement.d.ts.map +1 -0
  143. package/dist/retained-directory-replacement.js +193 -0
  144. package/dist/root-boundary.d.ts +1 -0
  145. package/dist/root-boundary.d.ts.map +1 -1
  146. package/dist/root-boundary.js +4 -0
  147. package/dist/root-context.d.ts +0 -8
  148. package/dist/root-context.d.ts.map +1 -1
  149. package/dist/root-context.js +0 -3
  150. package/dist/root-create-input.d.ts +6 -0
  151. package/dist/root-create-input.d.ts.map +1 -1
  152. package/dist/root-create-input.js +5 -1
  153. package/dist/root-directory-creation.d.ts.map +1 -1
  154. package/dist/root-directory-creation.js +5 -4
  155. package/dist/root-directory-list.d.ts +1 -0
  156. package/dist/root-directory-list.d.ts.map +1 -1
  157. package/dist/root-directory-list.js +1 -0
  158. package/dist/root-impl.d.ts.map +1 -1
  159. package/dist/root-impl.js +20 -11
  160. package/dist/root-move-noreplace.d.ts.map +1 -1
  161. package/dist/root-move-noreplace.js +2 -2
  162. package/dist/root-path-errors.d.ts +1 -0
  163. package/dist/root-path-errors.d.ts.map +1 -1
  164. package/dist/root-path-errors.js +11 -2
  165. package/dist/root-path-existing.d.ts.map +1 -1
  166. package/dist/root-path-existing.js +12 -37
  167. package/dist/root-path.js +1 -13
  168. package/dist/root-remove.d.ts +1 -0
  169. package/dist/root-remove.d.ts.map +1 -1
  170. package/dist/root-remove.js +4 -0
  171. package/dist/root-walk.d.ts +1 -1
  172. package/dist/root-walk.d.ts.map +1 -1
  173. package/dist/root-walk.js +17 -2
  174. package/dist/root-write-admission.d.ts +0 -2
  175. package/dist/root-write-admission.d.ts.map +1 -1
  176. package/dist/root-write-admission.js +1 -15
  177. package/dist/root-write-compatibility.d.ts +1 -2
  178. package/dist/root-write-compatibility.d.ts.map +1 -1
  179. package/dist/root-write-compatibility.js +17 -68
  180. package/dist/root-write-complete-parent.d.ts.map +1 -1
  181. package/dist/root-write-complete-parent.js +10 -24
  182. package/dist/root-write-lock-binding.d.ts +15 -0
  183. package/dist/root-write-lock-binding.d.ts.map +1 -0
  184. package/dist/root-write-lock-binding.js +162 -0
  185. package/dist/root-write-verification.d.ts.map +1 -1
  186. package/dist/root-write-verification.js +29 -42
  187. package/dist/secret-file.d.ts.map +1 -1
  188. package/dist/secret-file.js +2 -24
  189. package/dist/secret-read-async.d.ts.map +1 -1
  190. package/dist/secret-read-async.js +3 -24
  191. package/dist/secret-read-policy.d.ts +6 -2
  192. package/dist/secret-read-policy.d.ts.map +1 -1
  193. package/dist/secret-read-policy.js +26 -2
  194. package/dist/sibling-temp.d.ts.map +1 -1
  195. package/dist/sibling-temp.js +23 -12
  196. package/dist/sidecar-lock-acquire.d.ts +2 -28
  197. package/dist/sidecar-lock-acquire.d.ts.map +1 -1
  198. package/dist/sidecar-lock-acquire.js +288 -199
  199. package/dist/sidecar-lock-admission-context.d.ts +19 -0
  200. package/dist/sidecar-lock-admission-context.d.ts.map +1 -0
  201. package/dist/sidecar-lock-admission-context.js +60 -0
  202. package/dist/sidecar-lock-admission-parser.d.ts +43 -0
  203. package/dist/sidecar-lock-admission-parser.d.ts.map +1 -0
  204. package/dist/sidecar-lock-admission-parser.js +113 -0
  205. package/dist/sidecar-lock-admission.d.ts +35 -0
  206. package/dist/sidecar-lock-admission.d.ts.map +1 -0
  207. package/dist/sidecar-lock-admission.js +7 -0
  208. package/dist/sidecar-lock-reclaim.d.ts +9 -4
  209. package/dist/sidecar-lock-reclaim.d.ts.map +1 -1
  210. package/dist/sidecar-lock-reclaim.js +80 -25
  211. package/dist/sidecar-lock-stale-admission.d.ts +39 -0
  212. package/dist/sidecar-lock-stale-admission.d.ts.map +1 -0
  213. package/dist/sidecar-lock-stale-admission.js +232 -0
  214. package/dist/sidecar-lock-target.d.ts +8 -0
  215. package/dist/sidecar-lock-target.d.ts.map +1 -0
  216. package/dist/sidecar-lock-target.js +55 -0
  217. package/dist/sidecar-lock.d.ts.map +1 -1
  218. package/dist/sidecar-lock.js +100 -16
  219. package/dist/staged-directory.d.ts +5 -2
  220. package/dist/staged-directory.d.ts.map +1 -1
  221. package/dist/staged-directory.js +37 -1
  222. package/dist/temp-workspace-admission.d.ts.map +1 -1
  223. package/dist/temp-workspace-admission.js +28 -4
  224. package/dist/temp-workspace-descriptor.d.ts.map +1 -1
  225. package/dist/temp-workspace-descriptor.js +9 -27
  226. package/dist/temp-workspace-owner.d.ts.map +1 -1
  227. package/dist/temp-workspace-owner.js +8 -8
  228. package/dist/walk.d.ts +5 -1
  229. package/dist/walk.d.ts.map +1 -1
  230. package/dist/walk.js +19 -6
  231. package/dist/windows-owner.d.ts.map +1 -1
  232. package/dist/windows-owner.js +2 -2
  233. package/docs/advanced.md +1 -1
  234. package/docs/archive.md +36 -9
  235. package/docs/atomic.md +85 -8
  236. package/docs/copy.md +35 -0
  237. package/docs/file-store.md +19 -0
  238. package/docs/guest.md +1 -1
  239. package/docs/json-store.md +5 -0
  240. package/docs/native-helper.md +10 -3
  241. package/docs/native.md +8 -0
  242. package/docs/output.md +6 -0
  243. package/docs/path-prefix.md +10 -0
  244. package/docs/path-suffix-aliases.md +51 -6
  245. package/docs/permissions.md +13 -0
  246. package/docs/public-api.md +3 -2
  247. package/docs/root.md +22 -3
  248. package/docs/sidecar-lock.md +109 -4
  249. package/docs/staged-file.md +7 -3
  250. package/docs/temp.md +20 -3
  251. package/docs/walk.md +67 -1
  252. package/docs/writing.md +11 -1
  253. package/package.json +10 -10
  254. package/dist/darwin-acl.d.ts +0 -4
  255. package/dist/darwin-acl.d.ts.map +0 -1
  256. package/dist/darwin-acl.js +0 -24
@@ -0,0 +1,66 @@
1
+ import fs from "node:fs";
2
+ import { foreignSyncHeldLock } from "./file-lock-sync-admission.js";
3
+ import path from "node:path";
4
+ import { FsSafeError } from "./errors.js";
5
+ import {} from "./file-lock-sync-root.js";
6
+ import { removeFileLockSyncRootFile } from "./file-lock-sync-root-mutation.js";
7
+ import { createSuppressedError } from "./suppressed-error.js";
8
+ import { createRootSyncHeldLockHandle, verifyRootSyncHeldLock, } from "./file-lock-sync-root-held.js";
9
+ export class FileLockSyncRootArbitrationCollision extends FsSafeError {
10
+ constructor() {
11
+ super("path-mismatch", "file lock arbitration changed during local creation");
12
+ }
13
+ }
14
+ function sameRootLockPath(left, right) {
15
+ return left.relativePath === right.relativePath && path.relative(left.path, right.path) === "";
16
+ }
17
+ export function tryReuseCurrentRootSyncHeldLock(arbitration) {
18
+ const held = arbitration.heldLocks.get(arbitration.normalizedTargetPath);
19
+ const reusable = held && !foreignSyncHeldLock("root", arbitration.normalizedTargetPath) &&
20
+ arbitration.reentrantOwner !== undefined &&
21
+ held.reentrantOwner !== undefined &&
22
+ arbitration.reentrantOwner === held.reentrantOwner &&
23
+ arbitration.authority.adapter === held.rootAuthority.adapter &&
24
+ sameRootLockPath(arbitration.lockRootPath, held.rootPath) &&
25
+ held.releaseState !== "released";
26
+ if (!reusable)
27
+ return undefined;
28
+ if (!verifyRootSyncHeldLock(held)) {
29
+ throw new FsSafeError("path-mismatch", "held sidecar lock changed before reentrant reuse");
30
+ }
31
+ // Verification can invoke a custom parser. Re-fetch the exact entry before
32
+ // granting a reference, while retaining supported releasing/exit states.
33
+ if (arbitration.heldLocks.get(arbitration.normalizedTargetPath) !== held ||
34
+ held.releaseState === "released" || foreignSyncHeldLock("root", arbitration.normalizedTargetPath)) {
35
+ throw new FsSafeError("path-mismatch", "held sidecar lock changed during reentrant reuse");
36
+ }
37
+ held.refCount += 1;
38
+ held.revision += 1;
39
+ return createRootSyncHeldLockHandle(held);
40
+ }
41
+ export function cleanupCreatedRootSyncLock(lockRootPath, fd, receipt, timer) {
42
+ let timerCleanupFailed = false;
43
+ let timerCleanupError;
44
+ try {
45
+ if (timer)
46
+ clearInterval(timer);
47
+ }
48
+ catch (error) {
49
+ timerCleanupFailed = true;
50
+ timerCleanupError = error;
51
+ }
52
+ try {
53
+ fs.closeSync(fd);
54
+ if (!removeFileLockSyncRootFile(lockRootPath, receipt)) {
55
+ throw new FsSafeError("path-mismatch", "created sidecar lock changed before cleanup");
56
+ }
57
+ }
58
+ catch (fileCleanupError) {
59
+ if (timerCleanupFailed) {
60
+ throw createSuppressedError(timerCleanupError, fileCleanupError, "unpublished lock timer and file cleanup both failed");
61
+ }
62
+ throw fileCleanupError;
63
+ }
64
+ if (timerCleanupFailed)
65
+ throw timerCleanupError;
66
+ }
@@ -0,0 +1,34 @@
1
+ import type { FileLockSyncHandle } from "./file-lock-sync.js";
2
+ import { type SidecarLockSnapshot } from "./sidecar-lock-reclaim.js";
3
+ import { type FileLockSyncRootAuthority, type FileLockSyncRootPath } from "./file-lock-sync-root.js";
4
+ import { type FileLockSyncRootFileReceipt, type FileLockSyncRootSnapshot } from "./file-lock-sync-root-io.js";
5
+ type RootSyncHeldLockReleaseState = "active" | "releasing" | "exit-cleaning" | "released";
6
+ type RootSyncHeldLockHandleDisposition = {
7
+ held: RootSyncHeldLock;
8
+ released: boolean;
9
+ };
10
+ export type RootSyncHeldLock = {
11
+ deferredExitReleases?: Set<RootSyncHeldLockHandleDisposition>;
12
+ fd: number | undefined;
13
+ lockPath: string;
14
+ normalizedTargetPath: string;
15
+ parsePayload?: (raw: string) => unknown;
16
+ refCount: number;
17
+ reentrantOwner?: string;
18
+ releaseState: RootSyncHeldLockReleaseState;
19
+ revision: number;
20
+ rootAuthority: FileLockSyncRootAuthority;
21
+ rootPath: FileLockSyncRootPath;
22
+ rootReceipt: FileLockSyncRootFileReceipt;
23
+ snapshot: SidecarLockSnapshot;
24
+ timer?: NodeJS.Timeout;
25
+ };
26
+ export declare function readRootSidecarSnapshotSync(rootPath: FileLockSyncRootPath, parsePayload?: (raw: string) => unknown, onOpenFailure?: (error: unknown) => void, expectedReceipt?: FileLockSyncRootFileReceipt): FileLockSyncRootSnapshot | null;
27
+ export declare function getRootSyncHeldLocks(): Map<string, RootSyncHeldLock>;
28
+ export declare function ensureRootSyncExitCleanupRegistered(): void;
29
+ export declare function verifyRootSyncHeldLock(held: RootSyncHeldLock): boolean;
30
+ export declare function createRootSyncHeldLockHandle(held: RootSyncHeldLock): FileLockSyncHandle;
31
+ export declare function isRootSyncHeldLockHandle(handle: FileLockSyncHandle): boolean;
32
+ export declare function withRootSyncHeldLockHandle<T>(lock: FileLockSyncHandle, fn: () => T): T;
33
+ export {};
34
+ //# sourceMappingURL=file-lock-sync-root-held.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-lock-sync-root-held.d.ts","sourceRoot":"","sources":["../src/file-lock-sync-root-held.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACV,kBAAkB,EACnB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAGL,KAAK,mBAAmB,EACzB,MAAM,2BAA2B,CAAC;AAEnC,OAAO,EACL,KAAK,yBAAyB,EAC9B,KAAK,oBAAoB,EAC1B,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAIL,KAAK,2BAA2B,EAChC,KAAK,wBAAwB,EAC9B,MAAM,6BAA6B,CAAC;AAQrC,KAAK,4BAA4B,GAAG,QAAQ,GAAG,WAAW,GAAG,eAAe,GAAG,UAAU,CAAC;AAE1F,KAAK,iCAAiC,GAAG;IACvC,IAAI,EAAE,gBAAgB,CAAC;IACvB,QAAQ,EAAE,OAAO,CAAC;CACnB,CAAC;AAOF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,oBAAoB,CAAC,EAAE,GAAG,CAAC,iCAAiC,CAAC,CAAC;IAC9D,EAAE,EAAE,MAAM,GAAG,SAAS,CAAC;IACvB,QAAQ,EAAE,MAAM,CAAC;IACjB,oBAAoB,EAAE,MAAM,CAAC;IAC7B,YAAY,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC;IACxC,QAAQ,EAAE,MAAM,CAAC;IACjB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,YAAY,EAAE,4BAA4B,CAAC;IAC3C,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,yBAAyB,CAAC;IACzC,QAAQ,EAAE,oBAAoB,CAAC;IAC/B,WAAW,EAAE,2BAA2B,CAAC;IACzC,QAAQ,EAAE,mBAAmB,CAAC;IAC9B,KAAK,CAAC,EAAE,MAAM,CAAC,OAAO,CAAC;CACxB,CAAC;AAQF,wBAAgB,2BAA2B,CACzC,QAAQ,EAAE,oBAAoB,EAC9B,YAAY,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,EACvC,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,EACxC,eAAe,CAAC,EAAE,2BAA2B,GAC5C,wBAAwB,GAAG,IAAI,CAmCjC;AAED,wBAAgB,oBAAoB,IAAI,GAAG,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAQpE;AA+KD,wBAAgB,mCAAmC,IAAI,IAAI,CAE1D;AAED,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,gBAAgB,GAAG,OAAO,CAWtE;AAsHD,wBAAgB,4BAA4B,CAAC,IAAI,EAAE,gBAAgB,GAAG,kBAAkB,CAqBvF;AAED,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,kBAAkB,GAAG,OAAO,CAE5E;AAED,wBAAgB,0BAA0B,CAAC,CAAC,EAC1C,IAAI,EAAE,kBAAkB,EACxB,EAAE,EAAE,MAAM,CAAC,GACV,CAAC,CAoBH"}
@@ -0,0 +1,393 @@
1
+ import fs from "node:fs";
2
+ import { getSyncLockAdmissions } from "./file-lock-sync-admission.js";
3
+ import { FsSafeError } from "./errors.js";
4
+ import { parseSidecarLockPayload, sidecarLockSnapshotMatches, } from "./sidecar-lock-reclaim.js";
5
+ import { createSuppressedError } from "./suppressed-error.js";
6
+ import {} from "./file-lock-sync-root.js";
7
+ import { fileLockSyncRootReceiptStillCurrent, fileLockSyncRootSnapshotStillCurrent, readFileLockSyncRootSnapshot, } from "./file-lock-sync-root-io.js";
8
+ import { removeFileLockSyncRootFile } from "./file-lock-sync-root-mutation.js";
9
+ import { ensureFileLockSyncRootExitCleanupRegistered } from "./file-lock-sync-root-registration.js";
10
+ const ROOT_SYNC_HELD_LOCKS_KEY = Symbol.for("fsSafe.syncRootSidecarLocks.v1");
11
+ const ROOT_SYNC_CLEANUP_ACTIVE_KEY = Symbol.for("fsSafe.syncRootSidecarLockCleanupActive.v1");
12
+ const MAX_CONTINUITY_CLEANUP_ATTEMPTS = 2;
13
+ const rootSyncHandleDispositions = new WeakMap();
14
+ function readRootSyncHeldReleaseState(held) {
15
+ return held.releaseState;
16
+ }
17
+ export function readRootSidecarSnapshotSync(rootPath, parsePayload, onOpenFailure, expectedReceipt) {
18
+ const rawSnapshot = readFileLockSyncRootSnapshot(rootPath, {
19
+ expectedReceipt,
20
+ onOpenFailure,
21
+ });
22
+ if (!rawSnapshot)
23
+ return null;
24
+ let parsedPayload;
25
+ try {
26
+ parsedPayload = parseSidecarLockPayload(rawSnapshot.snapshot.raw, parsePayload);
27
+ }
28
+ catch (parseError) {
29
+ if (parsePayload) {
30
+ try {
31
+ if (!fileLockSyncRootSnapshotStillCurrent(rootPath, rawSnapshot)) {
32
+ throw new FsSafeError("path-mismatch", "sidecar changed during payload parsing");
33
+ }
34
+ }
35
+ catch (admissionError) {
36
+ throw createSuppressedError(parseError, admissionError, "sidecar parser and post-callback admission both failed");
37
+ }
38
+ }
39
+ throw parseError;
40
+ }
41
+ if (parsePayload && !fileLockSyncRootSnapshotStillCurrent(rootPath, rawSnapshot)) {
42
+ return null;
43
+ }
44
+ return {
45
+ receipt: rawSnapshot.receipt,
46
+ snapshot: { ...rawSnapshot.snapshot, payload: parsedPayload },
47
+ };
48
+ }
49
+ export function getRootSyncHeldLocks() {
50
+ const globalWithState = globalThis;
51
+ if (!globalWithState[ROOT_SYNC_HELD_LOCKS_KEY]) {
52
+ globalWithState[ROOT_SYNC_HELD_LOCKS_KEY] = new Map();
53
+ }
54
+ return globalWithState[ROOT_SYNC_HELD_LOCKS_KEY];
55
+ }
56
+ function existingRootSyncHeldLocks() {
57
+ return globalThis[ROOT_SYNC_HELD_LOCKS_KEY];
58
+ }
59
+ function reactivateExactHeldLock(heldLocks, held, restoreConsumedReference) {
60
+ if (heldLocks.get(held.normalizedTargetPath) !== held || held.releaseState === "released") {
61
+ return false;
62
+ }
63
+ if (restoreConsumedReference)
64
+ held.refCount += 1;
65
+ held.releaseState = "active";
66
+ held.revision += 1;
67
+ return true;
68
+ }
69
+ function finishDeferredExitReleases(held) {
70
+ const deferred = held.deferredExitReleases;
71
+ if (!deferred)
72
+ return;
73
+ for (const disposition of deferred)
74
+ disposition.released = true;
75
+ deferred.clear();
76
+ held.deferredExitReleases = undefined;
77
+ }
78
+ function restoreDeferredExitReleases(held) {
79
+ const deferred = held.deferredExitReleases;
80
+ if (!deferred)
81
+ return 0;
82
+ const restored = deferred.size;
83
+ deferred.clear();
84
+ held.deferredExitReleases = undefined;
85
+ return restored;
86
+ }
87
+ function settleDeferredExitRelease(lock) {
88
+ const disposition = rootSyncHandleDispositions.get(lock);
89
+ if (!disposition || disposition.released)
90
+ return;
91
+ const deferred = disposition.held.deferredExitReleases;
92
+ // Scoped disposal may surrender only its own reference, after release has
93
+ // proven that exact handle token was tentatively consumed by exit cleanup.
94
+ if (!deferred?.delete(disposition))
95
+ return;
96
+ if (deferred.size === 0)
97
+ disposition.held.deferredExitReleases = undefined;
98
+ disposition.released = true;
99
+ }
100
+ function restoreExactHeldLockAfterExitCleanup(heldLocks, held) {
101
+ if (heldLocks.get(held.normalizedTargetPath) !== held || held.releaseState === "released")
102
+ return;
103
+ // Restore exactly the handle references tentatively consumed by exit
104
+ // callbacks. A retained successor is a distinct reference and must not be
105
+ // borrowed by a pending original handle.
106
+ held.refCount += restoreDeferredExitReleases(held);
107
+ if (held.refCount <= 0) {
108
+ // No tracked handle can retry. Drop only the record; the sidecar itself is
109
+ // deliberately left in place for policy-governed reclamation.
110
+ held.releaseState = "released";
111
+ held.revision += 1;
112
+ heldLocks.delete(held.normalizedTargetPath);
113
+ return;
114
+ }
115
+ held.releaseState = "active";
116
+ held.revision += 1;
117
+ }
118
+ function releaseAllRootSyncHeldLocks() {
119
+ const globalWithCleanup = globalThis;
120
+ if (globalWithCleanup[ROOT_SYNC_CLEANUP_ACTIVE_KEY])
121
+ return;
122
+ globalWithCleanup[ROOT_SYNC_CLEANUP_ACTIVE_KEY] = true;
123
+ try {
124
+ // A newListener callback can invoke the candidate handler before
125
+ // registration succeeds. Do not create acquisition state in that window.
126
+ const heldLocks = existingRootSyncHeldLocks();
127
+ if (!heldLocks)
128
+ return;
129
+ getSyncLockAdmissions().clear();
130
+ for (const [normalizedTargetPath, held] of heldLocks) {
131
+ if (heldLocks.get(normalizedTargetPath) !== held || held.releaseState !== "active")
132
+ continue;
133
+ held.releaseState = "exit-cleaning";
134
+ held.revision += 1;
135
+ let cleanupRevision = held.revision;
136
+ const timer = held.timer;
137
+ const fd = held.fd;
138
+ // Consume descriptor/timer ownership before either operation can invoke
139
+ // instrumented callbacks or recursively enter the public cleanup hook.
140
+ held.timer = undefined;
141
+ held.fd = undefined;
142
+ if (timer)
143
+ clearInterval(timer);
144
+ try {
145
+ if (fd !== undefined)
146
+ fs.closeSync(fd);
147
+ }
148
+ catch {
149
+ // Best-effort process-exit cleanup.
150
+ }
151
+ const entryIsContinuous = () => heldLocks.get(normalizedTargetPath) === held &&
152
+ held.releaseState === "exit-cleaning" &&
153
+ held.revision === cleanupRevision;
154
+ const beginFreshCleanupAttempt = () => {
155
+ held.releaseState = "exit-cleaning";
156
+ held.revision += 1;
157
+ cleanupRevision = held.revision;
158
+ };
159
+ if (!entryIsContinuous()) {
160
+ if (heldLocks.get(normalizedTargetPath) === held &&
161
+ readRootSyncHeldReleaseState(held) !== "released" && held.refCount === 0) {
162
+ beginFreshCleanupAttempt();
163
+ }
164
+ else {
165
+ restoreExactHeldLockAfterExitCleanup(heldLocks, held);
166
+ continue;
167
+ }
168
+ }
169
+ let cleaned = false;
170
+ for (let cleanupAttempt = 0; cleanupAttempt < MAX_CONTINUITY_CLEANUP_ATTEMPTS; cleanupAttempt += 1) {
171
+ let continuityInterrupted = false;
172
+ let removalFailed = false;
173
+ let removed = false;
174
+ try {
175
+ removed = removeFileLockSyncRootFile(held.rootPath, held.rootReceipt, held.snapshot, () => {
176
+ const current = entryIsContinuous();
177
+ if (!current)
178
+ continuityInterrupted = true;
179
+ return current;
180
+ });
181
+ }
182
+ catch {
183
+ // A surviving sidecar fails closed and can be reclaimed by policy.
184
+ removalFailed = true;
185
+ }
186
+ if (!removalFailed && removed && !continuityInterrupted && entryIsContinuous()) {
187
+ cleaned = true;
188
+ break;
189
+ }
190
+ const continuousEntry = entryIsContinuous();
191
+ const exactEntry = heldLocks.get(normalizedTargetPath) === held &&
192
+ readRootSyncHeldReleaseState(held) !== "released";
193
+ if (exactEntry && (continuityInterrupted || !continuousEntry) &&
194
+ held.refCount === 0 && cleanupAttempt + 1 < MAX_CONTINUITY_CLEANUP_ATTEMPTS) {
195
+ beginFreshCleanupAttempt();
196
+ continue;
197
+ }
198
+ if (continuousEntry && held.refCount > 0) {
199
+ // Preserve established best-effort process-exit behavior: after
200
+ // authority-safe removal declines or throws, discard the live record
201
+ // without attempting any less-restricted deletion.
202
+ cleaned = true;
203
+ break;
204
+ }
205
+ restoreExactHeldLockAfterExitCleanup(heldLocks, held);
206
+ break;
207
+ }
208
+ if (!cleaned)
209
+ continue;
210
+ finishDeferredExitReleases(held);
211
+ held.releaseState = "released";
212
+ held.revision += 1;
213
+ if (heldLocks.get(normalizedTargetPath) === held) {
214
+ heldLocks.delete(normalizedTargetPath);
215
+ }
216
+ }
217
+ }
218
+ finally {
219
+ globalWithCleanup[ROOT_SYNC_CLEANUP_ACTIVE_KEY] = false;
220
+ }
221
+ }
222
+ export function ensureRootSyncExitCleanupRegistered() {
223
+ ensureFileLockSyncRootExitCleanupRegistered(releaseAllRootSyncHeldLocks);
224
+ }
225
+ export function verifyRootSyncHeldLock(held) {
226
+ const current = readRootSidecarSnapshotSync(held.rootPath, held.parsePayload, undefined, held.rootReceipt);
227
+ if (!current)
228
+ return false;
229
+ return current.receipt.identity.dev === held.rootReceipt.identity.dev &&
230
+ current.receipt.identity.ino === held.rootReceipt.identity.ino &&
231
+ sidecarLockSnapshotMatches(current.snapshot, held.snapshot);
232
+ }
233
+ function releaseRootSyncHeldLock(held, disposition) {
234
+ const heldLocks = getRootSyncHeldLocks();
235
+ if (heldLocks.get(held.normalizedTargetPath) !== held || held.releaseState === "released")
236
+ return true;
237
+ if (held.releaseState !== "active") {
238
+ if (held.deferredExitReleases?.has(disposition))
239
+ return false;
240
+ if (held.refCount <= 0)
241
+ return false;
242
+ if (!fileLockSyncRootReceiptStillCurrent(held.rootPath, held.rootReceipt)) {
243
+ throw new FsSafeError("path-mismatch", "held sidecar lock changed before release");
244
+ }
245
+ held.refCount -= 1;
246
+ held.revision += 1;
247
+ // Exit cleanup, unlike an ordinary final release, has not consumed a
248
+ // handle reference of its own. Keep the last callback-entered handle
249
+ // retryable until cleanup either succeeds or restores it.
250
+ if (held.releaseState !== "exit-cleaning" || held.refCount > 0)
251
+ return true;
252
+ (held.deferredExitReleases ??= new Set()).add(disposition);
253
+ return false;
254
+ }
255
+ if (held.refCount > 1) {
256
+ if (!fileLockSyncRootReceiptStillCurrent(held.rootPath, held.rootReceipt)) {
257
+ throw new FsSafeError("path-mismatch", "held sidecar lock changed before release");
258
+ }
259
+ held.refCount -= 1;
260
+ held.revision += 1;
261
+ return true;
262
+ }
263
+ if (held.refCount !== 1)
264
+ return false;
265
+ held.refCount = 0;
266
+ held.releaseState = "releasing";
267
+ held.revision += 1;
268
+ let releaseRevision = held.revision;
269
+ const entryIsContinuous = () => heldLocks.get(held.normalizedTargetPath) === held &&
270
+ held.releaseState === "releasing" &&
271
+ held.revision === releaseRevision &&
272
+ held.refCount === 0;
273
+ const beginFreshReleaseAttempt = () => {
274
+ held.releaseState = "releasing";
275
+ held.revision += 1;
276
+ releaseRevision = held.revision;
277
+ };
278
+ const fd = held.fd;
279
+ held.fd = undefined;
280
+ try {
281
+ if (fd !== undefined)
282
+ fs.closeSync(fd);
283
+ }
284
+ catch (error) {
285
+ reactivateExactHeldLock(heldLocks, held, true);
286
+ throw error;
287
+ }
288
+ if (!entryIsContinuous()) {
289
+ if (heldLocks.get(held.normalizedTargetPath) !== held ||
290
+ readRootSyncHeldReleaseState(held) === "released") {
291
+ return true;
292
+ }
293
+ if (held.refCount > 0) {
294
+ reactivateExactHeldLock(heldLocks, held, false);
295
+ return true;
296
+ }
297
+ beginFreshReleaseAttempt();
298
+ }
299
+ for (let releaseAttempt = 0; releaseAttempt < MAX_CONTINUITY_CLEANUP_ATTEMPTS; releaseAttempt += 1) {
300
+ let continuityInterrupted = false;
301
+ let removed;
302
+ try {
303
+ removed = removeFileLockSyncRootFile(held.rootPath, held.rootReceipt, held.snapshot, () => {
304
+ const current = entryIsContinuous();
305
+ if (!current)
306
+ continuityInterrupted = true;
307
+ return current;
308
+ });
309
+ }
310
+ catch (error) {
311
+ reactivateExactHeldLock(heldLocks, held, true);
312
+ throw error;
313
+ }
314
+ if (!continuityInterrupted && entryIsContinuous()) {
315
+ if (!removed) {
316
+ reactivateExactHeldLock(heldLocks, held, true);
317
+ throw new FsSafeError("path-mismatch", "owned sidecar lock changed before release");
318
+ }
319
+ const timer = held.timer;
320
+ held.timer = undefined;
321
+ held.releaseState = "released";
322
+ held.revision += 1;
323
+ if (heldLocks.get(held.normalizedTargetPath) === held) {
324
+ heldLocks.delete(held.normalizedTargetPath);
325
+ }
326
+ if (timer)
327
+ clearInterval(timer);
328
+ return true;
329
+ }
330
+ if (heldLocks.get(held.normalizedTargetPath) !== held ||
331
+ readRootSyncHeldReleaseState(held) === "released") {
332
+ return true;
333
+ }
334
+ if (held.refCount > 0) {
335
+ reactivateExactHeldLock(heldLocks, held, false);
336
+ return true;
337
+ }
338
+ if (held.refCount === 0 && releaseAttempt + 1 < MAX_CONTINUITY_CLEANUP_ATTEMPTS) {
339
+ beginFreshReleaseAttempt();
340
+ continue;
341
+ }
342
+ reactivateExactHeldLock(heldLocks, held, true);
343
+ throw new FsSafeError("path-mismatch", "held sidecar release continuity changed repeatedly");
344
+ }
345
+ throw new FsSafeError("path-mismatch", "held sidecar release could not complete");
346
+ }
347
+ export function createRootSyncHeldLockHandle(held) {
348
+ const disposition = { held, released: false };
349
+ let releaseInProgress = false;
350
+ const release = () => {
351
+ if (disposition.released || releaseInProgress)
352
+ return;
353
+ releaseInProgress = true;
354
+ try {
355
+ if (releaseRootSyncHeldLock(held, disposition))
356
+ disposition.released = true;
357
+ }
358
+ finally {
359
+ releaseInProgress = false;
360
+ }
361
+ };
362
+ const handle = {
363
+ lockPath: held.lockPath,
364
+ normalizedTargetPath: held.normalizedTargetPath,
365
+ verifyStillHeld: () => verifyRootSyncHeldLock(held),
366
+ release,
367
+ [Symbol.dispose]: release,
368
+ };
369
+ rootSyncHandleDispositions.set(handle, disposition);
370
+ return handle;
371
+ }
372
+ export function isRootSyncHeldLockHandle(handle) {
373
+ return rootSyncHandleDispositions.has(handle);
374
+ }
375
+ export function withRootSyncHeldLockHandle(lock, fn) {
376
+ let result;
377
+ try {
378
+ result = fn();
379
+ }
380
+ catch (callbackError) {
381
+ try {
382
+ lock.release();
383
+ settleDeferredExitRelease(lock);
384
+ }
385
+ catch (releaseError) {
386
+ throw createSuppressedError(releaseError, callbackError, "file lock release and protected callback both failed");
387
+ }
388
+ throw callbackError;
389
+ }
390
+ lock.release();
391
+ settleDeferredExitRelease(lock);
392
+ return result;
393
+ }
@@ -0,0 +1,44 @@
1
+ import { type BigIntStats, type Stats } from "node:fs";
2
+ import type { RootDefaults } from "./root-options.js";
3
+ import { type FileLockSyncRootPath } from "./file-lock-sync-root.js";
4
+ export type ExactIdentity = Readonly<{
5
+ dev: bigint;
6
+ ino: bigint;
7
+ }>;
8
+ export type DirectoryReceipt = Readonly<{
9
+ identity: ExactIdentity;
10
+ path: string;
11
+ realPath: string;
12
+ }>;
13
+ export type FileLockSyncRootFileReceipt = Readonly<{
14
+ identity: ExactIdentity;
15
+ parent: DirectoryReceipt;
16
+ }>;
17
+ export type FileLockSyncRootDirectoryReceipt = Readonly<{
18
+ identity: ExactIdentity;
19
+ parent: DirectoryReceipt;
20
+ }>;
21
+ export type FileLockSyncRootDiskSnapshot = {
22
+ ownershipToken?: never;
23
+ payload: unknown;
24
+ raw: string;
25
+ stat: Stats;
26
+ };
27
+ export type FileLockSyncRootSnapshot = Readonly<{
28
+ receipt: FileLockSyncRootFileReceipt;
29
+ snapshot: FileLockSyncRootDiskSnapshot;
30
+ }>;
31
+ export declare function observeDirectory(pathname: string, initial?: BigIntStats): DirectoryReceipt;
32
+ export declare function assertDirectoryCurrent(receipt: DirectoryReceipt, initial?: BigIntStats): void;
33
+ export declare function assertRetainedParentCurrent(pathAuthority: FileLockSyncRootPath, parent: DirectoryReceipt): void;
34
+ export declare function exactFileIdentity(stat: BigIntStats): ExactIdentity;
35
+ export declare function assertRegularFile(stat: BigIntStats, hardlinks: RootDefaults["hardlinks"]): void;
36
+ export declare function sameExactIdentity(left: ExactIdentity, right: ExactIdentity): boolean;
37
+ export declare function readFileLockSyncRootSnapshot(pathAuthority: FileLockSyncRootPath, options?: {
38
+ expectedReceipt?: FileLockSyncRootFileReceipt;
39
+ onOpenFailure?: (error: unknown) => void;
40
+ }): FileLockSyncRootSnapshot | null;
41
+ export declare function fileLockSyncRootSnapshotStillCurrent(pathAuthority: FileLockSyncRootPath, observed: FileLockSyncRootSnapshot): boolean;
42
+ export declare function fileReceiptCurrentAfterParentCheck(pathAuthority: FileLockSyncRootPath, receipt: FileLockSyncRootFileReceipt): boolean;
43
+ export declare function fileLockSyncRootReceiptStillCurrent(pathAuthority: FileLockSyncRootPath, receipt: FileLockSyncRootFileReceipt): boolean;
44
+ //# sourceMappingURL=file-lock-sync-root-io.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-lock-sync-root-io.d.ts","sourceRoot":"","sources":["../src/file-lock-sync-root-io.ts"],"names":[],"mappings":"AAAA,OAAW,EAAE,KAAK,WAAW,EAAE,KAAK,KAAK,EAAE,MAAM,SAAS,CAAC;AAQ3D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAQtD,OAAO,EAEL,KAAK,oBAAoB,EAC1B,MAAM,0BAA0B,CAAC;AAKlC,MAAM,MAAM,aAAa,GAAG,QAAQ,CAAC;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAEnE,MAAM,MAAM,gBAAgB,GAAG,QAAQ,CAAC;IACtC,QAAQ,EAAE,aAAa,CAAC;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC,CAAC;AAEH,MAAM,MAAM,2BAA2B,GAAG,QAAQ,CAAC;IACjD,QAAQ,EAAE,aAAa,CAAC;IACxB,MAAM,EAAE,gBAAgB,CAAC;CAC1B,CAAC,CAAC;AAEH,MAAM,MAAM,gCAAgC,GAAG,QAAQ,CAAC;IACtD,QAAQ,EAAE,aAAa,CAAC;IACxB,MAAM,EAAE,gBAAgB,CAAC;CAC1B,CAAC,CAAC;AAEH,MAAM,MAAM,4BAA4B,GAAG;IACzC,cAAc,CAAC,EAAE,KAAK,CAAC;IACvB,OAAO,EAAE,OAAO,CAAC;IACjB,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,KAAK,CAAC;CACb,CAAC;AAEF,MAAM,MAAM,wBAAwB,GAAG,QAAQ,CAAC;IAC9C,OAAO,EAAE,2BAA2B,CAAC;IACrC,QAAQ,EAAE,4BAA4B,CAAC;CACxC,CAAC,CAAC;AAKH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,WAAW,GAAG,gBAAgB,CAY1F;AAED,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,gBAAgB,EAAE,OAAO,CAAC,EAAE,WAAW,GAAG,IAAI,CAO7F;AAED,wBAAgB,2BAA2B,CACzC,aAAa,EAAE,oBAAoB,EACnC,MAAM,EAAE,gBAAgB,GACvB,IAAI,CAQN;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,WAAW,GAAG,aAAa,CAElE;AAED,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,WAAW,EACjB,SAAS,EAAE,YAAY,CAAC,WAAW,CAAC,GACnC,IAAI,CAKN;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,aAAa,EAAE,KAAK,EAAE,aAAa,GAAG,OAAO,CAEpF;AAqBD,wBAAgB,4BAA4B,CAC1C,aAAa,EAAE,oBAAoB,EACnC,OAAO,GAAE;IACP,eAAe,CAAC,EAAE,2BAA2B,CAAC;IAC9C,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;CACrC,GACL,wBAAwB,GAAG,IAAI,CA4GjC;AAED,wBAAgB,oCAAoC,CAClD,aAAa,EAAE,oBAAoB,EACnC,QAAQ,EAAE,wBAAwB,GACjC,OAAO,CAOT;AAED,wBAAgB,kCAAkC,CAChD,aAAa,EAAE,oBAAoB,EACnC,OAAO,EAAE,2BAA2B,GACnC,OAAO,CAeT;AAED,wBAAgB,mCAAmC,CACjD,aAAa,EAAE,oBAAoB,EACnC,OAAO,EAAE,2BAA2B,GACnC,OAAO,CAGT"}