@openclaw/fs-safe 0.1.2 → 0.2.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.
- package/CHANGELOG.md +41 -0
- package/README.md +39 -2
- package/dist/absolute-path.d.ts +14 -0
- package/dist/absolute-path.d.ts.map +1 -1
- package/dist/absolute-path.js +203 -2
- package/dist/advanced.d.ts +2 -1
- package/dist/advanced.d.ts.map +1 -1
- package/dist/advanced.js +2 -1
- package/dist/archive-staging.d.ts.map +1 -1
- package/dist/archive-staging.js +81 -7
- package/dist/archive.d.ts.map +1 -1
- package/dist/archive.js +25 -42
- package/dist/bounded-read-stream.d.ts +8 -0
- package/dist/bounded-read-stream.d.ts.map +1 -0
- package/dist/bounded-read-stream.js +20 -0
- package/dist/config.d.ts +1 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +1 -0
- package/dist/directory-guard.d.ts +18 -0
- package/dist/directory-guard.d.ts.map +1 -0
- package/dist/directory-guard.js +70 -0
- package/dist/file-lock.d.ts +4 -2
- package/dist/file-lock.d.ts.map +1 -1
- package/dist/file-lock.js +13 -2
- package/dist/file-store-boundary.d.ts +2 -4
- package/dist/file-store-boundary.d.ts.map +1 -1
- package/dist/file-store-boundary.js +11 -7
- package/dist/file-store-prune.d.ts +12 -0
- package/dist/file-store-prune.d.ts.map +1 -0
- package/dist/file-store-prune.js +86 -0
- package/dist/file-store-source.d.ts +5 -0
- package/dist/file-store-source.d.ts.map +1 -0
- package/dist/file-store-source.js +30 -0
- package/dist/file-store.d.ts +2 -6
- package/dist/file-store.d.ts.map +1 -1
- package/dist/file-store.js +60 -53
- package/dist/guarded-mkdir.d.ts +6 -0
- package/dist/guarded-mkdir.d.ts.map +1 -0
- package/dist/guarded-mkdir.js +45 -0
- package/dist/guarded-mutation.d.ts +33 -0
- package/dist/guarded-mutation.d.ts.map +1 -0
- package/dist/guarded-mutation.js +76 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/json-document-store.d.ts +2 -0
- package/dist/json-document-store.d.ts.map +1 -1
- package/dist/json-document-store.js +7 -3
- package/dist/json-durable-queue.d.ts +45 -0
- package/dist/json-durable-queue.d.ts.map +1 -0
- package/dist/json-durable-queue.js +200 -0
- package/dist/json.d.ts +35 -5
- package/dist/json.d.ts.map +1 -1
- package/dist/json.js +69 -2
- package/dist/local-roots.d.ts.map +1 -1
- package/dist/local-roots.js +17 -3
- package/dist/lock-config.d.ts +10 -0
- package/dist/lock-config.d.ts.map +1 -0
- package/dist/lock-config.js +12 -0
- package/dist/move-path.d.ts +1 -0
- package/dist/move-path.d.ts.map +1 -1
- package/dist/move-path.js +195 -7
- package/dist/output.d.ts +13 -0
- package/dist/output.d.ts.map +1 -0
- package/dist/output.js +69 -0
- package/dist/path-stat.d.ts +4 -0
- package/dist/path-stat.d.ts.map +1 -0
- package/dist/path-stat.js +15 -0
- package/dist/pinned-write.d.ts.map +1 -1
- package/dist/pinned-write.js +44 -19
- package/dist/private-temp-workspace.d.ts.map +1 -1
- package/dist/private-temp-workspace.js +28 -15
- package/dist/regular-file.d.ts.map +1 -1
- package/dist/regular-file.js +52 -8
- package/dist/replace-directory.d.ts.map +1 -1
- package/dist/replace-directory.js +5 -4
- package/dist/replace-file.d.ts +1 -1
- package/dist/replace-file.d.ts.map +1 -1
- package/dist/replace-file.js +93 -31
- package/dist/root-impl.d.ts.map +1 -1
- package/dist/root-impl.js +57 -51
- package/dist/safe-path-segment.d.ts +9 -0
- package/dist/safe-path-segment.d.ts.map +1 -0
- package/dist/safe-path-segment.js +51 -0
- package/dist/secret-file.d.ts.map +1 -1
- package/dist/secret-file.js +6 -1
- package/dist/sibling-temp.d.ts +1 -0
- package/dist/sibling-temp.d.ts.map +1 -1
- package/dist/sibling-temp.js +32 -7
- package/dist/sidecar-lock.d.ts +2 -0
- package/dist/sidecar-lock.d.ts.map +1 -1
- package/dist/sidecar-lock.js +93 -13
- package/dist/store.d.ts +1 -0
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +1 -0
- package/dist/temp-target.d.ts.map +1 -1
- package/dist/temp-target.js +7 -4
- package/dist/test-hooks.d.ts +6 -0
- package/dist/test-hooks.d.ts.map +1 -1
- package/dist/text-atomic.d.ts +7 -0
- package/dist/text-atomic.d.ts.map +1 -1
- package/dist/text-atomic.js +3 -2
- package/dist/trash.d.ts.map +1 -1
- package/dist/trash.js +48 -13
- package/docs/advanced.md +10 -0
- package/docs/atomic.md +33 -5
- package/docs/config.md +32 -3
- package/docs/index.md +3 -2
- package/docs/json-store.md +4 -0
- package/docs/json.md +34 -0
- package/docs/output.md +92 -0
- package/docs/sidecar-lock.md +13 -12
- package/docs/store.md +33 -0
- package/docs/temp.md +10 -2
- package/docs/testing.md +10 -0
- package/package.json +7 -2
package/dist/file-store.js
CHANGED
|
@@ -2,12 +2,18 @@ import { randomUUID } from "node:crypto";
|
|
|
2
2
|
import syncFs from "node:fs";
|
|
3
3
|
import fs from "node:fs/promises";
|
|
4
4
|
import path from "node:path";
|
|
5
|
+
import { createSyncDirectoryGuard } from "./directory-guard.js";
|
|
5
6
|
import { FsSafeError } from "./errors.js";
|
|
7
|
+
import { pruneExpiredStoreEntries, } from "./file-store-prune.js";
|
|
6
8
|
import { assertSyncDirectoryGuard, ensureParentInRoot, ensureParentSync, openWritableStoreRoot, writeStreamToTempSource, } from "./file-store-boundary.js";
|
|
9
|
+
import { readFileStoreCopySource } from "./file-store-source.js";
|
|
7
10
|
import { createJsonStore } from "./json-document-store.js";
|
|
8
11
|
import { isPathInside, resolveSafeRelativePath } from "./path.js";
|
|
9
12
|
import { root } from "./root.js";
|
|
13
|
+
import { DEFAULT_ROOT_MAX_BYTES } from "./root-impl.js";
|
|
14
|
+
import { matchRootFileOpenFailure, openRootFileSync } from "./root-file.js";
|
|
10
15
|
import { writeSecretFileAtomic } from "./secret-file.js";
|
|
16
|
+
import { getFsSafeTestHooks } from "./test-hooks.js";
|
|
11
17
|
function assertRelativePath(relativePath) {
|
|
12
18
|
const raw = relativePath.trim();
|
|
13
19
|
if (!raw) {
|
|
@@ -29,11 +35,38 @@ function assertMaxBytes(size, maxBytes) {
|
|
|
29
35
|
}
|
|
30
36
|
}
|
|
31
37
|
function isNotFound(error) {
|
|
38
|
+
if (!error) {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
32
41
|
return error instanceof FsSafeError
|
|
33
42
|
? error.code === "not-found"
|
|
34
43
|
: error.code === "ENOENT" ||
|
|
35
44
|
error.code === "ENOTDIR";
|
|
36
45
|
}
|
|
46
|
+
function handleSyncStoreReadOpenFailure(opened) {
|
|
47
|
+
return matchRootFileOpenFailure(opened, {
|
|
48
|
+
path: (failure) => {
|
|
49
|
+
if (isNotFound(failure.error)) {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
throw new FsSafeError("path-mismatch", "store target changed during read", {
|
|
53
|
+
cause: failure.error instanceof Error ? failure.error : undefined,
|
|
54
|
+
});
|
|
55
|
+
},
|
|
56
|
+
validation: (failure) => {
|
|
57
|
+
// Validation failures mean the path existed but violated store policy
|
|
58
|
+
// (directory, hardlink, symlink race). Do not report them as missing.
|
|
59
|
+
throw new FsSafeError("path-mismatch", "store target failed read validation", {
|
|
60
|
+
cause: failure.error instanceof Error ? failure.error : undefined,
|
|
61
|
+
});
|
|
62
|
+
},
|
|
63
|
+
fallback: (failure) => {
|
|
64
|
+
throw new FsSafeError("path-mismatch", "store target changed during read", {
|
|
65
|
+
cause: failure.error instanceof Error ? failure.error : undefined,
|
|
66
|
+
});
|
|
67
|
+
},
|
|
68
|
+
});
|
|
69
|
+
}
|
|
37
70
|
async function copyIntoRoot(params) {
|
|
38
71
|
const relativePath = assertRelativePath(params.relativePath);
|
|
39
72
|
const destination = resolveStorePath(params.rootDir, relativePath);
|
|
@@ -101,7 +134,7 @@ export function fileStore(options) {
|
|
|
101
134
|
writeStream: async (relativePath, stream, writeOptions) => {
|
|
102
135
|
const safeRelativePath = assertRelativePath(relativePath);
|
|
103
136
|
const destination = resolveStorePath(rootDir, safeRelativePath);
|
|
104
|
-
const limit = writeOptions?.maxBytes ?? maxBytes;
|
|
137
|
+
const limit = writeOptions?.maxBytes ?? maxBytes ?? (privateMode ? DEFAULT_ROOT_MAX_BYTES : undefined);
|
|
105
138
|
if (privateMode) {
|
|
106
139
|
const chunks = [];
|
|
107
140
|
let total = 0;
|
|
@@ -143,12 +176,11 @@ export function fileStore(options) {
|
|
|
143
176
|
},
|
|
144
177
|
copyIn: async (relativePath, sourcePath, writeOptions) => privateMode
|
|
145
178
|
? await (async () => {
|
|
146
|
-
const
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
return await write(relativePath, await fs.readFile(sourcePath), writeOptions);
|
|
179
|
+
const buffer = await readFileStoreCopySource({
|
|
180
|
+
sourcePath,
|
|
181
|
+
maxBytes: writeOptions?.maxBytes ?? maxBytes ?? DEFAULT_ROOT_MAX_BYTES,
|
|
182
|
+
});
|
|
183
|
+
return await write(relativePath, buffer, writeOptions);
|
|
152
184
|
})()
|
|
153
185
|
: await copyIntoRoot({
|
|
154
186
|
rootDir,
|
|
@@ -226,37 +258,7 @@ export function fileStore(options) {
|
|
|
226
258
|
}, jsonOptions);
|
|
227
259
|
},
|
|
228
260
|
pruneExpired: async (pruneOptions) => {
|
|
229
|
-
|
|
230
|
-
const recursive = pruneOptions.recursive ?? false;
|
|
231
|
-
const maxDepth = pruneOptions.maxDepth;
|
|
232
|
-
const pruneEmptyDirs = (recursive || maxDepth !== undefined) && (pruneOptions.pruneEmptyDirs ?? false);
|
|
233
|
-
async function pruneDir(dir, depth) {
|
|
234
|
-
const entries = await fs.readdir(dir, { withFileTypes: true }).catch(() => []);
|
|
235
|
-
for (const entry of entries) {
|
|
236
|
-
const fullPath = path.join(dir, entry.name);
|
|
237
|
-
const stat = await fs.lstat(fullPath).catch(() => null);
|
|
238
|
-
if (!stat || stat.isSymbolicLink()) {
|
|
239
|
-
continue;
|
|
240
|
-
}
|
|
241
|
-
if (stat.isDirectory()) {
|
|
242
|
-
const shouldDescend = maxDepth !== undefined ? depth < maxDepth : recursive;
|
|
243
|
-
if (shouldDescend && (await pruneDir(fullPath, depth + 1))) {
|
|
244
|
-
await fs.rmdir(fullPath).catch(() => undefined);
|
|
245
|
-
}
|
|
246
|
-
continue;
|
|
247
|
-
}
|
|
248
|
-
if (stat.isFile() && now - stat.mtimeMs > pruneOptions.ttlMs) {
|
|
249
|
-
await fs.rm(fullPath, { force: true }).catch(() => undefined);
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
if (!pruneEmptyDirs) {
|
|
253
|
-
return false;
|
|
254
|
-
}
|
|
255
|
-
const remaining = await fs.readdir(dir).catch(() => null);
|
|
256
|
-
return remaining !== null && remaining.length === 0;
|
|
257
|
-
}
|
|
258
|
-
await fs.mkdir(rootDir, { recursive: true, mode: dirMode });
|
|
259
|
-
await pruneDir(rootDir, 0);
|
|
261
|
+
await pruneExpiredStoreEntries({ rootDir, dirMode, options: pruneOptions });
|
|
260
262
|
},
|
|
261
263
|
};
|
|
262
264
|
}
|
|
@@ -302,13 +304,16 @@ function ensurePrivateDirectorySync(rootDir, targetDir, mode) {
|
|
|
302
304
|
// Best-effort on platforms that do not enforce POSIX modes.
|
|
303
305
|
}
|
|
304
306
|
}
|
|
307
|
+
const guard = createSyncDirectoryGuard(target);
|
|
308
|
+
assertSyncDirectoryGuard(guard);
|
|
309
|
+
return guard;
|
|
305
310
|
}
|
|
306
311
|
function writeFileSyncAtomic(params) {
|
|
307
312
|
const filePath = path.resolve(params.filePath);
|
|
308
313
|
assertStoreFilePath(params.rootDir, filePath);
|
|
309
314
|
let parentGuard;
|
|
310
315
|
if (params.privateMode) {
|
|
311
|
-
ensurePrivateDirectorySync(params.rootDir, path.dirname(filePath), params.dirMode);
|
|
316
|
+
parentGuard = ensurePrivateDirectorySync(params.rootDir, path.dirname(filePath), params.dirMode);
|
|
312
317
|
try {
|
|
313
318
|
const stat = syncFs.lstatSync(filePath);
|
|
314
319
|
if (stat.isSymbolicLink() || !stat.isFile()) {
|
|
@@ -331,6 +336,7 @@ function writeFileSyncAtomic(params) {
|
|
|
331
336
|
const tempPath = path.join(parentGuard?.dir ?? path.dirname(filePath), `.fs-safe-${process.pid}-${randomUUID()}.tmp`);
|
|
332
337
|
let tempExists = false;
|
|
333
338
|
try {
|
|
339
|
+
getFsSafeTestHooks()?.beforeFileStoreSyncPrivateWrite?.(filePath);
|
|
334
340
|
if (parentGuard) {
|
|
335
341
|
assertSyncDirectoryGuard(parentGuard);
|
|
336
342
|
}
|
|
@@ -393,22 +399,23 @@ export function fileStoreSync(options) {
|
|
|
393
399
|
path: (relativePath) => resolveStorePath(rootDir, relativePath),
|
|
394
400
|
readTextIfExists: (relativePath, readOptions) => {
|
|
395
401
|
const targetPath = resolveStorePath(rootDir, relativePath);
|
|
402
|
+
const opened = openRootFileSync({
|
|
403
|
+
absolutePath: targetPath,
|
|
404
|
+
rootPath: rootDir,
|
|
405
|
+
boundaryLabel: "store root",
|
|
406
|
+
rejectHardlinks: privateMode,
|
|
407
|
+
});
|
|
408
|
+
if (!opened.ok) {
|
|
409
|
+
return handleSyncStoreReadOpenFailure(opened);
|
|
410
|
+
}
|
|
396
411
|
try {
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
assertMaxBytes(stat.size, readOptions?.maxBytes ?? maxBytes);
|
|
402
|
-
if (privateMode && stat.nlink > 1) {
|
|
403
|
-
throw new FsSafeError("hardlink", "private store target must not be hardlinked");
|
|
404
|
-
}
|
|
405
|
-
return syncFs.readFileSync(targetPath, "utf8");
|
|
412
|
+
assertMaxBytes(opened.stat.size, readOptions?.maxBytes ?? maxBytes);
|
|
413
|
+
const raw = syncFs.readFileSync(opened.fd, "utf8");
|
|
414
|
+
assertMaxBytes(Buffer.byteLength(raw, "utf8"), readOptions?.maxBytes ?? maxBytes);
|
|
415
|
+
return raw;
|
|
406
416
|
}
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
return null;
|
|
410
|
-
}
|
|
411
|
-
throw error;
|
|
417
|
+
finally {
|
|
418
|
+
syncFs.closeSync(opened.fd);
|
|
412
419
|
}
|
|
413
420
|
},
|
|
414
421
|
readJsonIfExists: (relativePath, readOptions) => {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"guarded-mkdir.d.ts","sourceRoot":"","sources":["../src/guarded-mkdir.ts"],"names":[],"mappings":"AAaA,wBAAsB,6BAA6B,CAAC,MAAM,EAAE;IAC1D,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,eAAe,CAAC,EAAE,CAAC,aAAa,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;CACnE,GAAG,OAAO,CAAC,IAAI,CAAC,CAiChB"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { assertAsyncDirectoryGuard, createAsyncDirectoryGuard } from "./directory-guard.js";
|
|
4
|
+
import { FsSafeError } from "./errors.js";
|
|
5
|
+
function isSameOrChildPath(candidate, parent) {
|
|
6
|
+
return candidate === parent || candidate.startsWith(`${parent}${path.sep}`);
|
|
7
|
+
}
|
|
8
|
+
function isPathEscape(relativePath) {
|
|
9
|
+
return relativePath === ".." || relativePath.startsWith(`..${path.sep}`) || path.isAbsolute(relativePath);
|
|
10
|
+
}
|
|
11
|
+
export async function mkdirPathComponentsWithGuards(params) {
|
|
12
|
+
const root = path.resolve(params.rootReal);
|
|
13
|
+
const target = path.resolve(params.targetPath);
|
|
14
|
+
const relative = path.relative(root, target);
|
|
15
|
+
if (isPathEscape(relative)) {
|
|
16
|
+
throw new FsSafeError("outside-workspace", "directory is outside workspace root");
|
|
17
|
+
}
|
|
18
|
+
let current = root;
|
|
19
|
+
for (const part of relative.split(path.sep).filter(Boolean)) {
|
|
20
|
+
const next = path.join(current, part);
|
|
21
|
+
const parentGuard = await createAsyncDirectoryGuard(current);
|
|
22
|
+
await params.beforeComponent?.(next);
|
|
23
|
+
await assertAsyncDirectoryGuard(parentGuard);
|
|
24
|
+
try {
|
|
25
|
+
await fs.mkdir(next);
|
|
26
|
+
}
|
|
27
|
+
catch (error) {
|
|
28
|
+
if (!error || typeof error !== "object" || !("code" in error) || error.code !== "EEXIST") {
|
|
29
|
+
throw error;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
const stat = await fs.lstat(next);
|
|
33
|
+
if (stat.isSymbolicLink() || !stat.isDirectory()) {
|
|
34
|
+
throw new FsSafeError("not-file", "directory component must be a directory");
|
|
35
|
+
}
|
|
36
|
+
// Node's recursive mkdir follows symlinks in missing components. Build one
|
|
37
|
+
// segment at a time and realpath-check each segment before descending.
|
|
38
|
+
if (!isSameOrChildPath(path.resolve(await fs.realpath(next)), root)) {
|
|
39
|
+
throw new FsSafeError("outside-workspace", "directory escaped workspace root");
|
|
40
|
+
}
|
|
41
|
+
await createAsyncDirectoryGuard(next);
|
|
42
|
+
await assertAsyncDirectoryGuard(parentGuard);
|
|
43
|
+
current = next;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { type AsyncDirectoryGuard, type SyncDirectoryGuard } from "./directory-guard.js";
|
|
2
|
+
export declare function withAsyncDirectoryGuards<T>(guards: readonly AsyncDirectoryGuard[], mutate: () => Promise<T>, options?: {
|
|
3
|
+
verifyAfter?: boolean;
|
|
4
|
+
onPostGuardFailure?: (result: T, error: unknown) => Promise<void> | void;
|
|
5
|
+
}): Promise<T>;
|
|
6
|
+
export declare function withSyncDirectoryGuards<T>(guards: readonly SyncDirectoryGuard[], mutate: () => T, options?: {
|
|
7
|
+
verifyAfter?: boolean;
|
|
8
|
+
}): T;
|
|
9
|
+
export declare function guardedRename(params: {
|
|
10
|
+
from: string;
|
|
11
|
+
to: string;
|
|
12
|
+
targetRoot?: string;
|
|
13
|
+
verifyAfter?: boolean;
|
|
14
|
+
}): Promise<void>;
|
|
15
|
+
export declare function guardedRenameSync(params: {
|
|
16
|
+
from: string;
|
|
17
|
+
to: string;
|
|
18
|
+
targetRoot?: string;
|
|
19
|
+
verifyAfter?: boolean;
|
|
20
|
+
}): void;
|
|
21
|
+
export declare function guardedRm(params: {
|
|
22
|
+
target: string;
|
|
23
|
+
recursive?: boolean;
|
|
24
|
+
force?: boolean;
|
|
25
|
+
verifyAfter?: boolean;
|
|
26
|
+
}): Promise<void>;
|
|
27
|
+
export declare function guardedRmSync(params: {
|
|
28
|
+
target: string;
|
|
29
|
+
recursive?: boolean;
|
|
30
|
+
force?: boolean;
|
|
31
|
+
verifyAfter?: boolean;
|
|
32
|
+
}): void;
|
|
33
|
+
//# sourceMappingURL=guarded-mutation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"guarded-mutation.d.ts","sourceRoot":"","sources":["../src/guarded-mutation.ts"],"names":[],"mappings":"AAGA,OAAO,EAOL,KAAK,mBAAmB,EACxB,KAAK,kBAAkB,EACxB,MAAM,sBAAsB,CAAC;AAE9B,wBAAsB,wBAAwB,CAAC,CAAC,EAC9C,MAAM,EAAE,SAAS,mBAAmB,EAAE,EACtC,MAAM,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EACxB,OAAO,GAAE;IACP,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,kBAAkB,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,EAAE,KAAK,EAAE,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;CACrE,GACL,OAAO,CAAC,CAAC,CAAC,CAyBZ;AAED,wBAAgB,uBAAuB,CAAC,CAAC,EACvC,MAAM,EAAE,SAAS,kBAAkB,EAAE,EACrC,MAAM,EAAE,MAAM,CAAC,EACf,OAAO,GAAE;IAAE,WAAW,CAAC,EAAE,OAAO,CAAA;CAAO,GACtC,CAAC,CAWH;AAED,wBAAsB,aAAa,CAAC,MAAM,EAAE;IAC1C,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB,GAAG,OAAO,CAAC,IAAI,CAAC,CAYhB;AAED,wBAAgB,iBAAiB,CAAC,MAAM,EAAE;IACxC,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB,GAAG,IAAI,CAUP;AAED,wBAAsB,SAAS,CAAC,MAAM,EAAE;IACtC,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB,GAAG,OAAO,CAAC,IAAI,CAAC,CAYhB;AAED,wBAAgB,aAAa,CAAC,MAAM,EAAE;IACpC,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB,GAAG,IAAI,CAWP"}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import fsSync from "node:fs";
|
|
2
|
+
import fs from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { assertAsyncDirectoryGuard, assertSyncDirectoryGuard, createAsyncDirectoryGuard, createNearestExistingDirectoryGuard, createNearestExistingSyncDirectoryGuard, createSyncDirectoryGuard, } from "./directory-guard.js";
|
|
5
|
+
export async function withAsyncDirectoryGuards(guards, mutate, options = {}) {
|
|
6
|
+
for (const guard of guards) {
|
|
7
|
+
await assertAsyncDirectoryGuard(guard);
|
|
8
|
+
}
|
|
9
|
+
const result = await mutate();
|
|
10
|
+
if (options.verifyAfter !== false) {
|
|
11
|
+
try {
|
|
12
|
+
for (const guard of guards) {
|
|
13
|
+
await assertAsyncDirectoryGuard(guard);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
catch (error) {
|
|
17
|
+
if (options.onPostGuardFailure) {
|
|
18
|
+
try {
|
|
19
|
+
// The mutation may have returned an owned resource before the post-guard
|
|
20
|
+
// check detected a swapped directory. Give callers one chance to close
|
|
21
|
+
// handles without letting cleanup hide the boundary failure.
|
|
22
|
+
await options.onPostGuardFailure(result, error);
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
// Preserve the boundary failure. Cleanup is best-effort.
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
throw error;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return result;
|
|
32
|
+
}
|
|
33
|
+
export function withSyncDirectoryGuards(guards, mutate, options = {}) {
|
|
34
|
+
for (const guard of guards) {
|
|
35
|
+
assertSyncDirectoryGuard(guard);
|
|
36
|
+
}
|
|
37
|
+
const result = mutate();
|
|
38
|
+
if (options.verifyAfter !== false) {
|
|
39
|
+
for (const guard of guards) {
|
|
40
|
+
assertSyncDirectoryGuard(guard);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return result;
|
|
44
|
+
}
|
|
45
|
+
export async function guardedRename(params) {
|
|
46
|
+
const sourceGuard = await createAsyncDirectoryGuard(path.dirname(params.from));
|
|
47
|
+
const targetGuard = params.targetRoot
|
|
48
|
+
? await createNearestExistingDirectoryGuard(params.targetRoot, path.dirname(params.to))
|
|
49
|
+
: await createAsyncDirectoryGuard(path.dirname(params.to));
|
|
50
|
+
await withAsyncDirectoryGuards([sourceGuard, targetGuard], async () => {
|
|
51
|
+
await fs.rename(params.from, params.to);
|
|
52
|
+
}, { verifyAfter: params.verifyAfter });
|
|
53
|
+
}
|
|
54
|
+
export function guardedRenameSync(params) {
|
|
55
|
+
const sourceGuard = createSyncDirectoryGuard(path.dirname(params.from));
|
|
56
|
+
const targetGuard = params.targetRoot
|
|
57
|
+
? createNearestExistingSyncDirectoryGuard(params.targetRoot, path.dirname(params.to))
|
|
58
|
+
: createSyncDirectoryGuard(path.dirname(params.to));
|
|
59
|
+
withSyncDirectoryGuards([sourceGuard, targetGuard], () => fsSync.renameSync(params.from, params.to), { verifyAfter: params.verifyAfter });
|
|
60
|
+
}
|
|
61
|
+
export async function guardedRm(params) {
|
|
62
|
+
const guard = await createAsyncDirectoryGuard(path.dirname(params.target));
|
|
63
|
+
await withAsyncDirectoryGuards([guard], async () => {
|
|
64
|
+
await fs.rm(params.target, {
|
|
65
|
+
...(params.recursive !== undefined ? { recursive: params.recursive } : {}),
|
|
66
|
+
...(params.force !== undefined ? { force: params.force } : {}),
|
|
67
|
+
});
|
|
68
|
+
}, { verifyAfter: params.verifyAfter });
|
|
69
|
+
}
|
|
70
|
+
export function guardedRmSync(params) {
|
|
71
|
+
const guard = createSyncDirectoryGuard(path.dirname(params.target));
|
|
72
|
+
withSyncDirectoryGuards([guard], () => fsSync.rmSync(params.target, {
|
|
73
|
+
...(params.recursive !== undefined ? { recursive: params.recursive } : {}),
|
|
74
|
+
...(params.force !== undefined ? { force: params.force } : {}),
|
|
75
|
+
}), { verifyAfter: params.verifyAfter });
|
|
76
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
export { FsSafeError, categorizeFsSafeError, type FsSafeErrorCategory, type FsSafeErrorCode, } from "./errors.js";
|
|
2
2
|
export { DEFAULT_ROOT_MAX_BYTES, root, type HardlinkPolicy, type OpenResult, type ReadResult, type Root, type RootAppendOptions, type RootCopyOptions, type RootCreateJsonOptions, type RootCreateOptions, type RootDefaults, type RootOpenOptions, type RootOpenWritableOptions, type RootOptions, type RootReadOptions, type RootWriteJsonOptions, type RootWriteOptions, type SymlinkPolicy, type WritableOpenMode, type WritableOpenResult, } from "./root.js";
|
|
3
3
|
export { configureFsSafePython, getFsSafePythonConfig, type FsSafePythonConfig, type FsSafePythonMode, } from "./pinned-python-config.js";
|
|
4
|
+
export { writeExternalFileWithinRoot, type ExternalFileWriteOptions, type ExternalFileWriteResult, } from "./output.js";
|
|
5
|
+
export { configureFsSafeLocks, getFsSafeLockConfig, type FsSafeLockConfig, } from "./lock-config.js";
|
|
4
6
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,WAAW,EACX,qBAAqB,EACrB,KAAK,mBAAmB,EACxB,KAAK,eAAe,GACrB,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,sBAAsB,EACtB,IAAI,EACJ,KAAK,cAAc,EACnB,KAAK,UAAU,EACf,KAAK,UAAU,EACf,KAAK,IAAI,EACT,KAAK,iBAAiB,EACtB,KAAK,eAAe,EACpB,KAAK,qBAAqB,EAC1B,KAAK,iBAAiB,EACtB,KAAK,YAAY,EACjB,KAAK,eAAe,EACpB,KAAK,uBAAuB,EAC5B,KAAK,WAAW,EAChB,KAAK,eAAe,EACpB,KAAK,oBAAoB,EACzB,KAAK,gBAAgB,EACrB,KAAK,aAAa,EAClB,KAAK,gBAAgB,EACrB,KAAK,kBAAkB,GACxB,MAAM,WAAW,CAAC;AACnB,OAAO,EACL,qBAAqB,EACrB,qBAAqB,EACrB,KAAK,kBAAkB,EACvB,KAAK,gBAAgB,GACtB,MAAM,2BAA2B,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,WAAW,EACX,qBAAqB,EACrB,KAAK,mBAAmB,EACxB,KAAK,eAAe,GACrB,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,sBAAsB,EACtB,IAAI,EACJ,KAAK,cAAc,EACnB,KAAK,UAAU,EACf,KAAK,UAAU,EACf,KAAK,IAAI,EACT,KAAK,iBAAiB,EACtB,KAAK,eAAe,EACpB,KAAK,qBAAqB,EAC1B,KAAK,iBAAiB,EACtB,KAAK,YAAY,EACjB,KAAK,eAAe,EACpB,KAAK,uBAAuB,EAC5B,KAAK,WAAW,EAChB,KAAK,eAAe,EACpB,KAAK,oBAAoB,EACzB,KAAK,gBAAgB,EACrB,KAAK,aAAa,EAClB,KAAK,gBAAgB,EACrB,KAAK,kBAAkB,GACxB,MAAM,WAAW,CAAC;AACnB,OAAO,EACL,qBAAqB,EACrB,qBAAqB,EACrB,KAAK,kBAAkB,EACvB,KAAK,gBAAgB,GACtB,MAAM,2BAA2B,CAAC;AACnC,OAAO,EACL,2BAA2B,EAC3B,KAAK,wBAAwB,EAC7B,KAAK,uBAAuB,GAC7B,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,oBAAoB,EACpB,mBAAmB,EACnB,KAAK,gBAAgB,GACtB,MAAM,kBAAkB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
1
|
export { FsSafeError, categorizeFsSafeError, } from "./errors.js";
|
|
2
2
|
export { DEFAULT_ROOT_MAX_BYTES, root, } from "./root.js";
|
|
3
3
|
export { configureFsSafePython, getFsSafePythonConfig, } from "./pinned-python-config.js";
|
|
4
|
+
export { writeExternalFileWithinRoot, } from "./output.js";
|
|
5
|
+
export { configureFsSafeLocks, getFsSafeLockConfig, } from "./lock-config.js";
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import type { FileLockRetryOptions } from "./file-lock.js";
|
|
2
|
+
import type { SidecarLockStaleRecovery } from "./sidecar-lock.js";
|
|
2
3
|
export type JsonStoreLockOptions = {
|
|
3
4
|
staleMs?: number;
|
|
4
5
|
timeoutMs?: number;
|
|
5
6
|
retry?: FileLockRetryOptions;
|
|
7
|
+
staleRecovery?: SidecarLockStaleRecovery;
|
|
6
8
|
managerKey?: string;
|
|
7
9
|
};
|
|
8
10
|
export type JsonFileStoreOptions = {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"json-document-store.d.ts","sourceRoot":"","sources":["../src/json-document-store.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AAG3D,MAAM,MAAM,oBAAoB,GAAG;IACjC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,oBAAoB,CAAC;IAC7B,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG;IACjC,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,IAAI,CAAC,EAAE,OAAO,GAAG,oBAAoB,CAAC;CACvC,CAAC;AAEF,MAAM,MAAM,SAAS,CAAC,CAAC,IAAI;IACzB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,IAAI,IAAI,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC,CAAC;IAC/B,MAAM,CAAC,QAAQ,EAAE,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IAChC,YAAY,IAAI,OAAO,CAAC,CAAC,CAAC,CAAC;IAC3B,KAAK,CAAC,KAAK,EAAE,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/B,MAAM,CAAC,GAAG,EAAE,CAAC,OAAO,EAAE,CAAC,GAAG,SAAS,KAAK,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IACpE,QAAQ,CAAC,QAAQ,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,OAAO,EAAE,CAAC,KAAK,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;CACxE,CAAC;AAEF,MAAM,MAAM,gBAAgB,CAAC,CAAC,IAAI;IAChC,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,IAAI,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;IAClC,YAAY,IAAI,OAAO,CAAC,CAAC,CAAC,CAAC;IAC3B,KAAK,CAAC,KAAK,EAAE,CAAC,EAAE,OAAO,CAAC,EAAE;QAAE,eAAe,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACzE,CAAC;
|
|
1
|
+
{"version":3,"file":"json-document-store.d.ts","sourceRoot":"","sources":["../src/json-document-store.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AAG3D,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,mBAAmB,CAAC;AAElE,MAAM,MAAM,oBAAoB,GAAG;IACjC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,oBAAoB,CAAC;IAC7B,aAAa,CAAC,EAAE,wBAAwB,CAAC;IACzC,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG;IACjC,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,IAAI,CAAC,EAAE,OAAO,GAAG,oBAAoB,CAAC;CACvC,CAAC;AAEF,MAAM,MAAM,SAAS,CAAC,CAAC,IAAI;IACzB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,IAAI,IAAI,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC,CAAC;IAC/B,MAAM,CAAC,QAAQ,EAAE,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IAChC,YAAY,IAAI,OAAO,CAAC,CAAC,CAAC,CAAC;IAC3B,KAAK,CAAC,KAAK,EAAE,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/B,MAAM,CAAC,GAAG,EAAE,CAAC,OAAO,EAAE,CAAC,GAAG,SAAS,KAAK,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IACpE,QAAQ,CAAC,QAAQ,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,OAAO,EAAE,CAAC,KAAK,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;CACxE,CAAC;AAEF,MAAM,MAAM,gBAAgB,CAAC,CAAC,IAAI;IAChC,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,IAAI,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;IAClC,YAAY,IAAI,OAAO,CAAC,CAAC,CAAC,CAAC;IAC3B,KAAK,CAAC,KAAK,EAAE,CAAC,EAAE,OAAO,CAAC,EAAE;QAAE,eAAe,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACzE,CAAC;AA2BF,wBAAgB,eAAe,CAAC,CAAC,EAC/B,OAAO,EAAE,gBAAgB,CAAC,CAAC,CAAC,EAC5B,OAAO,GAAE,oBAAyB,GACjC,SAAS,CAAC,CAAC,CAAC,CA2Dd"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { getFsSafeLockConfig } from "./lock-config.js";
|
|
1
2
|
import { createSidecarLockManager } from "./sidecar-lock.js";
|
|
2
3
|
function cloneFallback(value) {
|
|
3
4
|
if (value && typeof value === "object") {
|
|
@@ -10,11 +11,13 @@ function resolveLockOptions(filePath, options) {
|
|
|
10
11
|
return null;
|
|
11
12
|
}
|
|
12
13
|
const lockOptions = options.lock === true ? {} : options.lock;
|
|
14
|
+
const defaults = getFsSafeLockConfig();
|
|
13
15
|
return {
|
|
14
16
|
managerKey: lockOptions.managerKey ?? `fs-safe.json-store:${filePath}`,
|
|
15
|
-
retry: lockOptions.retry ?? {},
|
|
16
|
-
staleMs: lockOptions.staleMs ?? 30_000,
|
|
17
|
-
|
|
17
|
+
retry: lockOptions.retry ?? defaults.retry ?? {},
|
|
18
|
+
staleMs: lockOptions.staleMs ?? defaults.staleMs ?? 30_000,
|
|
19
|
+
staleRecovery: lockOptions.staleRecovery ?? defaults.staleRecovery,
|
|
20
|
+
timeoutMs: lockOptions.timeoutMs ?? defaults.timeoutMs ?? 30_000,
|
|
18
21
|
};
|
|
19
22
|
}
|
|
20
23
|
export function createJsonStore(adapter, options = {}) {
|
|
@@ -40,6 +43,7 @@ export function createJsonStore(adapter, options = {}) {
|
|
|
40
43
|
staleMs: lockOptions.staleMs,
|
|
41
44
|
timeoutMs: lockOptions.timeoutMs,
|
|
42
45
|
retry: lockOptions.retry,
|
|
46
|
+
staleRecovery: lockOptions.staleRecovery,
|
|
43
47
|
allowReentrant: true,
|
|
44
48
|
payload: () => ({ pid: process.pid, createdAt: new Date().toISOString() }),
|
|
45
49
|
}, run);
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
export type JsonDurableQueueEntryPaths = {
|
|
2
|
+
jsonPath: string;
|
|
3
|
+
deliveredPath: string;
|
|
4
|
+
};
|
|
5
|
+
export type JsonDurableQueueReadResult<T> = {
|
|
6
|
+
entry: T;
|
|
7
|
+
migrated?: boolean;
|
|
8
|
+
};
|
|
9
|
+
export type JsonDurableQueueLoadOptions<T> = {
|
|
10
|
+
queueDir: string;
|
|
11
|
+
tempPrefix: string;
|
|
12
|
+
read?: (entry: T, filePath: string) => Promise<JsonDurableQueueReadResult<T>>;
|
|
13
|
+
cleanupTmpMaxAgeMs?: number;
|
|
14
|
+
maxBytes?: number;
|
|
15
|
+
};
|
|
16
|
+
export declare const DEFAULT_JSON_DURABLE_QUEUE_ENTRY_MAX_BYTES: number;
|
|
17
|
+
export declare function unlinkBestEffort(filePath: string): Promise<void>;
|
|
18
|
+
export declare function jsonDurableQueueEntryExists(filePath: string): Promise<boolean>;
|
|
19
|
+
export declare function resolveJsonDurableQueueEntryPaths(queueDir: string, id: string): JsonDurableQueueEntryPaths;
|
|
20
|
+
export declare function ensureJsonDurableQueueDirs(params: {
|
|
21
|
+
queueDir: string;
|
|
22
|
+
failedDir: string;
|
|
23
|
+
}): Promise<void>;
|
|
24
|
+
export declare function writeJsonDurableQueueEntry(params: {
|
|
25
|
+
filePath: string;
|
|
26
|
+
entry: unknown;
|
|
27
|
+
tempPrefix: string;
|
|
28
|
+
}): Promise<void>;
|
|
29
|
+
export declare function readJsonDurableQueueEntry<T>(filePath: string, options?: {
|
|
30
|
+
maxBytes?: number;
|
|
31
|
+
}): Promise<T>;
|
|
32
|
+
export declare function ackJsonDurableQueueEntry(paths: JsonDurableQueueEntryPaths): Promise<void>;
|
|
33
|
+
export declare function loadJsonDurableQueueEntry<T>(params: {
|
|
34
|
+
paths: JsonDurableQueueEntryPaths;
|
|
35
|
+
tempPrefix: string;
|
|
36
|
+
read?: (entry: T, filePath: string) => Promise<JsonDurableQueueReadResult<T>>;
|
|
37
|
+
maxBytes?: number;
|
|
38
|
+
}): Promise<T | null>;
|
|
39
|
+
export declare function loadPendingJsonDurableQueueEntries<T>(options: JsonDurableQueueLoadOptions<T>): Promise<T[]>;
|
|
40
|
+
export declare function moveJsonDurableQueueEntryToFailed(params: {
|
|
41
|
+
queueDir: string;
|
|
42
|
+
failedDir: string;
|
|
43
|
+
id: string;
|
|
44
|
+
}): Promise<void>;
|
|
45
|
+
//# sourceMappingURL=json-durable-queue.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"json-durable-queue.d.ts","sourceRoot":"","sources":["../src/json-durable-queue.ts"],"names":[],"mappings":"AAMA,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;AAEF,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,CAGhB;AAED,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;AAiDD,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,CAOhB"}
|