@openclaw/fs-safe 0.6.0 → 0.7.1

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 (202) hide show
  1. package/CHANGELOG.md +79 -0
  2. package/README.md +7 -0
  3. package/dist/archive-deadline.d.ts +3 -0
  4. package/dist/archive-deadline.d.ts.map +1 -1
  5. package/dist/archive-deadline.js +44 -8
  6. package/dist/archive-entry.d.ts.map +1 -1
  7. package/dist/archive-entry.js +1 -0
  8. package/dist/archive-errors.d.ts +1 -0
  9. package/dist/archive-errors.d.ts.map +1 -1
  10. package/dist/archive-errors.js +3 -0
  11. package/dist/archive-input.d.ts.map +1 -1
  12. package/dist/archive-input.js +26 -16
  13. package/dist/archive-limits.d.ts +11 -3
  14. package/dist/archive-limits.d.ts.map +1 -1
  15. package/dist/archive-limits.js +24 -0
  16. package/dist/archive-native.d.ts +3 -2
  17. package/dist/archive-native.d.ts.map +1 -1
  18. package/dist/archive-native.js +24 -9
  19. package/dist/archive-policy.d.ts +2 -0
  20. package/dist/archive-policy.d.ts.map +1 -1
  21. package/dist/archive-policy.js +9 -1
  22. package/dist/archive-read.d.ts.map +1 -1
  23. package/dist/archive-read.js +69 -41
  24. package/dist/archive-staging.d.ts +3 -0
  25. package/dist/archive-staging.d.ts.map +1 -1
  26. package/dist/archive-staging.js +91 -43
  27. package/dist/archive-tar-admission.d.ts +7 -0
  28. package/dist/archive-tar-admission.d.ts.map +1 -0
  29. package/dist/archive-tar-admission.js +43 -0
  30. package/dist/archive-tar-gnu.d.ts +2 -0
  31. package/dist/archive-tar-gnu.d.ts.map +1 -0
  32. package/dist/archive-tar-gnu.js +20 -0
  33. package/dist/archive-tar-header.d.ts +8 -0
  34. package/dist/archive-tar-header.d.ts.map +1 -0
  35. package/dist/archive-tar-header.js +47 -0
  36. package/dist/archive-tar-meta.d.ts +29 -1
  37. package/dist/archive-tar-meta.d.ts.map +1 -1
  38. package/dist/archive-tar-meta.js +104 -35
  39. package/dist/archive-tar-runtime.d.ts +3 -0
  40. package/dist/archive-tar-runtime.d.ts.map +1 -1
  41. package/dist/archive-tar-runtime.js +3 -0
  42. package/dist/archive-tar.d.ts.map +1 -1
  43. package/dist/archive-tar.js +8 -2
  44. package/dist/archive-zip-names.js +2 -2
  45. package/dist/archive.d.ts.map +1 -1
  46. package/dist/archive.js +38 -27
  47. package/dist/atomic.d.ts +1 -1
  48. package/dist/atomic.d.ts.map +1 -1
  49. package/dist/bounded-read-stream.d.ts.map +1 -1
  50. package/dist/bounded-read-stream.js +2 -4
  51. package/dist/bounded-read.d.ts.map +1 -1
  52. package/dist/bounded-read.js +3 -10
  53. package/dist/byte-budget.d.ts +5 -0
  54. package/dist/byte-budget.d.ts.map +1 -0
  55. package/dist/byte-budget.js +9 -0
  56. package/dist/directory-guard.d.ts +2 -1
  57. package/dist/directory-guard.d.ts.map +1 -1
  58. package/dist/directory-guard.js +10 -0
  59. package/dist/file-lock-sync.d.ts.map +1 -1
  60. package/dist/file-lock-sync.js +81 -30
  61. package/dist/file-observation.d.ts +9 -0
  62. package/dist/file-observation.d.ts.map +1 -0
  63. package/dist/file-observation.js +22 -0
  64. package/dist/file-store-boundary.d.ts.map +1 -1
  65. package/dist/file-store-boundary.js +9 -5
  66. package/dist/file-store-limit.d.ts +2 -0
  67. package/dist/file-store-limit.d.ts.map +1 -0
  68. package/dist/file-store-limit.js +8 -0
  69. package/dist/file-store-sync-write.d.ts.map +1 -1
  70. package/dist/file-store-sync-write.js +38 -8
  71. package/dist/file-store.d.ts.map +1 -1
  72. package/dist/file-store.js +26 -26
  73. package/dist/json-durable-queue-directory.d.ts +2 -0
  74. package/dist/json-durable-queue-directory.d.ts.map +1 -0
  75. package/dist/json-durable-queue-directory.js +20 -0
  76. package/dist/json-durable-queue-ownership.d.ts +14 -0
  77. package/dist/json-durable-queue-ownership.d.ts.map +1 -0
  78. package/dist/json-durable-queue-ownership.js +168 -0
  79. package/dist/json-durable-queue-retirement.d.ts +9 -0
  80. package/dist/json-durable-queue-retirement.d.ts.map +1 -0
  81. package/dist/json-durable-queue-retirement.js +126 -0
  82. package/dist/json-durable-queue-transfer-lock.d.ts +2 -0
  83. package/dist/json-durable-queue-transfer-lock.d.ts.map +1 -0
  84. package/dist/json-durable-queue-transfer-lock.js +19 -0
  85. package/dist/json-durable-queue.d.ts +1 -0
  86. package/dist/json-durable-queue.d.ts.map +1 -1
  87. package/dist/json-durable-queue.js +90 -57
  88. package/dist/json.d.ts.map +1 -1
  89. package/dist/json.js +32 -13
  90. package/dist/local-roots.d.ts.map +1 -1
  91. package/dist/local-roots.js +4 -2
  92. package/dist/native-binding.d.ts +12 -3
  93. package/dist/native-binding.d.ts.map +1 -1
  94. package/dist/native-pinned-write-windows.js +1 -1
  95. package/dist/opened-file-failure.d.ts +7 -0
  96. package/dist/opened-file-failure.d.ts.map +1 -0
  97. package/dist/opened-file-failure.js +41 -0
  98. package/dist/opened-realpath.d.ts.map +1 -1
  99. package/dist/opened-realpath.js +8 -2
  100. package/dist/output-sibling.d.ts.map +1 -1
  101. package/dist/output-sibling.js +11 -110
  102. package/dist/output.d.ts.map +1 -1
  103. package/dist/output.js +4 -2
  104. package/dist/pinned-open.d.ts.map +1 -1
  105. package/dist/pinned-open.js +27 -48
  106. package/dist/pinned-write.d.ts.map +1 -1
  107. package/dist/pinned-write.js +10 -10
  108. package/dist/private-temp-workspace.d.ts +3 -1
  109. package/dist/private-temp-workspace.d.ts.map +1 -1
  110. package/dist/private-temp-workspace.js +81 -56
  111. package/dist/publish-file.d.ts.map +1 -1
  112. package/dist/publish-file.js +2 -4
  113. package/dist/read-opened-file.d.ts.map +1 -1
  114. package/dist/read-opened-file.js +6 -4
  115. package/dist/regular-file.d.ts.map +1 -1
  116. package/dist/regular-file.js +136 -82
  117. package/dist/replace-file-copy-fallback.d.ts +3 -1
  118. package/dist/replace-file-copy-fallback.d.ts.map +1 -1
  119. package/dist/replace-file-copy-fallback.js +21 -36
  120. package/dist/replace-file-copy-source.d.ts +21 -0
  121. package/dist/replace-file-copy-source.d.ts.map +1 -0
  122. package/dist/replace-file-copy-source.js +112 -0
  123. package/dist/replace-file-descriptor.d.ts +13 -3
  124. package/dist/replace-file-descriptor.d.ts.map +1 -1
  125. package/dist/replace-file-descriptor.js +32 -7
  126. package/dist/replace-file-rename-policy.d.ts +7 -0
  127. package/dist/replace-file-rename-policy.d.ts.map +1 -0
  128. package/dist/replace-file-rename-policy.js +30 -0
  129. package/dist/replace-file-temp-owner.d.ts +46 -0
  130. package/dist/replace-file-temp-owner.d.ts.map +1 -0
  131. package/dist/replace-file-temp-owner.js +346 -0
  132. package/dist/replace-file.d.ts +6 -1
  133. package/dist/replace-file.d.ts.map +1 -1
  134. package/dist/replace-file.js +72 -58
  135. package/dist/root-impl.d.ts.map +1 -1
  136. package/dist/root-impl.js +113 -82
  137. package/dist/secret-file.d.ts.map +1 -1
  138. package/dist/secret-file.js +6 -3
  139. package/dist/secret-read-async.d.ts.map +1 -1
  140. package/dist/secret-read-async.js +6 -5
  141. package/dist/secure-file.d.ts.map +1 -1
  142. package/dist/secure-file.js +17 -13
  143. package/dist/sibling-staged-file.d.ts +15 -0
  144. package/dist/sibling-staged-file.d.ts.map +1 -0
  145. package/dist/sibling-staged-file.js +148 -0
  146. package/dist/sibling-temp.d.ts +3 -0
  147. package/dist/sibling-temp.d.ts.map +1 -1
  148. package/dist/sibling-temp.js +30 -74
  149. package/dist/sidecar-lock-acquire.d.ts.map +1 -1
  150. package/dist/sidecar-lock-acquire.js +91 -37
  151. package/dist/sidecar-lock-handle.d.ts +6 -2
  152. package/dist/sidecar-lock-handle.d.ts.map +1 -1
  153. package/dist/sidecar-lock-handle.js +17 -3
  154. package/dist/sidecar-lock-policy.d.ts +2 -0
  155. package/dist/sidecar-lock-policy.d.ts.map +1 -1
  156. package/dist/sidecar-lock-policy.js +29 -0
  157. package/dist/sidecar-lock-reclaim.d.ts +16 -5
  158. package/dist/sidecar-lock-reclaim.d.ts.map +1 -1
  159. package/dist/sidecar-lock-reclaim.js +60 -28
  160. package/dist/sidecar-lock-root.d.ts +3 -0
  161. package/dist/sidecar-lock-root.d.ts.map +1 -0
  162. package/dist/sidecar-lock-root.js +76 -0
  163. package/dist/sidecar-lock.d.ts.map +1 -1
  164. package/dist/sidecar-lock.js +28 -16
  165. package/dist/strict-file-identity.d.ts.map +1 -1
  166. package/dist/strict-file-identity.js +4 -1
  167. package/dist/suppressed-error.d.ts +6 -0
  168. package/dist/suppressed-error.d.ts.map +1 -0
  169. package/dist/suppressed-error.js +15 -0
  170. package/dist/temp-cleanup.d.ts +2 -0
  171. package/dist/temp-cleanup.d.ts.map +1 -1
  172. package/dist/temp-cleanup.js +25 -10
  173. package/dist/temp-workspace-owner.d.ts +23 -0
  174. package/dist/temp-workspace-owner.d.ts.map +1 -0
  175. package/dist/temp-workspace-owner.js +320 -0
  176. package/dist/temp.d.ts +1 -1
  177. package/dist/temp.d.ts.map +1 -1
  178. package/dist/test-hooks.d.ts +5 -0
  179. package/dist/test-hooks.d.ts.map +1 -1
  180. package/docs/advanced.md +8 -1
  181. package/docs/archive.md +202 -26
  182. package/docs/atomic.md +11 -2
  183. package/docs/durability.md +1 -1
  184. package/docs/file-store.md +2 -0
  185. package/docs/install.md +8 -4
  186. package/docs/json.md +11 -4
  187. package/docs/native-helper.md +15 -3
  188. package/docs/native.md +33 -6
  189. package/docs/output.md +16 -3
  190. package/docs/public-api.md +6 -3
  191. package/docs/quickstart.md +1 -1
  192. package/docs/reading.md +1 -1
  193. package/docs/regular-file.md +9 -2
  194. package/docs/root.md +2 -0
  195. package/docs/secret-file.md +5 -1
  196. package/docs/secure-file.md +4 -2
  197. package/docs/security-model.md +18 -0
  198. package/docs/sidecar-lock.md +60 -4
  199. package/docs/store.md +13 -0
  200. package/docs/temp.md +128 -14
  201. package/docs/writing.md +6 -0
  202. package/package.json +10 -10
@@ -1,8 +1,10 @@
1
1
  import syncFs from "node:fs";
2
2
  import fs from "node:fs/promises";
3
3
  import path from "node:path";
4
+ import { normalizeMaxBytes } from "./byte-budget.js";
4
5
  import { readFileDescriptorBoundedSync } from "./bounded-read.js";
5
6
  import { FsSafeError } from "./errors.js";
7
+ import { assertFileStoreMaxBytes } from "./file-store-limit.js";
6
8
  import { pruneExpiredStoreEntries } from "./file-store-prune.js";
7
9
  import { ensureParentInRoot, openWritableStoreRoot, writeStreamToTempSource, } from "./file-store-boundary.js";
8
10
  import { writeFileSyncAtomic } from "./file-store-sync-write.js";
@@ -38,15 +40,8 @@ function assertRelativePath(relativePath) {
38
40
  function resolveStorePath(rootDir, relativePath) {
39
41
  return resolveSafeRelativePath(rootDir, assertRelativePath(relativePath));
40
42
  }
41
- function assertMaxBytes(size, maxBytes) {
42
- if (maxBytes !== undefined && size > maxBytes) {
43
- throw new FsSafeError("too-large", `file exceeds maximum size of ${maxBytes} bytes`);
44
- }
45
- }
46
43
  function isNotFound(error) {
47
- return error instanceof FsSafeError
48
- ? error.code === "not-found"
49
- : isNotFoundPathError(error);
44
+ return error instanceof FsSafeError ? error.code === "not-found" : isNotFoundPathError(error);
50
45
  }
51
46
  function handleSyncStoreReadOpenFailure(opened) {
52
47
  return matchRootFileOpenFailure(opened, {
@@ -81,7 +76,7 @@ async function readFileStoreCopySource(params) {
81
76
  if (sourceStat.isSymbolicLink() || !sourceStat.isFile()) {
82
77
  throw new FsSafeError("not-file", "source path is not a file");
83
78
  }
84
- assertMaxBytes(sourceStat.size, params.maxBytes);
79
+ assertFileStoreMaxBytes(sourceStat.size, params.maxBytes);
85
80
  try {
86
81
  return (await readRegularFile({ filePath: params.sourcePath, maxBytes: params.maxBytes }))
87
82
  .buffer;
@@ -108,7 +103,7 @@ async function copyIntoRoot(params) {
108
103
  if (sourceStat.isSymbolicLink() || !sourceStat.isFile()) {
109
104
  throw new FsSafeError("not-file", "source path is not a file");
110
105
  }
111
- assertMaxBytes(sourceStat.size, params.maxBytes);
106
+ assertFileStoreMaxBytes(sourceStat.size, params.maxBytes);
112
107
  const dirMode = params.dirMode ?? 0o700;
113
108
  const scopedRoot = await openWritableStoreRoot({
114
109
  rootDir: params.rootDir,
@@ -128,7 +123,7 @@ export function fileStore(options) {
128
123
  const privateMode = options.private ?? false;
129
124
  const dirMode = options.dirMode ?? 0o700;
130
125
  const mode = options.mode ?? 0o600;
131
- const maxBytes = options.maxBytes;
126
+ const maxBytes = normalizeMaxBytes(options.maxBytes);
132
127
  async function openRoot() {
133
128
  return await root(rootDir, { hardlinks: "reject", maxBytes });
134
129
  }
@@ -136,7 +131,8 @@ export function fileStore(options) {
136
131
  const safeRelativePath = assertRelativePath(relativePath);
137
132
  const destination = resolveStorePath(rootDir, safeRelativePath);
138
133
  const content = Buffer.isBuffer(data) ? data : Buffer.from(data);
139
- assertMaxBytes(content.byteLength, writeOptions?.maxBytes ?? maxBytes);
134
+ const writeMaxBytes = normalizeMaxBytes(writeOptions?.maxBytes, { defaultValue: maxBytes });
135
+ assertFileStoreMaxBytes(content.byteLength, writeMaxBytes);
140
136
  if (privateMode) {
141
137
  await writeSecretFileAtomic({
142
138
  rootDir,
@@ -151,7 +147,7 @@ export function fileStore(options) {
151
147
  const scopedRoot = await openWritableStoreRoot({
152
148
  rootDir,
153
149
  dirMode: writeDirMode,
154
- maxBytes: writeOptions?.maxBytes ?? maxBytes,
150
+ maxBytes: writeMaxBytes,
155
151
  });
156
152
  await ensureParentInRoot(scopedRoot, safeRelativePath, writeDirMode);
157
153
  await scopedRoot.write(safeRelativePath, content, {
@@ -168,14 +164,15 @@ export function fileStore(options) {
168
164
  writeStream: async (relativePath, stream, writeOptions) => {
169
165
  const safeRelativePath = assertRelativePath(relativePath);
170
166
  const destination = resolveStorePath(rootDir, safeRelativePath);
171
- const limit = writeOptions?.maxBytes ?? maxBytes ?? (privateMode ? DEFAULT_ROOT_MAX_BYTES : undefined);
167
+ const configuredLimit = normalizeMaxBytes(writeOptions?.maxBytes, { defaultValue: maxBytes });
168
+ const limit = configuredLimit ?? (privateMode ? DEFAULT_ROOT_MAX_BYTES : undefined);
172
169
  if (privateMode) {
173
170
  const chunks = [];
174
171
  let total = 0;
175
172
  for await (const chunk of stream) {
176
173
  const buffer = typeof chunk === "string" ? Buffer.from(chunk) : Buffer.from(chunk);
177
174
  total += buffer.byteLength;
178
- assertMaxBytes(total, limit);
175
+ assertFileStoreMaxBytes(total, limit);
179
176
  chunks.push(buffer);
180
177
  }
181
178
  await writeSecretFileAtomic({
@@ -208,23 +205,25 @@ export function fileStore(options) {
208
205
  }
209
206
  return destination;
210
207
  },
211
- copyIn: async (relativePath, sourcePath, writeOptions) => privateMode
212
- ? await (async () => {
208
+ copyIn: async (relativePath, sourcePath, writeOptions) => {
209
+ const configuredLimit = normalizeMaxBytes(writeOptions?.maxBytes, { defaultValue: maxBytes });
210
+ if (privateMode) {
213
211
  const buffer = await readFileStoreCopySource({
214
212
  sourcePath,
215
- maxBytes: writeOptions?.maxBytes ?? maxBytes ?? DEFAULT_ROOT_MAX_BYTES,
213
+ maxBytes: configuredLimit ?? DEFAULT_ROOT_MAX_BYTES,
216
214
  });
217
215
  return await write(relativePath, buffer, writeOptions);
218
- })()
219
- : await copyIntoRoot({
216
+ }
217
+ return await copyIntoRoot({
220
218
  rootDir,
221
219
  relativePath,
222
220
  sourcePath,
223
221
  dirMode: writeOptions?.dirMode ?? dirMode,
224
- maxBytes: writeOptions?.maxBytes ?? maxBytes,
222
+ maxBytes: configuredLimit,
225
223
  mode: writeOptions?.mode ?? mode,
226
224
  tempPrefix: writeOptions?.tempPrefix,
227
- }),
225
+ });
226
+ },
228
227
  open: async (relativePath, readOptions) => await (await openRoot()).open(assertRelativePath(relativePath), readOptions),
229
228
  read: async (relativePath, readOptions) => await (await openRoot()).read(assertRelativePath(relativePath), readOptions),
230
229
  readBytes: async (relativePath, readOptions) => await (await openRoot()).readBytes(assertRelativePath(relativePath), readOptions),
@@ -301,11 +300,12 @@ export function fileStoreSync(options) {
301
300
  const privateMode = options.private ?? false;
302
301
  const dirMode = options.dirMode ?? 0o700;
303
302
  const mode = options.mode ?? 0o600;
304
- const maxBytes = options.maxBytes;
303
+ const maxBytes = normalizeMaxBytes(options.maxBytes);
305
304
  function write(relativePath, data, writeOptions) {
306
305
  const destination = resolveStorePath(rootDir, relativePath);
307
306
  const content = Buffer.isBuffer(data) ? data : Buffer.from(data);
308
- assertMaxBytes(content.byteLength, writeOptions?.maxBytes ?? maxBytes);
307
+ const writeMaxBytes = normalizeMaxBytes(writeOptions?.maxBytes, { defaultValue: maxBytes });
308
+ assertFileStoreMaxBytes(content.byteLength, writeMaxBytes);
309
309
  return writeFileSyncAtomic({
310
310
  rootDir,
311
311
  filePath: destination,
@@ -319,6 +319,7 @@ export function fileStoreSync(options) {
319
319
  rootDir,
320
320
  path: (relativePath) => resolveStorePath(rootDir, relativePath),
321
321
  readTextIfExists: (relativePath, readOptions) => {
322
+ const limit = normalizeMaxBytes(readOptions?.maxBytes, { defaultValue: maxBytes });
322
323
  const targetPath = resolveStorePath(rootDir, relativePath);
323
324
  const opened = openRootFileSync({
324
325
  absolutePath: targetPath,
@@ -330,8 +331,7 @@ export function fileStoreSync(options) {
330
331
  return handleSyncStoreReadOpenFailure(opened);
331
332
  }
332
333
  try {
333
- assertMaxBytes(opened.stat.size, readOptions?.maxBytes ?? maxBytes);
334
- const limit = readOptions?.maxBytes ?? maxBytes;
334
+ assertFileStoreMaxBytes(opened.stat.size, limit);
335
335
  try {
336
336
  return limit === undefined
337
337
  ? syncFs.readFileSync(opened.fd, "utf8")
@@ -0,0 +1,2 @@
1
+ export declare function syncQueueDirectoryCreation(dir: string, validationBase: string): Promise<void>;
2
+ //# sourceMappingURL=json-durable-queue-directory.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"json-durable-queue-directory.d.ts","sourceRoot":"","sources":["../src/json-durable-queue-directory.ts"],"names":[],"mappings":"AAKA,wBAAsB,0BAA0B,CAC9C,GAAG,EAAE,MAAM,EACX,cAAc,EAAE,MAAM,GACrB,OAAO,CAAC,IAAI,CAAC,CAiBf"}
@@ -0,0 +1,20 @@
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { syncDirectory } from "./directory-durability.js";
4
+ import { isPathRelativeEscape } from "./path.js";
5
+ export async function syncQueueDirectoryCreation(dir, validationBase) {
6
+ const baseReal = await fs.realpath(validationBase);
7
+ const targetReal = await fs.realpath(dir);
8
+ const relative = path.relative(baseReal, targetReal);
9
+ if (isPathRelativeEscape(relative)) {
10
+ throw new Error(`durable queue directory escapes validation base: ${dir}`);
11
+ }
12
+ const directories = [baseReal];
13
+ for (const segment of relative.split(path.sep).filter(Boolean)) {
14
+ directories.push(path.join(directories.at(-1), segment));
15
+ }
16
+ await syncDirectory(targetReal, { label: "durable queue directory" });
17
+ for (const parent of directories.slice(0, -1).toReversed()) {
18
+ await syncDirectory(parent, { label: "durable queue parent" });
19
+ }
20
+ }
@@ -0,0 +1,14 @@
1
+ export type DurableQueueEntryPathsLike = {
2
+ jsonPath: string;
3
+ deliveredPath: string;
4
+ processingPath?: string;
5
+ };
6
+ export declare function durableQueueProcessingPath(paths: DurableQueueEntryPathsLike): string;
7
+ export declare function claimDurableQueueEntry(paths: DurableQueueEntryPathsLike): Promise<string | null>;
8
+ export declare function completeDeliveredQueueEntry(paths: DurableQueueEntryPathsLike): Promise<boolean>;
9
+ export declare function acknowledgeDurableQueueEntry(paths: DurableQueueEntryPathsLike): Promise<void>;
10
+ export declare function moveDurableQueueEntryToFailed(params: {
11
+ paths: DurableQueueEntryPathsLike;
12
+ failedPath: string;
13
+ }): Promise<void>;
14
+ //# sourceMappingURL=json-durable-queue-ownership.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"json-durable-queue-ownership.d.ts","sourceRoot":"","sources":["../src/json-durable-queue-ownership.ts"],"names":[],"mappings":"AAaA,MAAM,MAAM,0BAA0B,GAAG;IACvC,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB,CAAC;AAQF,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,0BAA0B,GAAG,MAAM,CAKpF;AAqED,wBAAsB,sBAAsB,CAC1C,KAAK,EAAE,0BAA0B,GAChC,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAGxB;AAED,wBAAsB,2BAA2B,CAC/C,KAAK,EAAE,0BAA0B,GAChC,OAAO,CAAC,OAAO,CAAC,CAalB;AAED,wBAAsB,4BAA4B,CAChD,KAAK,EAAE,0BAA0B,GAChC,OAAO,CAAC,IAAI,CAAC,CAyBf;AAED,wBAAsB,6BAA6B,CAAC,MAAM,EAAE;IAC1D,KAAK,EAAE,0BAA0B,CAAC;IAClC,UAAU,EAAE,MAAM,CAAC;CACpB,GAAG,OAAO,CAAC,IAAI,CAAC,CAiDhB"}
@@ -0,0 +1,168 @@
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { syncDirectory } from "./directory-durability.js";
4
+ import { FsSafeError } from "./errors.js";
5
+ import { sameFileIdentityForCleanup } from "./file-identity.js";
6
+ import { recoverDurableQueueRetirement, retireDurableQueueSource, } from "./json-durable-queue-retirement.js";
7
+ import { withQueueTransferLock } from "./json-durable-queue-transfer-lock.js";
8
+ import { serializePathWrite } from "./write-queue.js";
9
+ function getErrorCode(error) {
10
+ return error && typeof error === "object" && "code" in error
11
+ ? String(error.code)
12
+ : null;
13
+ }
14
+ export function durableQueueProcessingPath(paths) {
15
+ if (paths.processingPath)
16
+ return paths.processingPath;
17
+ return paths.jsonPath.endsWith(".json")
18
+ ? `${paths.jsonPath.slice(0, -".json".length)}.processing`
19
+ : `${paths.jsonPath}.processing`;
20
+ }
21
+ async function lstatOrNull(filePath) {
22
+ try {
23
+ return await fs.lstat(filePath, { bigint: true });
24
+ }
25
+ catch (error) {
26
+ if (getErrorCode(error) === "ENOENT")
27
+ return null;
28
+ throw error;
29
+ }
30
+ }
31
+ async function regularQueueFileIdentity(filePath) {
32
+ const identity = await lstatOrNull(filePath);
33
+ if (!identity)
34
+ return null;
35
+ if (identity.isSymbolicLink() || !identity.isFile()) {
36
+ throw new Error(`queue entry is not a regular file: ${filePath}`);
37
+ }
38
+ return identity;
39
+ }
40
+ async function withQueueEntryLock(paths, run) {
41
+ return await serializePathWrite(paths.jsonPath, async () => await withQueueTransferLock(path.resolve(paths.jsonPath), run));
42
+ }
43
+ async function claimDurableQueueEntryUnlocked(paths) {
44
+ const processingPath = durableQueueProcessingPath(paths);
45
+ await recoverDurableQueueRetirement({ jsonPath: paths.jsonPath, processingPath });
46
+ const existingProcessing = await regularQueueFileIdentity(processingPath);
47
+ if (existingProcessing) {
48
+ const pending = await lstatOrNull(paths.jsonPath);
49
+ if (pending &&
50
+ !pending.isSymbolicLink() &&
51
+ pending.isFile() &&
52
+ sameFileIdentityForCleanup(pending, existingProcessing)) {
53
+ await retireDurableQueueSource({ jsonPath: paths.jsonPath, processingPath });
54
+ }
55
+ return processingPath;
56
+ }
57
+ const pending = await lstatOrNull(paths.jsonPath);
58
+ if (!pending || pending.isSymbolicLink() || !pending.isFile())
59
+ return null;
60
+ if (pending.nlink > 1n || !sameFileIdentityForCleanup(pending, pending)) {
61
+ throw new FsSafeError("path-mismatch", "queue entry is not exclusively owned");
62
+ }
63
+ try {
64
+ await fs.link(paths.jsonPath, processingPath);
65
+ }
66
+ catch (error) {
67
+ if (getErrorCode(error) === "EEXIST") {
68
+ return (await regularQueueFileIdentity(processingPath)) ? processingPath : null;
69
+ }
70
+ if (getErrorCode(error) === "ENOENT")
71
+ return null;
72
+ throw error;
73
+ }
74
+ await syncDirectory(path.dirname(processingPath));
75
+ const claimed = await regularQueueFileIdentity(processingPath);
76
+ if (!claimed || claimed.nlink > 2n) {
77
+ throw new FsSafeError("path-mismatch", "queue claim is not an owned regular file");
78
+ }
79
+ await retireDurableQueueSource({ jsonPath: paths.jsonPath, processingPath });
80
+ return processingPath;
81
+ }
82
+ export async function claimDurableQueueEntry(paths) {
83
+ if (!(await lstatOrNull(path.dirname(paths.jsonPath))))
84
+ return null;
85
+ return await withQueueEntryLock(paths, async () => await claimDurableQueueEntryUnlocked(paths));
86
+ }
87
+ export async function completeDeliveredQueueEntry(paths) {
88
+ if (!(await lstatOrNull(path.dirname(paths.deliveredPath))))
89
+ return false;
90
+ return await withQueueEntryLock(paths, async () => {
91
+ if (!(await regularQueueFileIdentity(paths.deliveredPath)))
92
+ return false;
93
+ if (await regularQueueFileIdentity(durableQueueProcessingPath(paths))) {
94
+ await fs.unlink(paths.deliveredPath);
95
+ await syncDirectory(path.dirname(paths.deliveredPath));
96
+ return false;
97
+ }
98
+ await fs.unlink(paths.deliveredPath);
99
+ await syncDirectory(path.dirname(paths.deliveredPath));
100
+ return true;
101
+ });
102
+ }
103
+ export async function acknowledgeDurableQueueEntry(paths) {
104
+ if (!(await lstatOrNull(path.dirname(paths.jsonPath))))
105
+ return;
106
+ await withQueueEntryLock(paths, async () => {
107
+ const processingPath = durableQueueProcessingPath(paths);
108
+ const processing = await regularQueueFileIdentity(processingPath);
109
+ const delivered = await regularQueueFileIdentity(paths.deliveredPath);
110
+ if (!processing) {
111
+ if (delivered) {
112
+ await fs.unlink(paths.deliveredPath);
113
+ await syncDirectory(path.dirname(paths.deliveredPath));
114
+ }
115
+ if (await regularQueueFileIdentity(paths.jsonPath)) {
116
+ throw new FsSafeError("path-mismatch", "queue acknowledgement requires a processing claim");
117
+ }
118
+ return;
119
+ }
120
+ if (delivered)
121
+ await fs.unlink(paths.deliveredPath);
122
+ await fs.rename(processingPath, paths.deliveredPath);
123
+ await syncDirectory(path.dirname(paths.deliveredPath));
124
+ await fs.unlink(paths.deliveredPath);
125
+ await syncDirectory(path.dirname(paths.deliveredPath));
126
+ });
127
+ }
128
+ export async function moveDurableQueueEntryToFailed(params) {
129
+ await withQueueEntryLock(params.paths, async () => {
130
+ const processingPath = durableQueueProcessingPath(params.paths);
131
+ const processing = await regularQueueFileIdentity(processingPath);
132
+ const existingFailed = await regularQueueFileIdentity(params.failedPath);
133
+ if (!processing && existingFailed) {
134
+ await syncDirectory(path.dirname(params.failedPath));
135
+ await syncDirectory(path.dirname(processingPath));
136
+ const recoveredFailed = await regularQueueFileIdentity(params.failedPath);
137
+ if (!recoveredFailed ||
138
+ !sameFileIdentityForCleanup(existingFailed, recoveredFailed)) {
139
+ throw new FsSafeError("path-mismatch", "failed queue destination changed during recovery");
140
+ }
141
+ if (await regularQueueFileIdentity(params.paths.jsonPath)) {
142
+ throw new FsSafeError("already-exists", "failed queue destination already exists");
143
+ }
144
+ return;
145
+ }
146
+ const sourcePath = processing
147
+ ? processingPath
148
+ : await claimDurableQueueEntryUnlocked(params.paths);
149
+ if (!sourcePath) {
150
+ throw Object.assign(new Error("queue entry does not exist"), { code: "ENOENT" });
151
+ }
152
+ const source = await regularQueueFileIdentity(sourcePath);
153
+ const failed = await regularQueueFileIdentity(params.failedPath);
154
+ if (failed) {
155
+ if (source && sameFileIdentityForCleanup(source, failed)) {
156
+ await syncDirectory(path.dirname(params.failedPath));
157
+ await fs.unlink(sourcePath);
158
+ await syncDirectory(path.dirname(sourcePath));
159
+ return;
160
+ }
161
+ throw new FsSafeError("already-exists", "failed queue destination already exists");
162
+ }
163
+ await fs.link(sourcePath, params.failedPath);
164
+ await syncDirectory(path.dirname(params.failedPath));
165
+ await fs.unlink(sourcePath);
166
+ await syncDirectory(path.dirname(sourcePath));
167
+ });
168
+ }
@@ -0,0 +1,9 @@
1
+ export declare function recoverDurableQueueRetirement(params: {
2
+ jsonPath: string;
3
+ processingPath: string;
4
+ }): Promise<void>;
5
+ export declare function retireDurableQueueSource(params: {
6
+ jsonPath: string;
7
+ processingPath: string;
8
+ }): Promise<void>;
9
+ //# sourceMappingURL=json-durable-queue-retirement.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"json-durable-queue-retirement.d.ts","sourceRoot":"","sources":["../src/json-durable-queue-retirement.ts"],"names":[],"mappings":"AAgFA,wBAAsB,6BAA6B,CAAC,MAAM,EAAE;IAC1D,QAAQ,EAAE,MAAM,CAAC;IACjB,cAAc,EAAE,MAAM,CAAC;CACxB,GAAG,OAAO,CAAC,IAAI,CAAC,CAmChB;AAED,wBAAsB,wBAAwB,CAAC,MAAM,EAAE;IACrD,QAAQ,EAAE,MAAM,CAAC;IACjB,cAAc,EAAE,MAAM,CAAC;CACxB,GAAG,OAAO,CAAC,IAAI,CAAC,CAgBhB"}
@@ -0,0 +1,126 @@
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { syncDirectory } from "./directory-durability.js";
4
+ import { FsSafeError } from "./errors.js";
5
+ import { sameFileIdentityForCleanup } from "./file-identity.js";
6
+ const RETIREMENT_ROOT_NAME = ".fs-safe-retirements";
7
+ const RETIREMENT_ENTRY_NAME = "entry";
8
+ function getErrorCode(error) {
9
+ return error && typeof error === "object" && "code" in error
10
+ ? String(error.code)
11
+ : null;
12
+ }
13
+ async function lstatOrNull(filePath) {
14
+ try {
15
+ return await fs.lstat(filePath, { bigint: true });
16
+ }
17
+ catch (error) {
18
+ if (getErrorCode(error) === "ENOENT")
19
+ return null;
20
+ throw error;
21
+ }
22
+ }
23
+ function assertDirectory(identity, label) {
24
+ if (identity.isSymbolicLink() || !identity.isDirectory()) {
25
+ throw new FsSafeError("path-mismatch", `${label} is not an owned directory`);
26
+ }
27
+ }
28
+ async function regularFileIdentity(filePath) {
29
+ const identity = await lstatOrNull(filePath);
30
+ if (!identity)
31
+ return null;
32
+ if (identity.isSymbolicLink() || !identity.isFile()) {
33
+ throw new FsSafeError("path-mismatch", "queue retirement path is not a regular file");
34
+ }
35
+ return identity;
36
+ }
37
+ function retirementPaths(jsonPath) {
38
+ const rootPath = path.join(path.dirname(jsonPath), RETIREMENT_ROOT_NAME);
39
+ const dirPath = path.join(rootPath, path.basename(jsonPath));
40
+ return { dirPath, entryPath: path.join(dirPath, RETIREMENT_ENTRY_NAME), rootPath };
41
+ }
42
+ async function inspectRetirementRoot(jsonPath) {
43
+ const rootPath = retirementPaths(jsonPath).rootPath;
44
+ const root = await lstatOrNull(rootPath);
45
+ if (!root)
46
+ return null;
47
+ assertDirectory(root, "queue retirement root");
48
+ return rootPath;
49
+ }
50
+ async function ensureRetirementRoot(jsonPath) {
51
+ const parentPath = path.dirname(jsonPath);
52
+ const rootPath = retirementPaths(jsonPath).rootPath;
53
+ await fs.mkdir(rootPath, { mode: 0o700 }).catch((error) => {
54
+ if (getErrorCode(error) !== "EEXIST")
55
+ throw error;
56
+ });
57
+ const root = await fs.lstat(rootPath, { bigint: true });
58
+ assertDirectory(root, "queue retirement root");
59
+ await syncDirectory(parentPath);
60
+ return rootPath;
61
+ }
62
+ async function removeRetirementRecord(params) {
63
+ await fs.rmdir(params.dirPath).catch((error) => {
64
+ if (getErrorCode(error) !== "ENOENT")
65
+ throw error;
66
+ });
67
+ await syncDirectory(params.rootPath);
68
+ }
69
+ export async function recoverDurableQueueRetirement(params) {
70
+ const rootPath = await inspectRetirementRoot(params.jsonPath);
71
+ if (!rootPath)
72
+ return;
73
+ const { dirPath, entryPath } = retirementPaths(params.jsonPath);
74
+ const dir = await lstatOrNull(dirPath);
75
+ if (!dir)
76
+ return;
77
+ assertDirectory(dir, "queue retirement record");
78
+ const names = await fs.readdir(dirPath);
79
+ if (names.length === 0) {
80
+ await removeRetirementRecord({ dirPath, rootPath });
81
+ return;
82
+ }
83
+ if (names.length !== 1 || names[0] !== RETIREMENT_ENTRY_NAME) {
84
+ throw new FsSafeError("path-mismatch", "queue retirement record is ambiguous");
85
+ }
86
+ const entry = await regularFileIdentity(entryPath);
87
+ const processing = await regularFileIdentity(params.processingPath);
88
+ if (!entry || !processing) {
89
+ throw new FsSafeError("path-mismatch", "queue retirement ownership is incomplete");
90
+ }
91
+ if (!sameFileIdentityForCleanup(entry, processing)) {
92
+ const pending = await regularFileIdentity(params.jsonPath);
93
+ if (!pending) {
94
+ await fs.link(entryPath, params.jsonPath);
95
+ await syncDirectory(path.dirname(params.jsonPath));
96
+ }
97
+ else if (sameFileIdentityForCleanup(pending, entry)) {
98
+ await syncDirectory(path.dirname(params.jsonPath));
99
+ }
100
+ else if (pending.nlink > 1n) {
101
+ throw new FsSafeError("path-mismatch", "queue replacement identity is ambiguous");
102
+ }
103
+ }
104
+ await fs.unlink(entryPath);
105
+ await syncDirectory(dirPath);
106
+ await removeRetirementRecord({ dirPath, rootPath });
107
+ }
108
+ export async function retireDurableQueueSource(params) {
109
+ await recoverDurableQueueRetirement(params);
110
+ const rootPath = await ensureRetirementRoot(params.jsonPath);
111
+ const { dirPath, entryPath } = retirementPaths(params.jsonPath);
112
+ await fs.mkdir(dirPath, { mode: 0o700 });
113
+ await syncDirectory(rootPath);
114
+ try {
115
+ await fs.rename(params.jsonPath, entryPath);
116
+ }
117
+ catch (error) {
118
+ if (getErrorCode(error) !== "ENOENT")
119
+ throw error;
120
+ await removeRetirementRecord({ dirPath, rootPath });
121
+ return;
122
+ }
123
+ await syncDirectory(dirPath);
124
+ await syncDirectory(path.dirname(params.jsonPath));
125
+ await recoverDurableQueueRetirement(params);
126
+ }
@@ -0,0 +1,2 @@
1
+ export declare function withQueueTransferLock<T>(filePath: string, run: () => Promise<T>): Promise<T>;
2
+ //# sourceMappingURL=json-durable-queue-transfer-lock.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"json-durable-queue-transfer-lock.d.ts","sourceRoot":"","sources":["../src/json-durable-queue-transfer-lock.ts"],"names":[],"mappings":"AAKA,wBAAsB,qBAAqB,CAAC,CAAC,EAC3C,QAAQ,EAAE,MAAM,EAChB,GAAG,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GACpB,OAAO,CAAC,CAAC,CAAC,CAqBZ"}
@@ -0,0 +1,19 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import path from "node:path";
3
+ import { sha256Hex } from "./file-identity.js";
4
+ import { withSidecarLock } from "./sidecar-lock.js";
5
+ export async function withQueueTransferLock(filePath, run) {
6
+ const lockPath = path.join(path.dirname(filePath), `.fs-safe-transfer-${sha256Hex(path.basename(filePath)).slice(0, 32)}.lock`);
7
+ return await withSidecarLock(filePath, {
8
+ managerKey: "fs-safe.queue-transfer",
9
+ lockPath,
10
+ staleMs: 30_000,
11
+ staleRecovery: "fail-closed",
12
+ timeoutMs: 45_000,
13
+ payload: () => ({
14
+ ownerToken: randomUUID(),
15
+ createdAt: new Date().toISOString(),
16
+ }),
17
+ retry: { retries: 180, minTimeout: 25, maxTimeout: 250, factor: 1.1 },
18
+ }, run);
19
+ }
@@ -1,6 +1,7 @@
1
1
  export type JsonDurableQueueEntryPaths = {
2
2
  jsonPath: string;
3
3
  deliveredPath: string;
4
+ processingPath?: string;
4
5
  };
5
6
  export type JsonDurableQueueReadResult<T> = {
6
7
  entry: T;
@@ -1 +1 @@
1
- {"version":3,"file":"json-durable-queue.d.ts","sourceRoot":"","sources":["../src/json-durable-queue.ts"],"names":[],"mappings":"AAQA,MAAM,MAAM,0BAA0B,GAAG;IACvC,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,0BAA0B,CAAC,CAAC,IAAI;IAC1C,KAAK,EAAE,CAAC,CAAC;IACT,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,2BAA2B,CAAC,CAAC,IAAI;IAC3C,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC,EAAE,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,0BAA0B,CAAC,CAAC,CAAC,CAAC,CAAC;IAC9E,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,CAAC;AAOF,eAAO,MAAM,0CAA0C,QAAmB,CAAC;AAY3E,wBAAsB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAEtE;AAED,wBAAsB,2BAA2B,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAUpF;AAmBD,wBAAgB,iCAAiC,CAC/C,QAAQ,EAAE,MAAM,EAChB,EAAE,EAAE,MAAM,GACT,0BAA0B,CAM5B;AAED,wBAAsB,0BAA0B,CAAC,MAAM,EAAE;IACvD,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;CACnB,GAAG,OAAO,CAAC,IAAI,CAAC,CAIhB;AA+KD,wBAAsB,0BAA0B,CAAC,MAAM,EAAE;IACvD,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,OAAO,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;CACpB,GAAG,OAAO,CAAC,IAAI,CAAC,CAOhB;AAsDD,wBAAsB,yBAAyB,CAAC,CAAC,EAC/C,QAAQ,EAAE,MAAM,EAChB,OAAO,GAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAO,GAClC,OAAO,CAAC,CAAC,CAAC,CAOZ;AAED,wBAAsB,wBAAwB,CAAC,KAAK,EAAE,0BAA0B,GAAG,OAAO,CAAC,IAAI,CAAC,CAW/F;AAED,wBAAsB,yBAAyB,CAAC,CAAC,EAAE,MAAM,EAAE;IACzD,KAAK,EAAE,0BAA0B,CAAC;IAClC,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC,EAAE,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,0BAA0B,CAAC,CAAC,CAAC,CAAC,CAAC;IAC9E,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,GAAG,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC,CAwBpB;AAED,wBAAsB,kCAAkC,CAAC,CAAC,EACxD,OAAO,EAAE,2BAA2B,CAAC,CAAC,CAAC,GACtC,OAAO,CAAC,CAAC,EAAE,CAAC,CAkDd;AAED,wBAAsB,iCAAiC,CAAC,MAAM,EAAE;IAC9D,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,EAAE,EAAE,MAAM,CAAC;CACZ,GAAG,OAAO,CAAC,IAAI,CAAC,CAShB"}
1
+ {"version":3,"file":"json-durable-queue.d.ts","sourceRoot":"","sources":["../src/json-durable-queue.ts"],"names":[],"mappings":"AAiBA,MAAM,MAAM,0BAA0B,GAAG;IACvC,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB,CAAC;AAEF,MAAM,MAAM,0BAA0B,CAAC,CAAC,IAAI;IAC1C,KAAK,EAAE,CAAC,CAAC;IACT,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,2BAA2B,CAAC,CAAC,IAAI;IAC3C,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC,EAAE,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,0BAA0B,CAAC,CAAC,CAAC,CAAC,CAAC;IAC9E,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,CAAC;AAOF,eAAO,MAAM,0CAA0C,QAAmB,CAAC;AAY3E,wBAAsB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAEtE;AAED,wBAAsB,2BAA2B,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAUpF;AAmBD,wBAAgB,iCAAiC,CAC/C,QAAQ,EAAE,MAAM,EAChB,EAAE,EAAE,MAAM,GACT,0BAA0B,CAO5B;AAED,wBAAsB,0BAA0B,CAAC,MAAM,EAAE;IACvD,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;CACnB,GAAG,OAAO,CAAC,IAAI,CAAC,CAIhB;AAgLD,wBAAsB,0BAA0B,CAAC,MAAM,EAAE;IACvD,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,OAAO,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;CACpB,GAAG,OAAO,CAAC,IAAI,CAAC,CAShB;AA4ED,wBAAsB,yBAAyB,CAAC,CAAC,EAC/C,QAAQ,EAAE,MAAM,EAChB,OAAO,GAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAO,GAClC,OAAO,CAAC,CAAC,CAAC,CASZ;AAED,wBAAsB,wBAAwB,CAAC,KAAK,EAAE,0BAA0B,GAAG,OAAO,CAAC,IAAI,CAAC,CAE/F;AAED,wBAAsB,yBAAyB,CAAC,CAAC,EAAE,MAAM,EAAE;IACzD,KAAK,EAAE,0BAA0B,CAAC;IAClC,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC,EAAE,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,0BAA0B,CAAC,CAAC,CAAC,CAAC,CAAC;IAC9E,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,GAAG,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC,CAsBpB;AAED,wBAAsB,kCAAkC,CAAC,CAAC,EACxD,OAAO,EAAE,2BAA2B,CAAC,CAAC,CAAC,GACtC,OAAO,CAAC,CAAC,EAAE,CAAC,CA4Dd;AAED,wBAAsB,iCAAiC,CAAC,MAAM,EAAE;IAC9D,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,EAAE,EAAE,MAAM,CAAC;CACZ,GAAG,OAAO,CAAC,IAAI,CAAC,CAShB"}