@effect-vfs/core 0.7.0 → 0.8.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/README.md +15 -238
- package/dist/LiveVolume.d.ts.map +1 -1
- package/dist/LiveVolume.js +9 -9
- package/dist/Search.d.ts +730 -0
- package/dist/Search.d.ts.map +1 -0
- package/dist/Search.js +568 -0
- package/dist/SnapshotDelta.d.ts +234 -0
- package/dist/SnapshotDelta.d.ts.map +1 -1
- package/dist/SnapshotDelta.js +107 -0
- package/dist/VirtualFileSystem.d.ts +53 -1
- package/dist/VirtualFileSystem.d.ts.map +1 -1
- package/dist/VirtualFileSystem.js +59 -1
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -0
- package/dist/internal/canonicalBase64.js +3 -3
- package/dist/internal/fixture.js +3 -3
- package/dist/internal/glob.d.ts +66 -0
- package/dist/internal/glob.d.ts.map +1 -0
- package/dist/internal/glob.js +248 -0
- package/dist/internal/image.d.ts +7 -1
- package/dist/internal/image.d.ts.map +1 -1
- package/dist/internal/image.js +9 -7
- package/dist/internal/liveImage.d.ts +1 -1
- package/dist/internal/liveImage.d.ts.map +1 -1
- package/dist/internal/liveImage.js +2 -10
- package/dist/internal/overlayDiff.js +2 -2
- package/dist/internal/path.d.ts.map +1 -1
- package/dist/internal/path.js +3 -3
- package/dist/internal/resolution.d.ts +135 -0
- package/dist/internal/resolution.d.ts.map +1 -0
- package/dist/internal/resolution.js +277 -0
- package/dist/internal/search.d.ts +24 -0
- package/dist/internal/search.d.ts.map +1 -0
- package/dist/internal/search.js +78 -0
- package/dist/internal/searchContent.d.ts +77 -0
- package/dist/internal/searchContent.d.ts.map +1 -0
- package/dist/internal/searchContent.js +223 -0
- package/dist/internal/searchModel.d.ts +83 -0
- package/dist/internal/searchModel.d.ts.map +1 -0
- package/dist/internal/searchModel.js +23 -0
- package/dist/internal/snapshotDelta.d.ts +470 -1
- package/dist/internal/snapshotDelta.d.ts.map +1 -1
- package/dist/internal/snapshotDelta.js +60 -26
- package/dist/internal/snapshotMerge.d.ts +54 -0
- package/dist/internal/snapshotMerge.d.ts.map +1 -0
- package/dist/internal/snapshotMerge.js +959 -0
- package/dist/internal/tokenRegistry.d.ts +77 -0
- package/dist/internal/tokenRegistry.d.ts.map +1 -0
- package/dist/internal/tokenRegistry.js +128 -0
- package/dist/internal/tree.js +2 -2
- package/dist/internal/virtualFileSystem.d.ts +32 -17
- package/dist/internal/virtualFileSystem.d.ts.map +1 -1
- package/dist/internal/virtualFileSystem.js +640 -993
- package/dist/internal/volumeLimits.d.ts +53 -0
- package/dist/internal/volumeLimits.d.ts.map +1 -0
- package/dist/internal/volumeLimits.js +63 -0
- package/dist/internal/volumeState.d.ts +2 -0
- package/dist/internal/volumeState.d.ts.map +1 -1
- package/dist/internal/volumeState.js +9 -0
- package/package.json +10 -6
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as ByteSize from "effect/ByteSize";
|
|
2
2
|
import * as Effect from "effect/Effect";
|
|
3
|
-
import * as
|
|
3
|
+
import * as Hex from "effect/encoding/Hex";
|
|
4
4
|
import * as Match from "effect/Match";
|
|
5
5
|
import * as Order from "effect/Order";
|
|
6
6
|
import * as Result from "effect/Result";
|
|
@@ -18,19 +18,24 @@ import { isAttachedBytes, isNameBytes, joinPath, nameBytes, NUL_BYTE, ROOT_PATH,
|
|
|
18
18
|
import * as SnapshotDeltaModel from "./snapshotDeltaModel.js";
|
|
19
19
|
import { UNCHECKED } from "./tree.js";
|
|
20
20
|
import { byEntryName, getNode, Ino, MAX_INO, payloadOf, ROOT_INO, storedMetadata } from "./volumeState.js";
|
|
21
|
-
|
|
22
|
-
const
|
|
21
|
+
/** @internal */
|
|
22
|
+
export const FORMAT = "effect-vfs-delta";
|
|
23
|
+
/** @internal */
|
|
24
|
+
export const OPERATION = "snapshotDelta";
|
|
23
25
|
const RULE_CHECK = "@effect-vfs/core/deltaRuleCheck";
|
|
24
26
|
const Path = CanonicalBase64.Encoded;
|
|
25
27
|
// Unchanged payloads come from the base; a new hard link points to the group's first changed path.
|
|
26
|
-
|
|
27
|
-
const
|
|
28
|
+
/** @internal */
|
|
29
|
+
export const InlineContent = Schema.TaggedStruct("Inline", { bytes: CanonicalBase64.Encoded });
|
|
30
|
+
/** @internal */
|
|
31
|
+
export const DeltaNode = Schema.TaggedUnion({
|
|
28
32
|
directory: { metadata: StoredMetadata },
|
|
29
33
|
file: { metadata: StoredMetadata, content: Schema.optionalKey(InlineContent) },
|
|
30
34
|
symlink: { metadata: StoredMetadata, target: Schema.optionalKey(CanonicalBase64.Encoded) },
|
|
31
35
|
link: { to: Path }
|
|
32
36
|
});
|
|
33
|
-
|
|
37
|
+
/** @internal */
|
|
38
|
+
export const Change = Schema.TaggedUnion({
|
|
34
39
|
Added: { path: Path, kind: SnapshotNodeKind, node: DeltaNode },
|
|
35
40
|
Removed: { path: Path, kind: SnapshotNodeKind },
|
|
36
41
|
Updated: {
|
|
@@ -41,7 +46,8 @@ const Change = Schema.TaggedUnion({
|
|
|
41
46
|
node: DeltaNode
|
|
42
47
|
}
|
|
43
48
|
});
|
|
44
|
-
|
|
49
|
+
/** @internal */
|
|
50
|
+
export const Document = Schema.Struct({
|
|
45
51
|
format: Schema.Literal(FORMAT),
|
|
46
52
|
version: Schema.Literal(1),
|
|
47
53
|
base: CanonicalBase64.Encoded,
|
|
@@ -52,9 +58,12 @@ const VersionProbe = Schema.Struct({
|
|
|
52
58
|
format: Schema.Literal(FORMAT),
|
|
53
59
|
version: Schema.Unknown
|
|
54
60
|
});
|
|
55
|
-
|
|
56
|
-
const
|
|
57
|
-
|
|
61
|
+
/** @internal */
|
|
62
|
+
export const differenceOrder = SnapshotDifference.literals;
|
|
63
|
+
/** @internal */
|
|
64
|
+
export const timestampFields = new Set(["atimeNs", "mtimeNs", "ctimeNs", "birthtimeNs"]);
|
|
65
|
+
/** @internal */
|
|
66
|
+
export const key = Hex.encode;
|
|
58
67
|
const validPath = (path) => {
|
|
59
68
|
if (path[0] !== SLASH_BYTE)
|
|
60
69
|
return false;
|
|
@@ -68,7 +77,8 @@ const validPath = (path) => {
|
|
|
68
77
|
}
|
|
69
78
|
return true;
|
|
70
79
|
};
|
|
71
|
-
|
|
80
|
+
/** @internal */
|
|
81
|
+
export const splitNames = (path) => {
|
|
72
82
|
const names = [];
|
|
73
83
|
if (path.length === 1)
|
|
74
84
|
return names;
|
|
@@ -81,12 +91,16 @@ const splitNames = (path) => {
|
|
|
81
91
|
}
|
|
82
92
|
return names;
|
|
83
93
|
};
|
|
84
|
-
|
|
85
|
-
const
|
|
86
|
-
|
|
94
|
+
/** @internal */
|
|
95
|
+
export const kindOf = (change) => Change.guards.Updated(change) ? change.afterKind : change.kind;
|
|
96
|
+
/** @internal */
|
|
97
|
+
export const payloadDifference = (kind) => (kind === "file" ? "content" : "target");
|
|
98
|
+
/** @internal */
|
|
99
|
+
export const carriesPayload = (change) => Change.guards.Added(change) ||
|
|
87
100
|
(Change.guards.Updated(change) &&
|
|
88
101
|
(change.differences.includes("kind") || change.differences.includes(payloadDifference(change.afterKind))));
|
|
89
|
-
|
|
102
|
+
/** @internal */
|
|
103
|
+
export const carriedPayload = (node) => DeltaNode.guards.file(node) ? node.content?.bytes : DeltaNode.guards.symlink(node) ? node.target : undefined;
|
|
90
104
|
const changesIssue = (changes) => {
|
|
91
105
|
const at = (path, issue) => ({ path: ["changes", ...path], issue });
|
|
92
106
|
const heads = new Map();
|
|
@@ -168,7 +182,8 @@ const decodeFailure = (error) => {
|
|
|
168
182
|
});
|
|
169
183
|
};
|
|
170
184
|
const exceeds = (value, limit) => ByteSize.isGreaterThan(ByteSize.bytes(value), limit);
|
|
171
|
-
|
|
185
|
+
/** @internal */
|
|
186
|
+
export const validate = (document, limits) => {
|
|
172
187
|
const fail = (code, field) => Result.fail(imageFailure(OPERATION, code, { field }));
|
|
173
188
|
if (document.changes.length > limits.records)
|
|
174
189
|
return fail("LimitExceeded", "deltaRecords");
|
|
@@ -203,14 +218,16 @@ const validate = (document, limits) => {
|
|
|
203
218
|
return fail("LimitExceeded", "outputBytes");
|
|
204
219
|
return Result.void;
|
|
205
220
|
};
|
|
206
|
-
|
|
221
|
+
/** @internal */
|
|
222
|
+
export const baseBudget = (limits) => ({
|
|
207
223
|
operation: OPERATION,
|
|
208
224
|
records: limits.baseRecords,
|
|
209
225
|
recordsField: "baseRecords",
|
|
210
226
|
entries: limits.entries,
|
|
211
227
|
identityBytes: limits.identityBytes
|
|
212
228
|
});
|
|
213
|
-
|
|
229
|
+
/** @internal */
|
|
230
|
+
export const targetBudget = (limits) => ({
|
|
214
231
|
operation: OPERATION,
|
|
215
232
|
records: limits.targetRecords,
|
|
216
233
|
recordsField: "targetRecords",
|
|
@@ -219,7 +236,8 @@ const targetBudget = (limits) => ({
|
|
|
219
236
|
payloadBytes: { limit: limits.outputBytes, field: "outputBytes" }
|
|
220
237
|
});
|
|
221
238
|
// Check target bounds first so applying and diffing report the same limit.
|
|
222
|
-
|
|
239
|
+
/** @internal */
|
|
240
|
+
export const outputIssue = (limits, output) => {
|
|
223
241
|
const fail = (field) => Result.fail(imageFailure(OPERATION, "LimitExceeded", { field }));
|
|
224
242
|
if (output.records > limits.targetRecords)
|
|
225
243
|
return fail("targetRecords");
|
|
@@ -234,7 +252,8 @@ const outputIssue = (limits, output) => {
|
|
|
234
252
|
return Result.void;
|
|
235
253
|
};
|
|
236
254
|
const groupKey = (paths) => paths.map(key).join("/");
|
|
237
|
-
|
|
255
|
+
/** @internal */
|
|
256
|
+
export const side = (value, identity) => {
|
|
238
257
|
const groupOf = new Map();
|
|
239
258
|
const firstOf = new Map();
|
|
240
259
|
for (const [ino, paths] of identity.groups) {
|
|
@@ -246,7 +265,8 @@ const side = (value, identity) => {
|
|
|
246
265
|
return { value, identity, groupOf, firstOf };
|
|
247
266
|
};
|
|
248
267
|
const groupAt = (side, pathKey) => side.groupOf.get(pathKey) ?? pathKey;
|
|
249
|
-
|
|
268
|
+
/** @internal */
|
|
269
|
+
export const differencesOf = (before, after, hardLinks) => differenceOrder.filter((field) => {
|
|
250
270
|
if (field === "kind")
|
|
251
271
|
return before.kind !== after.kind;
|
|
252
272
|
if (field === "content" || field === "target") {
|
|
@@ -267,7 +287,8 @@ const deltaNode = (target, path, node, withPayload) => {
|
|
|
267
287
|
? { metadata, content: InlineContent.make({ bytes: CanonicalBase64.encode(file.data) }, UNCHECKED) }
|
|
268
288
|
: { metadata }, UNCHECKED)), Match.discriminator("kind")("symlink", (symlink) => DeltaNode.cases.symlink.make(withPayload ? { metadata, target: CanonicalBase64.encode(symlink.target) } : { metadata }, UNCHECKED)), Match.exhaustive);
|
|
269
289
|
};
|
|
270
|
-
|
|
290
|
+
/** @internal */
|
|
291
|
+
export const lookup = (get, entriesOf, path) => {
|
|
271
292
|
let parent = ROOT_INO;
|
|
272
293
|
let node = get(ROOT_INO);
|
|
273
294
|
let name = "";
|
|
@@ -283,7 +304,8 @@ const lookup = (get, entriesOf, path) => {
|
|
|
283
304
|
}
|
|
284
305
|
return node === undefined ? undefined : { parent, name, node };
|
|
285
306
|
};
|
|
286
|
-
|
|
307
|
+
/** @internal */
|
|
308
|
+
export const valueEntries = (directory) => directory.entries;
|
|
287
309
|
// Equal digests skip a subtree; hard-link groups still need a separate path comparison.
|
|
288
310
|
const compare = (base, target) => {
|
|
289
311
|
const changes = [];
|
|
@@ -361,7 +383,8 @@ const compare = (base, target) => {
|
|
|
361
383
|
}
|
|
362
384
|
return changes.sort(Order.mapInput(bytesOrder, (entry) => entry.path)).map(({ change }) => change);
|
|
363
385
|
};
|
|
364
|
-
|
|
386
|
+
/** @internal */
|
|
387
|
+
export const getDocument = (delta) => Effect.suspend(() => {
|
|
365
388
|
const value = SnapshotDeltaModel.value(delta);
|
|
366
389
|
return value !== undefined && Schema.is(Document)(value)
|
|
367
390
|
? Effect.succeed(value)
|
|
@@ -369,7 +392,8 @@ const getDocument = (delta) => Effect.suspend(() => {
|
|
|
369
392
|
});
|
|
370
393
|
// Detach deepest paths first, then attach from the root. Check each claimed change against the base: a matching
|
|
371
394
|
// target digest alone cannot detect unreachable children or forged hard-link groups.
|
|
372
|
-
|
|
395
|
+
/** @internal */
|
|
396
|
+
export const fold = (base, identity, document) => {
|
|
373
397
|
const fail = (index, field) => Result.fail(imageFailure(OPERATION, "InvalidStructure", {
|
|
374
398
|
field: field === undefined ? `changes.${index}` : `changes.${index}.${field}`
|
|
375
399
|
}));
|
|
@@ -587,7 +611,8 @@ const fold = (base, identity, document) => {
|
|
|
587
611
|
return Result.succeed({ value, dirty, removed, groups, records, entries, payloadBytes, written });
|
|
588
612
|
};
|
|
589
613
|
// Reuse base digests for untouched nodes, but charge the meter for the whole target.
|
|
590
|
-
|
|
614
|
+
/** @internal */
|
|
615
|
+
export const foldedIdentity = Effect.fnUntraced(function* (base, folded, identity, limits) {
|
|
591
616
|
const digests = new Map();
|
|
592
617
|
const measure = Merkle.meter(targetBudget(limits));
|
|
593
618
|
let inherited = identity.nodeBytes;
|
|
@@ -626,6 +651,15 @@ const verify = Effect.fnUntraced(function* (base, document, limits) {
|
|
|
626
651
|
yield* Effect.fromResult(validate(document, limits));
|
|
627
652
|
const value = yield* Image.valueOf(base);
|
|
628
653
|
const identity = yield* Merkle.identify(value, baseBudget(limits));
|
|
654
|
+
return yield* verifyValidated(value, identity, document, limits);
|
|
655
|
+
});
|
|
656
|
+
// Verify a document against a base whose identity is already known; returns the folded target value.
|
|
657
|
+
/** @internal */
|
|
658
|
+
export const verifyWith = Effect.fnUntraced(function* (value, identity, document, limits) {
|
|
659
|
+
yield* Effect.fromResult(validate(document, limits));
|
|
660
|
+
return yield* verifyValidated(value, identity, document, limits);
|
|
661
|
+
});
|
|
662
|
+
const verifyValidated = Effect.fnUntraced(function* (value, identity, document, limits) {
|
|
629
663
|
if (!sameBytes(identity.digest, CanonicalBase64.toBytes(document.base))) {
|
|
630
664
|
return yield* new VfsError({ code: "BaseMismatch", operation: OPERATION });
|
|
631
665
|
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import * as Effect from "effect/Effect";
|
|
2
|
+
import type { Snapshot } from "../Snapshot.js";
|
|
3
|
+
import { type SnapshotDelta } from "../SnapshotDelta.js";
|
|
4
|
+
/** @internal */
|
|
5
|
+
export declare const mergeSnapshotDeltas: (base: Snapshot, ours: SnapshotDelta, theirs: SnapshotDelta, resolutions: readonly {
|
|
6
|
+
readonly path: import("../BytePath.js").BytePath;
|
|
7
|
+
readonly take: "base" | "ours" | "theirs";
|
|
8
|
+
}[], limits: {
|
|
9
|
+
readonly encodedBytes: import("effect/ByteSize").ByteSize;
|
|
10
|
+
readonly records: number;
|
|
11
|
+
readonly entries: number;
|
|
12
|
+
readonly decodedBytes: import("effect/ByteSize").ByteSize;
|
|
13
|
+
readonly identityBytes: import("effect/ByteSize").ByteSize;
|
|
14
|
+
readonly baseRecords: number;
|
|
15
|
+
readonly targetRecords: number;
|
|
16
|
+
readonly outputRecords: number;
|
|
17
|
+
readonly outputBytes: import("effect/ByteSize").ByteSize;
|
|
18
|
+
readonly inheritedRecords: number;
|
|
19
|
+
}) => Effect.Effect<{
|
|
20
|
+
delta: SnapshotDelta;
|
|
21
|
+
conflicts: {
|
|
22
|
+
readonly path: import("../BytePath.js").BytePath;
|
|
23
|
+
readonly reason: "BothAddedDifferent" | "BothChanged" | "ChangedRemoved" | "HardLinkGroupDiverged" | "KindDiverged" | "ParentRemoved";
|
|
24
|
+
readonly ours: {
|
|
25
|
+
readonly _tag: "Added";
|
|
26
|
+
readonly kind: "directory" | "file" | "symlink";
|
|
27
|
+
} | {
|
|
28
|
+
readonly _tag: "Removed";
|
|
29
|
+
readonly kind: "directory" | "file" | "symlink";
|
|
30
|
+
} | {
|
|
31
|
+
readonly _tag: "Unchanged";
|
|
32
|
+
} | {
|
|
33
|
+
readonly _tag: "Updated";
|
|
34
|
+
readonly beforeKind: "directory" | "file" | "symlink";
|
|
35
|
+
readonly afterKind: "directory" | "file" | "symlink";
|
|
36
|
+
readonly differences: readonly ("atimeNs" | "birthtimeNs" | "content" | "ctimeNs" | "gid" | "hardLinks" | "kind" | "mode" | "mtimeNs" | "target" | "uid")[];
|
|
37
|
+
};
|
|
38
|
+
readonly theirs: {
|
|
39
|
+
readonly _tag: "Added";
|
|
40
|
+
readonly kind: "directory" | "file" | "symlink";
|
|
41
|
+
} | {
|
|
42
|
+
readonly _tag: "Removed";
|
|
43
|
+
readonly kind: "directory" | "file" | "symlink";
|
|
44
|
+
} | {
|
|
45
|
+
readonly _tag: "Unchanged";
|
|
46
|
+
} | {
|
|
47
|
+
readonly _tag: "Updated";
|
|
48
|
+
readonly beforeKind: "directory" | "file" | "symlink";
|
|
49
|
+
readonly afterKind: "directory" | "file" | "symlink";
|
|
50
|
+
readonly differences: readonly ("atimeNs" | "birthtimeNs" | "content" | "ctimeNs" | "gid" | "hardLinks" | "kind" | "mode" | "mtimeNs" | "target" | "uid")[];
|
|
51
|
+
};
|
|
52
|
+
}[];
|
|
53
|
+
}, import("effect/PlatformError").PlatformError | import("./errors.js").VfsError, import("effect/Crypto").Crypto>;
|
|
54
|
+
//# sourceMappingURL=snapshotMerge.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"snapshotMerge.d.ts","sourceRoot":"","sources":["../../src/internal/snapshotMerge.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AAGvC,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AAC9C,OAAO,EAML,KAAK,aAAa,EAGnB,MAAM,qBAAqB,CAAA;AA8tC5B,gBAAgB;AAChB,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iHAkD9B,CAAA"}
|