@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
|
@@ -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 @@
|
|
|
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 +1 @@
|
|
|
1
|
-
{"version":3,"file":"json-durable-queue.d.ts","sourceRoot":"","sources":["../src/json-durable-queue.ts"],"names":[],"mappings":"
|
|
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"}
|
|
@@ -1,9 +1,13 @@
|
|
|
1
|
-
import fs from "node:fs";
|
|
1
|
+
import fs, {} from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import {
|
|
3
|
+
import { normalizeMaxBytes } from "./byte-budget.js";
|
|
4
|
+
import { syncQueueDirectoryCreation } from "./json-durable-queue-directory.js";
|
|
5
|
+
import { acknowledgeDurableQueueEntry, claimDurableQueueEntry, completeDeliveredQueueEntry, moveDurableQueueEntryToFailed, } from "./json-durable-queue-ownership.js";
|
|
4
6
|
import { stringifyJsonDocument } from "./json-stringify.js";
|
|
7
|
+
import { resolveReadOpenFlags } from "./read-open-flags.js";
|
|
5
8
|
import { replaceFileAtomic } from "./replace-file.js";
|
|
6
9
|
import { assertSafePathSegment } from "./safe-path-segment.js";
|
|
10
|
+
import { inspectFileIdentity } from "./strict-file-identity.js";
|
|
7
11
|
export const DEFAULT_JSON_DURABLE_QUEUE_ENTRY_MAX_BYTES = 16 * 1024 * 1024;
|
|
8
12
|
function getErrnoCode(error) {
|
|
9
13
|
return error && typeof error === "object" && "code" in error
|
|
@@ -46,6 +50,7 @@ export function resolveJsonDurableQueueEntryPaths(queueDir, id) {
|
|
|
46
50
|
return {
|
|
47
51
|
jsonPath: path.join(queueDir, `${id}.json`),
|
|
48
52
|
deliveredPath: path.join(queueDir, `${id}.delivered`),
|
|
53
|
+
processingPath: path.join(queueDir, `${id}.processing`),
|
|
49
54
|
};
|
|
50
55
|
}
|
|
51
56
|
export async function ensureJsonDurableQueueDirs(params) {
|
|
@@ -61,6 +66,7 @@ async function ensureJsonDurableQueueDir(dir, validationRoot) {
|
|
|
61
66
|
await fs.promises.mkdir(dir, { recursive: true, mode: 0o700 });
|
|
62
67
|
await assertNoSymlinkDirectorySegments(root, dir, false);
|
|
63
68
|
await chmodQueueDirectory(dir);
|
|
69
|
+
await syncQueueDirectoryCreation(dir, root.path);
|
|
64
70
|
}
|
|
65
71
|
async function assertJsonDurableQueueDir(dir, validationRoot) {
|
|
66
72
|
const root = validationRoot
|
|
@@ -208,35 +214,55 @@ export async function writeJsonDurableQueueEntry(params) {
|
|
|
208
214
|
content: stringifyJsonDocument(params.entry, null, 2),
|
|
209
215
|
mode: 0o600,
|
|
210
216
|
tempPrefix: params.tempPrefix,
|
|
217
|
+
syncTempFile: true,
|
|
218
|
+
syncParentDir: true,
|
|
211
219
|
});
|
|
212
220
|
}
|
|
213
|
-
async function
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
221
|
+
async function replaceJsonDurableQueueEntry(params) {
|
|
222
|
+
await replaceFileAtomic({
|
|
223
|
+
filePath: params.filePath,
|
|
224
|
+
content: stringifyJsonDocument(params.entry, null, 2),
|
|
225
|
+
mode: 0o600,
|
|
226
|
+
tempPrefix: params.tempPrefix,
|
|
227
|
+
syncTempFile: true,
|
|
228
|
+
syncParentDir: true,
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
async function inspectQueueEntry(inspect, maxBytes, expected) {
|
|
232
|
+
let inspectionFailed = false;
|
|
233
|
+
try {
|
|
234
|
+
return await inspectFileIdentity(async () => {
|
|
235
|
+
try {
|
|
236
|
+
const stat = await inspect();
|
|
237
|
+
if (stat.isSymbolicLink() || !stat.isFile()) {
|
|
238
|
+
throw new Error("queue entry is not a regular file");
|
|
239
|
+
}
|
|
240
|
+
if (stat.nlink > 1n)
|
|
241
|
+
throw new Error("queue entry hardlinks are not allowed");
|
|
242
|
+
if (stat.size > maxBytes)
|
|
243
|
+
throw new Error(`queue entry exceeds ${maxBytes} bytes`);
|
|
244
|
+
return stat;
|
|
245
|
+
}
|
|
246
|
+
catch (error) {
|
|
247
|
+
inspectionFailed = true;
|
|
248
|
+
throw error;
|
|
249
|
+
}
|
|
250
|
+
}, expected);
|
|
220
251
|
}
|
|
221
|
-
|
|
222
|
-
|
|
252
|
+
catch (error) {
|
|
253
|
+
// Translate only the identity helper's rejection, never inspection errors.
|
|
254
|
+
if (inspectionFailed)
|
|
255
|
+
throw error;
|
|
256
|
+
throw new Error("queue entry changed during read", { cause: error });
|
|
223
257
|
}
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
const
|
|
258
|
+
}
|
|
259
|
+
async function readBoundedUtf8File(params) {
|
|
260
|
+
const inspectPath = () => fs.promises.lstat(params.filePath, { bigint: true });
|
|
261
|
+
const initialStat = await inspectQueueEntry(inspectPath, params.maxBytes);
|
|
262
|
+
const handle = await fs.promises.open(params.filePath, resolveReadOpenFlags());
|
|
228
263
|
try {
|
|
229
|
-
const openedStat = await handle.stat();
|
|
230
|
-
|
|
231
|
-
if (!openedStat.isFile() ||
|
|
232
|
-
pathStat.isSymbolicLink() ||
|
|
233
|
-
!pathStat.isFile() ||
|
|
234
|
-
openedStat.nlink > 1 ||
|
|
235
|
-
pathStat.nlink > 1 ||
|
|
236
|
-
!sameFileIdentity(initialStat, openedStat) ||
|
|
237
|
-
!sameFileIdentity(pathStat, openedStat)) {
|
|
238
|
-
throw new Error("queue entry changed during read");
|
|
239
|
-
}
|
|
264
|
+
const openedStat = await inspectQueueEntry(() => handle.stat({ bigint: true }), params.maxBytes, initialStat);
|
|
265
|
+
await inspectQueueEntry(inspectPath, params.maxBytes, openedStat);
|
|
240
266
|
const chunks = [];
|
|
241
267
|
const scratch = Buffer.allocUnsafe(Math.min(64 * 1024, params.maxBytes + 1));
|
|
242
268
|
let total = 0;
|
|
@@ -259,35 +285,26 @@ async function readBoundedUtf8File(params) {
|
|
|
259
285
|
export async function readJsonDurableQueueEntry(filePath, options = {}) {
|
|
260
286
|
return JSON.parse(await readBoundedUtf8File({
|
|
261
287
|
filePath,
|
|
262
|
-
maxBytes: options.maxBytes
|
|
288
|
+
maxBytes: normalizeMaxBytes(options.maxBytes, {
|
|
289
|
+
defaultValue: DEFAULT_JSON_DURABLE_QUEUE_ENTRY_MAX_BYTES,
|
|
290
|
+
}),
|
|
263
291
|
}));
|
|
264
292
|
}
|
|
265
293
|
export async function ackJsonDurableQueueEntry(paths) {
|
|
266
|
-
|
|
267
|
-
await fs.promises.rename(paths.jsonPath, paths.deliveredPath);
|
|
268
|
-
}
|
|
269
|
-
catch (error) {
|
|
270
|
-
if (getErrnoCode(error) === "ENOENT") {
|
|
271
|
-
await unlinkBestEffort(paths.deliveredPath);
|
|
272
|
-
return;
|
|
273
|
-
}
|
|
274
|
-
throw error;
|
|
275
|
-
}
|
|
276
|
-
await unlinkBestEffort(paths.deliveredPath);
|
|
294
|
+
await acknowledgeDurableQueueEntry(paths);
|
|
277
295
|
}
|
|
278
296
|
export async function loadJsonDurableQueueEntry(params) {
|
|
279
297
|
try {
|
|
280
|
-
const
|
|
281
|
-
if (!
|
|
298
|
+
const claimedPath = await claimDurableQueueEntry(params.paths);
|
|
299
|
+
if (!claimedPath)
|
|
282
300
|
return null;
|
|
283
|
-
|
|
284
|
-
const raw = await readJsonDurableQueueEntry(params.paths.jsonPath, {
|
|
301
|
+
const raw = await readJsonDurableQueueEntry(claimedPath, {
|
|
285
302
|
maxBytes: params.maxBytes,
|
|
286
303
|
});
|
|
287
304
|
const result = params.read ? await params.read(raw, params.paths.jsonPath) : { entry: raw };
|
|
288
305
|
if (result.migrated) {
|
|
289
|
-
await
|
|
290
|
-
filePath:
|
|
306
|
+
await replaceJsonDurableQueueEntry({
|
|
307
|
+
filePath: claimedPath,
|
|
291
308
|
entry: result.entry,
|
|
292
309
|
tempPrefix: params.tempPrefix,
|
|
293
310
|
});
|
|
@@ -315,28 +332,41 @@ export async function loadPendingJsonDurableQueueEntries(options) {
|
|
|
315
332
|
const now = Date.now();
|
|
316
333
|
for (const file of files) {
|
|
317
334
|
if (file.endsWith(".delivered")) {
|
|
318
|
-
|
|
335
|
+
const id = file.slice(0, -".delivered".length);
|
|
336
|
+
await completeDeliveredQueueEntry(resolveJsonDurableQueueEntryPaths(options.queueDir, id));
|
|
319
337
|
}
|
|
320
338
|
else if (options.cleanupTmpMaxAgeMs !== undefined && file.endsWith(".tmp")) {
|
|
321
339
|
await unlinkStaleTmpBestEffort(path.join(options.queueDir, file), now, options.cleanupTmpMaxAgeMs);
|
|
322
340
|
}
|
|
323
341
|
}
|
|
324
|
-
const
|
|
342
|
+
const ids = [];
|
|
343
|
+
const seenIds = new Set();
|
|
325
344
|
for (const file of files) {
|
|
326
|
-
|
|
345
|
+
const suffix = file.endsWith(".processing")
|
|
346
|
+
? ".processing"
|
|
347
|
+
: file.endsWith(".json")
|
|
348
|
+
? ".json"
|
|
349
|
+
: null;
|
|
350
|
+
if (!suffix)
|
|
327
351
|
continue;
|
|
328
|
-
|
|
329
|
-
|
|
352
|
+
const id = file.slice(0, -suffix.length);
|
|
353
|
+
if (seenIds.has(id))
|
|
354
|
+
continue;
|
|
355
|
+
seenIds.add(id);
|
|
356
|
+
ids.push(id);
|
|
357
|
+
}
|
|
358
|
+
const entries = [];
|
|
359
|
+
for (const id of ids) {
|
|
330
360
|
try {
|
|
331
|
-
const
|
|
332
|
-
|
|
361
|
+
const paths = resolveJsonDurableQueueEntryPaths(options.queueDir, id);
|
|
362
|
+
const claimedPath = await claimDurableQueueEntry(paths);
|
|
363
|
+
if (!claimedPath)
|
|
333
364
|
continue;
|
|
334
|
-
}
|
|
335
|
-
const
|
|
336
|
-
const result = options.read ? await options.read(raw, filePath) : { entry: raw };
|
|
365
|
+
const raw = await readJsonDurableQueueEntry(claimedPath, { maxBytes: options.maxBytes });
|
|
366
|
+
const result = options.read ? await options.read(raw, paths.jsonPath) : { entry: raw };
|
|
337
367
|
if (result.migrated) {
|
|
338
|
-
await
|
|
339
|
-
filePath,
|
|
368
|
+
await replaceJsonDurableQueueEntry({
|
|
369
|
+
filePath: claimedPath,
|
|
340
370
|
entry: result.entry,
|
|
341
371
|
tempPrefix: options.tempPrefix,
|
|
342
372
|
});
|
|
@@ -354,5 +384,8 @@ export async function moveJsonDurableQueueEntryToFailed(params) {
|
|
|
354
384
|
const roots = await queueValidationRoots(params.queueDir, params.failedDir);
|
|
355
385
|
await assertJsonDurableQueueDir(params.queueDir, roots.queueRoot);
|
|
356
386
|
await ensureJsonDurableQueueDir(params.failedDir, roots.failedRoot);
|
|
357
|
-
await
|
|
387
|
+
await moveDurableQueueEntryToFailed({
|
|
388
|
+
paths: resolveJsonDurableQueueEntryPaths(params.queueDir, params.id),
|
|
389
|
+
failedPath: path.join(params.failedDir, `${params.id}.json`),
|
|
390
|
+
});
|
|
358
391
|
}
|
package/dist/json.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"json.d.ts","sourceRoot":"","sources":["../src/json.ts"],"names":[],"mappings":"AACA,OAAO,MAAM,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"json.d.ts","sourceRoot":"","sources":["../src/json.ts"],"names":[],"mappings":"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"}
|