@effect-vfs/core 0.7.1 → 0.9.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.
Files changed (79) hide show
  1. package/README.md +21 -235
  2. package/dist/Caller.d.ts +2 -0
  3. package/dist/Caller.d.ts.map +1 -1
  4. package/dist/Caller.js +3 -1
  5. package/dist/LiveVolume.d.ts +69 -1
  6. package/dist/LiveVolume.d.ts.map +1 -1
  7. package/dist/LiveVolume.js +89 -12
  8. package/dist/Search.d.ts +730 -0
  9. package/dist/Search.d.ts.map +1 -0
  10. package/dist/Search.js +576 -0
  11. package/dist/SnapshotDelta.d.ts +234 -0
  12. package/dist/SnapshotDelta.d.ts.map +1 -1
  13. package/dist/SnapshotDelta.js +107 -0
  14. package/dist/VfsError.d.ts +3 -1
  15. package/dist/VfsError.d.ts.map +1 -1
  16. package/dist/VfsError.js +3 -1
  17. package/dist/VirtualFileSystem.d.ts +78 -2
  18. package/dist/VirtualFileSystem.d.ts.map +1 -1
  19. package/dist/VirtualFileSystem.js +68 -4
  20. package/dist/Watch.d.ts +18 -0
  21. package/dist/Watch.d.ts.map +1 -1
  22. package/dist/Watch.js +12 -0
  23. package/dist/index.d.ts +7 -0
  24. package/dist/index.d.ts.map +1 -1
  25. package/dist/index.js +7 -0
  26. package/dist/internal/callerWatch.d.ts +32 -0
  27. package/dist/internal/callerWatch.d.ts.map +1 -0
  28. package/dist/internal/callerWatch.js +184 -0
  29. package/dist/internal/confinement.d.ts +16 -0
  30. package/dist/internal/confinement.d.ts.map +1 -0
  31. package/dist/internal/confinement.js +79 -0
  32. package/dist/internal/directoryEntries.d.ts +6 -0
  33. package/dist/internal/directoryEntries.d.ts.map +1 -0
  34. package/dist/internal/directoryEntries.js +79 -0
  35. package/dist/internal/fixture.js +4 -4
  36. package/dist/internal/glob.d.ts +66 -0
  37. package/dist/internal/glob.d.ts.map +1 -0
  38. package/dist/internal/glob.js +248 -0
  39. package/dist/internal/image.d.ts +7 -1
  40. package/dist/internal/image.d.ts.map +1 -1
  41. package/dist/internal/image.js +9 -7
  42. package/dist/internal/liveImage.d.ts +1 -1
  43. package/dist/internal/liveImage.d.ts.map +1 -1
  44. package/dist/internal/liveImage.js +2 -10
  45. package/dist/internal/path.js +2 -2
  46. package/dist/internal/resolution.d.ts +139 -0
  47. package/dist/internal/resolution.d.ts.map +1 -0
  48. package/dist/internal/resolution.js +332 -0
  49. package/dist/internal/search.d.ts +24 -0
  50. package/dist/internal/search.d.ts.map +1 -0
  51. package/dist/internal/search.js +78 -0
  52. package/dist/internal/searchContent.d.ts +77 -0
  53. package/dist/internal/searchContent.d.ts.map +1 -0
  54. package/dist/internal/searchContent.js +223 -0
  55. package/dist/internal/searchModel.d.ts +83 -0
  56. package/dist/internal/searchModel.d.ts.map +1 -0
  57. package/dist/internal/searchModel.js +23 -0
  58. package/dist/internal/snapshotDelta.d.ts +470 -1
  59. package/dist/internal/snapshotDelta.d.ts.map +1 -1
  60. package/dist/internal/snapshotDelta.js +59 -25
  61. package/dist/internal/snapshotMerge.d.ts +54 -0
  62. package/dist/internal/snapshotMerge.d.ts.map +1 -0
  63. package/dist/internal/snapshotMerge.js +959 -0
  64. package/dist/internal/tokenRegistry.d.ts +83 -0
  65. package/dist/internal/tokenRegistry.d.ts.map +1 -0
  66. package/dist/internal/tokenRegistry.js +146 -0
  67. package/dist/internal/virtualFileSystem.d.ts +32 -17
  68. package/dist/internal/virtualFileSystem.d.ts.map +1 -1
  69. package/dist/internal/virtualFileSystem.js +503 -736
  70. package/dist/internal/volumeLimits.d.ts +53 -0
  71. package/dist/internal/volumeLimits.d.ts.map +1 -0
  72. package/dist/internal/volumeLimits.js +63 -0
  73. package/dist/internal/volumeState.d.ts +2 -0
  74. package/dist/internal/volumeState.d.ts.map +1 -1
  75. package/dist/internal/volumeState.js +9 -0
  76. package/dist/internal/watchHub.d.ts +6 -2
  77. package/dist/internal/watchHub.d.ts.map +1 -1
  78. package/dist/internal/watchHub.js +31 -6
  79. package/package.json +9 -3
@@ -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
- const FORMAT = "effect-vfs-delta";
22
- const OPERATION = "snapshotDelta";
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
- const InlineContent = Schema.TaggedStruct("Inline", { bytes: CanonicalBase64.Encoded });
27
- const DeltaNode = Schema.TaggedUnion({
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
- const Change = Schema.TaggedUnion({
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
- const Document = Schema.Struct({
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
- const differenceOrder = SnapshotDifference.literals;
56
- const timestampFields = new Set(["atimeNs", "mtimeNs", "ctimeNs", "birthtimeNs"]);
57
- const key = Hex.encode;
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
- const splitNames = (path) => {
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
- const kindOf = (change) => Change.guards.Updated(change) ? change.afterKind : change.kind;
85
- const payloadDifference = (kind) => (kind === "file" ? "content" : "target");
86
- const carriesPayload = (change) => Change.guards.Added(change) ||
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
- const carriedPayload = (node) => DeltaNode.guards.file(node) ? node.content?.bytes : DeltaNode.guards.symlink(node) ? node.target : undefined;
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
- const validate = (document, limits) => {
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
- const baseBudget = (limits) => ({
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
- const targetBudget = (limits) => ({
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
- const outputIssue = (limits, output) => {
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
- const side = (value, identity) => {
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
- const differencesOf = (before, after, hardLinks) => differenceOrder.filter((field) => {
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
- const lookup = (get, entriesOf, path) => {
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
- const valueEntries = (directory) => directory.entries;
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
- const getDocument = (delta) => Effect.suspend(() => {
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
- const fold = (base, identity, document) => {
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
- const foldedIdentity = Effect.fnUntraced(function* (base, folded, identity, limits) {
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"}