@openclaw/fs-safe 0.6.0 → 0.7.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 +50 -0
- package/README.md +7 -0
- package/dist/archive-deadline.d.ts +3 -0
- package/dist/archive-deadline.d.ts.map +1 -1
- package/dist/archive-deadline.js +44 -8
- package/dist/archive-entry.d.ts.map +1 -1
- package/dist/archive-entry.js +1 -0
- package/dist/archive-errors.d.ts +1 -0
- package/dist/archive-errors.d.ts.map +1 -1
- package/dist/archive-errors.js +3 -0
- package/dist/archive-input.d.ts.map +1 -1
- package/dist/archive-input.js +26 -16
- package/dist/archive-limits.d.ts +11 -3
- package/dist/archive-limits.d.ts.map +1 -1
- package/dist/archive-limits.js +24 -0
- package/dist/archive-native.d.ts +3 -2
- package/dist/archive-native.d.ts.map +1 -1
- package/dist/archive-native.js +24 -9
- package/dist/archive-policy.d.ts +2 -0
- package/dist/archive-policy.d.ts.map +1 -1
- package/dist/archive-policy.js +9 -1
- package/dist/archive-read.d.ts.map +1 -1
- package/dist/archive-read.js +69 -41
- package/dist/archive-staging.d.ts +3 -0
- package/dist/archive-staging.d.ts.map +1 -1
- package/dist/archive-staging.js +91 -43
- package/dist/archive-tar-admission.d.ts +7 -0
- package/dist/archive-tar-admission.d.ts.map +1 -0
- package/dist/archive-tar-admission.js +43 -0
- package/dist/archive-tar-gnu.d.ts +2 -0
- package/dist/archive-tar-gnu.d.ts.map +1 -0
- package/dist/archive-tar-gnu.js +20 -0
- package/dist/archive-tar-header.d.ts +8 -0
- package/dist/archive-tar-header.d.ts.map +1 -0
- package/dist/archive-tar-header.js +47 -0
- package/dist/archive-tar-meta.d.ts +29 -1
- package/dist/archive-tar-meta.d.ts.map +1 -1
- package/dist/archive-tar-meta.js +104 -35
- package/dist/archive-tar-runtime.d.ts +3 -0
- package/dist/archive-tar-runtime.d.ts.map +1 -1
- package/dist/archive-tar-runtime.js +3 -0
- package/dist/archive-tar.d.ts.map +1 -1
- package/dist/archive-tar.js +8 -2
- package/dist/archive-zip-names.js +2 -2
- package/dist/archive.d.ts.map +1 -1
- package/dist/archive.js +38 -26
- package/dist/atomic.d.ts +1 -1
- package/dist/atomic.d.ts.map +1 -1
- package/dist/bounded-read-stream.d.ts.map +1 -1
- package/dist/bounded-read-stream.js +2 -4
- package/dist/bounded-read.d.ts.map +1 -1
- package/dist/bounded-read.js +3 -10
- package/dist/byte-budget.d.ts +5 -0
- package/dist/byte-budget.d.ts.map +1 -0
- package/dist/byte-budget.js +9 -0
- package/dist/file-lock-sync.d.ts.map +1 -1
- package/dist/file-lock-sync.js +10 -5
- package/dist/file-store-boundary.d.ts.map +1 -1
- package/dist/file-store-boundary.js +9 -5
- package/dist/file-store-limit.d.ts +2 -0
- package/dist/file-store-limit.d.ts.map +1 -0
- package/dist/file-store-limit.js +8 -0
- package/dist/file-store-sync-write.d.ts.map +1 -1
- package/dist/file-store-sync-write.js +38 -8
- package/dist/file-store.d.ts.map +1 -1
- package/dist/file-store.js +26 -26
- package/dist/json-durable-queue-directory.d.ts +2 -0
- package/dist/json-durable-queue-directory.d.ts.map +1 -0
- package/dist/json-durable-queue-directory.js +20 -0
- package/dist/json-durable-queue-ownership.d.ts +14 -0
- package/dist/json-durable-queue-ownership.d.ts.map +1 -0
- package/dist/json-durable-queue-ownership.js +168 -0
- package/dist/json-durable-queue-retirement.d.ts +9 -0
- package/dist/json-durable-queue-retirement.d.ts.map +1 -0
- package/dist/json-durable-queue-retirement.js +126 -0
- package/dist/json-durable-queue-transfer-lock.d.ts +2 -0
- package/dist/json-durable-queue-transfer-lock.d.ts.map +1 -0
- package/dist/json-durable-queue-transfer-lock.js +19 -0
- package/dist/json-durable-queue.d.ts +1 -0
- package/dist/json-durable-queue.d.ts.map +1 -1
- package/dist/json-durable-queue.js +90 -57
- package/dist/json.d.ts.map +1 -1
- package/dist/json.js +27 -8
- package/dist/local-roots.d.ts.map +1 -1
- package/dist/local-roots.js +4 -2
- package/dist/native-binding.d.ts +12 -3
- package/dist/native-binding.d.ts.map +1 -1
- package/dist/output-sibling.d.ts.map +1 -1
- package/dist/output-sibling.js +11 -110
- package/dist/output.d.ts.map +1 -1
- package/dist/output.js +4 -2
- package/dist/pinned-open.d.ts.map +1 -1
- package/dist/pinned-open.js +27 -48
- package/dist/pinned-write.d.ts.map +1 -1
- package/dist/pinned-write.js +8 -9
- package/dist/private-temp-workspace.d.ts +3 -1
- package/dist/private-temp-workspace.d.ts.map +1 -1
- package/dist/private-temp-workspace.js +81 -56
- package/dist/publish-file.d.ts.map +1 -1
- package/dist/publish-file.js +2 -4
- package/dist/read-opened-file.d.ts.map +1 -1
- package/dist/read-opened-file.js +6 -4
- package/dist/regular-file.d.ts.map +1 -1
- package/dist/regular-file.js +136 -82
- package/dist/replace-file-copy-fallback.d.ts +3 -1
- package/dist/replace-file-copy-fallback.d.ts.map +1 -1
- package/dist/replace-file-copy-fallback.js +21 -36
- package/dist/replace-file-copy-source.d.ts +21 -0
- package/dist/replace-file-copy-source.d.ts.map +1 -0
- package/dist/replace-file-copy-source.js +112 -0
- package/dist/replace-file-descriptor.d.ts +13 -3
- package/dist/replace-file-descriptor.d.ts.map +1 -1
- package/dist/replace-file-descriptor.js +32 -7
- package/dist/replace-file-rename-policy.d.ts +7 -0
- package/dist/replace-file-rename-policy.d.ts.map +1 -0
- package/dist/replace-file-rename-policy.js +30 -0
- package/dist/replace-file-temp-owner.d.ts +46 -0
- package/dist/replace-file-temp-owner.d.ts.map +1 -0
- package/dist/replace-file-temp-owner.js +346 -0
- package/dist/replace-file.d.ts +6 -1
- package/dist/replace-file.d.ts.map +1 -1
- package/dist/replace-file.js +72 -58
- package/dist/root-impl.d.ts.map +1 -1
- package/dist/root-impl.js +40 -26
- package/dist/secret-file.d.ts.map +1 -1
- package/dist/secret-file.js +6 -3
- package/dist/secret-read-async.d.ts.map +1 -1
- package/dist/secret-read-async.js +6 -5
- package/dist/secure-file.d.ts.map +1 -1
- package/dist/secure-file.js +17 -13
- package/dist/sibling-staged-file.d.ts +15 -0
- package/dist/sibling-staged-file.d.ts.map +1 -0
- package/dist/sibling-staged-file.js +148 -0
- package/dist/sibling-temp.d.ts +3 -0
- package/dist/sibling-temp.d.ts.map +1 -1
- package/dist/sibling-temp.js +30 -74
- package/dist/sidecar-lock-acquire.d.ts.map +1 -1
- package/dist/sidecar-lock-acquire.js +48 -27
- package/dist/sidecar-lock-handle.d.ts +6 -2
- package/dist/sidecar-lock-handle.d.ts.map +1 -1
- package/dist/sidecar-lock-handle.js +17 -3
- package/dist/sidecar-lock-policy.d.ts +2 -0
- package/dist/sidecar-lock-policy.d.ts.map +1 -1
- package/dist/sidecar-lock-policy.js +29 -0
- package/dist/sidecar-lock-reclaim.d.ts.map +1 -1
- package/dist/sidecar-lock-reclaim.js +29 -7
- package/dist/sidecar-lock.d.ts.map +1 -1
- package/dist/sidecar-lock.js +28 -16
- package/dist/suppressed-error.d.ts +6 -0
- package/dist/suppressed-error.d.ts.map +1 -0
- package/dist/suppressed-error.js +15 -0
- package/dist/temp-cleanup.d.ts +2 -0
- package/dist/temp-cleanup.d.ts.map +1 -1
- package/dist/temp-cleanup.js +25 -10
- package/dist/temp-workspace-owner.d.ts +23 -0
- package/dist/temp-workspace-owner.d.ts.map +1 -0
- package/dist/temp-workspace-owner.js +320 -0
- package/dist/temp.d.ts +1 -1
- package/dist/temp.d.ts.map +1 -1
- package/dist/test-hooks.d.ts +5 -0
- package/dist/test-hooks.d.ts.map +1 -1
- package/docs/advanced.md +8 -1
- package/docs/archive.md +194 -26
- package/docs/atomic.md +11 -2
- package/docs/durability.md +1 -1
- package/docs/file-store.md +2 -0
- package/docs/install.md +8 -4
- package/docs/json.md +8 -4
- package/docs/native-helper.md +15 -3
- package/docs/native.md +33 -6
- package/docs/output.md +10 -0
- package/docs/public-api.md +6 -3
- package/docs/quickstart.md +1 -1
- package/docs/reading.md +1 -1
- package/docs/regular-file.md +9 -2
- package/docs/root.md +2 -0
- package/docs/secret-file.md +5 -1
- package/docs/secure-file.md +4 -2
- package/docs/security-model.md +18 -0
- package/docs/sidecar-lock.md +4 -0
- package/docs/store.md +13 -0
- package/docs/temp.md +128 -14
- package/package.json +10 -10
package/dist/bounded-read.js
CHANGED
|
@@ -1,14 +1,7 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
|
+
import { normalizeMaxBytes } from "./byte-budget.js";
|
|
2
3
|
import { FsSafeError } from "./errors.js";
|
|
3
4
|
const READ_CHUNK_BYTES = 64 * 1024;
|
|
4
|
-
function assertMaxBytes(maxBytes) {
|
|
5
|
-
if (maxBytes === Number.POSITIVE_INFINITY) {
|
|
6
|
-
return;
|
|
7
|
-
}
|
|
8
|
-
if (!Number.isSafeInteger(maxBytes) || maxBytes < 0) {
|
|
9
|
-
throw new RangeError("maxBytes must be a non-negative safe integer or Infinity");
|
|
10
|
-
}
|
|
11
|
-
}
|
|
12
5
|
function createScratchBuffer(maxBytes) {
|
|
13
6
|
const initialReadBytes = Number.isFinite(maxBytes)
|
|
14
7
|
? Math.min(READ_CHUNK_BYTES, maxBytes + 1)
|
|
@@ -29,7 +22,7 @@ function appendChunk(params) {
|
|
|
29
22
|
return total;
|
|
30
23
|
}
|
|
31
24
|
async function readBoundedAsync(maxBytes, readChunk) {
|
|
32
|
-
|
|
25
|
+
normalizeMaxBytes(maxBytes);
|
|
33
26
|
const chunks = [];
|
|
34
27
|
const scratch = createScratchBuffer(maxBytes);
|
|
35
28
|
let total = 0;
|
|
@@ -71,7 +64,7 @@ export async function readFileDescriptorBounded(fd, maxBytes) {
|
|
|
71
64
|
}
|
|
72
65
|
/** Sync bounded read from a numeric descriptor. The caller owns the descriptor. */
|
|
73
66
|
export function readFileDescriptorBoundedSync(fd, maxBytes) {
|
|
74
|
-
|
|
67
|
+
normalizeMaxBytes(maxBytes);
|
|
75
68
|
const chunks = [];
|
|
76
69
|
const scratch = createScratchBuffer(maxBytes);
|
|
77
70
|
let total = 0;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"byte-budget.d.ts","sourceRoot":"","sources":["../src/byte-budget.ts"],"names":[],"mappings":"AAAA,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,MAAM,GAAG,SAAS,EACzB,OAAO,GAAE;IAAE,YAAY,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAO,GACtD,MAAM,GAAG,SAAS,CASpB"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export function normalizeMaxBytes(value, options = {}) {
|
|
2
|
+
const selected = value === undefined ? options.defaultValue : value;
|
|
3
|
+
if (selected === undefined || selected === Number.POSITIVE_INFINITY)
|
|
4
|
+
return selected;
|
|
5
|
+
if (!Number.isSafeInteger(selected) || selected < 0) {
|
|
6
|
+
throw new RangeError(`${options.label ?? "maxBytes"} must be a non-negative safe integer or Infinity`);
|
|
7
|
+
}
|
|
8
|
+
return selected;
|
|
9
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"file-lock-sync.d.ts","sourceRoot":"","sources":["../src/file-lock-sync.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EAOL,KAAK,wBAAwB,EAC9B,MAAM,2BAA2B,CAAC;
|
|
1
|
+
{"version":3,"file":"file-lock-sync.d.ts","sourceRoot":"","sources":["../src/file-lock-sync.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EAOL,KAAK,wBAAwB,EAC9B,MAAM,2BAA2B,CAAC;AAOnC,OAAO,KAAK,EACV,0BAA0B,EAC1B,uBAAuB,EACvB,wBAAwB,EACzB,MAAM,yBAAyB,CAAC;AAIjC,MAAM,MAAM,0BAA0B,CAAC,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,IAAI;IACjF,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,uBAAuB,CAAC;IAChC,aAAa,CAAC,EAAE,wBAAwB,CAAC;IACzC,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,OAAO,EAAE,MAAM,QAAQ,CAAC;IACxB,aAAa,CAAC,EAAE,CAAC,MAAM,EAAE;QACvB,QAAQ,EAAE,MAAM,CAAC;QACjB,oBAAoB,EAAE,MAAM,CAAC;QAC7B,OAAO,EAAE,OAAO,CAAC;QACjB,OAAO,EAAE,MAAM,CAAC;QAChB,KAAK,EAAE,MAAM,CAAC;QACd,iBAAiB,EAAE,KAAK,CAAC;KAC1B,KAAK,OAAO,CAAC;IACd,qBAAqB,CAAC,EAAE,CAAC,QAAQ,EAAE,wBAAwB,KAAK,OAAO,CAAC;IACxE,YAAY,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC;IACxC,QAAQ,CAAC,EAAE,IAAI,CAAC;IAChB,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE,0BAA0B,KAAK,IAAI,CAAC;IAC3D,yBAAyB,CAAC,EAAE,MAAM,CAAC;CACpC,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,QAAQ,EAAE,MAAM,CAAC;IACjB,oBAAoB,EAAE,MAAM,CAAC;IAC7B,eAAe,IAAI,OAAO,CAAC;IAC3B,OAAO,IAAI,IAAI,CAAC;IAChB,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,IAAI,CAAC;CAC1B,CAAC;AA0IF,wBAAgB,mBAAmB,CAAC,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC1E,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,0BAA0B,CAAC,QAAQ,CAAC,GAC5C,kBAAkB,CAwKpB;AAED,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC1E,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,0BAA0B,CAAC,QAAQ,CAAC,EAC7C,EAAE,EAAE,MAAM,CAAC,GACV,CAAC,CAOH"}
|
package/dist/file-lock-sync.js
CHANGED
|
@@ -2,7 +2,7 @@ import fs from "node:fs";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { FsSafeError } from "./errors.js";
|
|
4
4
|
import { readSidecarLockSnapshotSync, relativeSidecarLockPath, removeSidecarLockIfUnchangedSync, serializeSidecarLockPayload, sidecarLockSnapshotMatches, } from "./sidecar-lock-reclaim.js";
|
|
5
|
-
import { computeSidecarLockDelayMs, sidecarLockPayloadCreatedAtMs, } from "./sidecar-lock-policy.js";
|
|
5
|
+
import { computeSidecarLockDelayMs, sidecarLockPayloadCreatedAtMs, validateSidecarLockRetryOptions, validateSidecarLockTimeoutMs, } from "./sidecar-lock-policy.js";
|
|
6
6
|
import { getFsSafeLockConfig } from "./lock-config.js";
|
|
7
7
|
import { sleepSync } from "./timing.js";
|
|
8
8
|
const SYNC_HELD_LOCKS_KEY = Symbol.for("fsSafe.syncSidecarLocks");
|
|
@@ -127,6 +127,11 @@ function reclaimGuardExists(reclaimGuardPath) {
|
|
|
127
127
|
}
|
|
128
128
|
}
|
|
129
129
|
export function acquireFileLockSync(targetPath, options) {
|
|
130
|
+
const defaults = getFsSafeLockConfig();
|
|
131
|
+
const retry = options.retry ?? defaults.retry ?? {};
|
|
132
|
+
const timeoutMs = options.timeoutMs ?? defaults.timeoutMs;
|
|
133
|
+
validateSidecarLockRetryOptions(retry);
|
|
134
|
+
validateSidecarLockTimeoutMs(timeoutMs);
|
|
130
135
|
const normalizedTargetPath = normalizeTargetPath(targetPath);
|
|
131
136
|
const lockPath = boundedLockPath(options.lockPath ?? `${normalizedTargetPath}.lock`, options.lockRoot);
|
|
132
137
|
const heldLocks = getSyncHeldLocks();
|
|
@@ -140,10 +145,7 @@ export function acquireFileLockSync(targetPath, options) {
|
|
|
140
145
|
}
|
|
141
146
|
// Process defaults fill the same fields here as withLockDefaults() fills for
|
|
142
147
|
// the asynchronous manager, so both acquirers honor configureFsSafeLocks().
|
|
143
|
-
const defaults = getFsSafeLockConfig();
|
|
144
148
|
const staleMs = options.staleMs ?? defaults.staleMs ?? 30_000;
|
|
145
|
-
const retry = options.retry ?? defaults.retry ?? {};
|
|
146
|
-
const timeoutMs = options.timeoutMs ?? defaults.timeoutMs;
|
|
147
149
|
const staleRecovery = options.staleRecovery ?? defaults.staleRecovery;
|
|
148
150
|
const startedAt = Date.now();
|
|
149
151
|
let attempt = 0;
|
|
@@ -158,7 +160,10 @@ export function acquireFileLockSync(targetPath, options) {
|
|
|
158
160
|
normalizedTargetPath,
|
|
159
161
|
});
|
|
160
162
|
}
|
|
161
|
-
|
|
163
|
+
const remaining = timeoutMs === undefined || timeoutMs === Number.POSITIVE_INFINITY
|
|
164
|
+
? Number.POSITIVE_INFINITY
|
|
165
|
+
: Math.max(0, timeoutMs - elapsed);
|
|
166
|
+
sleepSync(Math.min(computeSidecarLockDelayMs(retry, attempt), remaining));
|
|
162
167
|
attempt += 1;
|
|
163
168
|
};
|
|
164
169
|
while (true) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"file-store-boundary.d.ts","sourceRoot":"","sources":["../src/file-store-boundary.ts"],"names":[],"mappings":"AAGA,OAAO,EAAa,KAAK,QAAQ,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"file-store-boundary.d.ts","sourceRoot":"","sources":["../src/file-store-boundary.ts"],"names":[],"mappings":"AAGA,OAAO,EAAa,KAAK,QAAQ,EAAE,MAAM,aAAa,CAAC;AAGvD,OAAO,EAGL,KAAK,kBAAkB,EACxB,MAAM,sBAAsB,CAAC;AAI9B,OAAO,EAA4C,KAAK,IAAI,EAAE,MAAM,WAAW,CAAC;AAGhF,MAAM,MAAM,eAAe,GAAG,kBAAkB,CAAC;AAOjD,wBAAsB,kBAAkB,CACtC,UAAU,EAAE,IAAI,EAChB,YAAY,EAAE,MAAM,EACpB,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,IAAI,CAAC,CAOf;AAED,wBAAsB,qBAAqB,CAAC,MAAM,EAAE;IAClD,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,GAAG,OAAO,CAAC,IAAI,CAAC,CAKhB;AAgDD,wBAAsB,uBAAuB,CAAC,MAAM,EAAE;IACpD,MAAM,EAAE,QAAQ,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;CACd,GAAG,OAAO,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;CAAE,CAAC,CAwC1D;AAED,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,eAAe,GAAG,IAAI,CAWrE;AAUD,wBAAgB,gBAAgB,CAAC,MAAM,EAAE;IACvC,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;CACd,GAAG,eAAe,CAOlB;AAED,wBAAgB,wBAAwB,CAAC,MAAM,EAAE;IAC/C,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,aAAa,EAAE,eAAe,GAAG,OAAO,CAAC;CAC1C,GAAG,eAAe,CAyDlB"}
|
|
@@ -2,6 +2,7 @@ import syncFs from "node:fs";
|
|
|
2
2
|
import fs from "node:fs/promises";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { Transform } from "node:stream";
|
|
5
|
+
import { normalizeMaxBytes } from "./byte-budget.js";
|
|
5
6
|
import { pipeline } from "node:stream/promises";
|
|
6
7
|
import { assertSyncDirectoryGuard as assertDirectoryGuardSync, createSyncDirectoryGuard, } from "./directory-guard.js";
|
|
7
8
|
import { FsSafeError } from "./errors.js";
|
|
@@ -22,9 +23,10 @@ export async function ensureParentInRoot(scopedRoot, relativePath, mode) {
|
|
|
22
23
|
await chmodDirectoryInRootBestEffort(scopedRoot, parent, mode).catch(() => undefined);
|
|
23
24
|
}
|
|
24
25
|
export async function openWritableStoreRoot(params) {
|
|
26
|
+
const maxBytes = normalizeMaxBytes(params.maxBytes);
|
|
25
27
|
await fs.mkdir(params.rootDir, { recursive: true, mode: params.dirMode });
|
|
26
28
|
await fs.chmod(params.rootDir, params.dirMode).catch(() => undefined);
|
|
27
|
-
return await root(params.rootDir, { hardlinks: "reject", maxBytes
|
|
29
|
+
return await root(params.rootDir, { hardlinks: "reject", maxBytes });
|
|
28
30
|
}
|
|
29
31
|
async function chmodDirectoryInRootBestEffort(scopedRoot, relativePath, mode) {
|
|
30
32
|
const dirPath = await scopedRoot.resolve(relativePath);
|
|
@@ -49,7 +51,8 @@ async function chmodDirectoryInRootBestEffort(scopedRoot, relativePath, mode) {
|
|
|
49
51
|
}
|
|
50
52
|
}
|
|
51
53
|
function createMaxBytesTransform(maxBytes) {
|
|
52
|
-
|
|
54
|
+
const limit = normalizeMaxBytes(maxBytes);
|
|
55
|
+
if (limit === undefined) {
|
|
53
56
|
return undefined;
|
|
54
57
|
}
|
|
55
58
|
let total = 0;
|
|
@@ -57,8 +60,8 @@ function createMaxBytesTransform(maxBytes) {
|
|
|
57
60
|
transform(chunk, _encoding, callback) {
|
|
58
61
|
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
59
62
|
total += buffer.byteLength;
|
|
60
|
-
if (total >
|
|
61
|
-
callback(new FsSafeError("too-large", `file exceeds maximum size of ${
|
|
63
|
+
if (total > limit) {
|
|
64
|
+
callback(new FsSafeError("too-large", `file exceeds maximum size of ${limit} bytes`));
|
|
62
65
|
return;
|
|
63
66
|
}
|
|
64
67
|
callback(null, buffer);
|
|
@@ -66,6 +69,7 @@ function createMaxBytesTransform(maxBytes) {
|
|
|
66
69
|
});
|
|
67
70
|
}
|
|
68
71
|
export async function writeStreamToTempSource(params) {
|
|
72
|
+
const maxBytes = normalizeMaxBytes(params.maxBytes);
|
|
69
73
|
const tempRoot = resolveSecureTempRoot({
|
|
70
74
|
fallbackPrefix: "fs-safe-file-store",
|
|
71
75
|
unsafeFallbackLabel: "file store temp dir",
|
|
@@ -81,7 +85,7 @@ export async function writeStreamToTempSource(params) {
|
|
|
81
85
|
writable.once("close", () => {
|
|
82
86
|
handleClosedByStream = true;
|
|
83
87
|
});
|
|
84
|
-
const limiter = createMaxBytesTransform(
|
|
88
|
+
const limiter = createMaxBytesTransform(maxBytes);
|
|
85
89
|
if (limiter) {
|
|
86
90
|
await pipeline(params.stream, limiter, writable);
|
|
87
91
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file-store-limit.d.ts","sourceRoot":"","sources":["../src/file-store-limit.ts"],"names":[],"mappings":"AAGA,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAK7E"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { normalizeMaxBytes } from "./byte-budget.js";
|
|
2
|
+
import { FsSafeError } from "./errors.js";
|
|
3
|
+
export function assertFileStoreMaxBytes(size, maxBytes) {
|
|
4
|
+
const limit = normalizeMaxBytes(maxBytes);
|
|
5
|
+
if (limit !== undefined && size > limit) {
|
|
6
|
+
throw new FsSafeError("too-large", `file exceeds maximum size of ${limit} bytes`);
|
|
7
|
+
}
|
|
8
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"file-store-sync-write.d.ts","sourceRoot":"","sources":["../src/file-store-sync-write.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"file-store-sync-write.d.ts","sourceRoot":"","sources":["../src/file-store-sync-write.ts"],"names":[],"mappings":"AAwBA,wBAAgB,mBAAmB,CAAC,MAAM,EAAE;IAC1C,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,GAAG,UAAU,CAAC;IAC7B,WAAW,EAAE,OAAO,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;CACd,GAAG,MAAM,CA6GT"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import fs from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
|
+
import { syncDirectorySync } from "./directory-durability.js";
|
|
4
5
|
import { FsSafeError } from "./errors.js";
|
|
5
6
|
import { sameFileIdentity } from "./file-identity.js";
|
|
6
7
|
import { assertSyncDirectoryGuard, ensureParentSync, ensureStoreDirectorySync, } from "./file-store-boundary.js";
|
|
@@ -48,15 +49,35 @@ export function writeFileSyncAtomic(params) {
|
|
|
48
49
|
if (parentGuard) {
|
|
49
50
|
assertSyncDirectoryGuard(parentGuard);
|
|
50
51
|
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
52
|
+
const tempStat = (() => {
|
|
53
|
+
const descriptor = fs.openSync(tempPath, "wx", params.mode);
|
|
54
|
+
tempExists = true;
|
|
55
|
+
try {
|
|
56
|
+
fs.writeFileSync(descriptor, params.content);
|
|
57
|
+
try {
|
|
58
|
+
fs.fchmodSync(descriptor, params.mode);
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
// Best-effort on platforms that do not enforce POSIX modes.
|
|
62
|
+
}
|
|
63
|
+
const opened = fs.fstatSync(descriptor);
|
|
64
|
+
if (!opened.isFile() || opened.nlink > 1) {
|
|
65
|
+
throw new FsSafeError("path-mismatch", "store temp is not an owned regular file");
|
|
66
|
+
}
|
|
67
|
+
fs.fsyncSync(descriptor);
|
|
68
|
+
return opened;
|
|
69
|
+
}
|
|
70
|
+
finally {
|
|
71
|
+
fs.closeSync(descriptor);
|
|
72
|
+
}
|
|
73
|
+
})();
|
|
74
|
+
const tempPathStat = fs.lstatSync(tempPath);
|
|
75
|
+
if (tempPathStat.isSymbolicLink() ||
|
|
76
|
+
!tempPathStat.isFile() ||
|
|
77
|
+
tempPathStat.nlink > 1 ||
|
|
78
|
+
!sameFileIdentity(tempPathStat, tempStat)) {
|
|
79
|
+
throw new FsSafeError("path-mismatch", "store temp changed before publication");
|
|
58
80
|
}
|
|
59
|
-
const tempStat = fs.lstatSync(tempPath);
|
|
60
81
|
if (parentGuard) {
|
|
61
82
|
assertSyncDirectoryGuard(parentGuard);
|
|
62
83
|
}
|
|
@@ -82,6 +103,15 @@ export function writeFileSyncAtomic(params) {
|
|
|
82
103
|
cause: error instanceof Error ? error : undefined,
|
|
83
104
|
});
|
|
84
105
|
}
|
|
106
|
+
if (parentGuard) {
|
|
107
|
+
assertSyncDirectoryGuard(parentGuard);
|
|
108
|
+
syncDirectorySync({
|
|
109
|
+
path: parentGuard.dir,
|
|
110
|
+
realPath: parentGuard.realPath,
|
|
111
|
+
identity: parentGuard.stat,
|
|
112
|
+
}, { label: "store parent" });
|
|
113
|
+
assertSyncDirectoryGuard(parentGuard);
|
|
114
|
+
}
|
|
85
115
|
return filePath;
|
|
86
116
|
}
|
|
87
117
|
finally {
|
package/dist/file-store.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"file-store.d.ts","sourceRoot":"","sources":["../src/file-store.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"file-store.d.ts","sourceRoot":"","sources":["../src/file-store.ts"],"names":[],"mappings":"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"}
|
package/dist/file-store.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
|
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
|
-
|
|
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) =>
|
|
212
|
-
|
|
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:
|
|
213
|
+
maxBytes: configuredLimit ?? DEFAULT_ROOT_MAX_BYTES,
|
|
216
214
|
});
|
|
217
215
|
return await write(relativePath, buffer, writeOptions);
|
|
218
|
-
}
|
|
219
|
-
|
|
216
|
+
}
|
|
217
|
+
return await copyIntoRoot({
|
|
220
218
|
rootDir,
|
|
221
219
|
relativePath,
|
|
222
220
|
sourcePath,
|
|
223
221
|
dirMode: writeOptions?.dirMode ?? dirMode,
|
|
224
|
-
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
|
-
|
|
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
|
-
|
|
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 @@
|
|
|
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"}
|