@effect-vfs/core 0.3.0 → 0.4.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 (69) hide show
  1. package/README.md +36 -149
  2. package/dist/BytePath.d.ts +34 -2
  3. package/dist/BytePath.d.ts.map +1 -1
  4. package/dist/BytePath.js +1 -1
  5. package/dist/LiveVolume.d.ts +101 -0
  6. package/dist/LiveVolume.d.ts.map +1 -0
  7. package/dist/LiveVolume.js +72 -0
  8. package/dist/Snapshot.d.ts +60 -2
  9. package/dist/Snapshot.d.ts.map +1 -1
  10. package/dist/Snapshot.js +59 -1
  11. package/dist/SnapshotDelta.d.ts +113 -3
  12. package/dist/SnapshotDelta.d.ts.map +1 -1
  13. package/dist/SnapshotDelta.js +112 -2
  14. package/dist/VirtualFileSystem.d.ts +1224 -90
  15. package/dist/VirtualFileSystem.d.ts.map +1 -1
  16. package/dist/VirtualFileSystem.js +678 -20
  17. package/dist/VirtualFileSystemError.d.ts +54 -0
  18. package/dist/VirtualFileSystemError.d.ts.map +1 -0
  19. package/dist/VirtualFileSystemError.js +49 -0
  20. package/dist/index.d.ts +15 -1
  21. package/dist/index.d.ts.map +1 -1
  22. package/dist/index.js +15 -1
  23. package/dist/internal/bytePath.d.ts.map +1 -1
  24. package/dist/internal/bytePath.js +5 -10
  25. package/dist/internal/bytes.d.ts +6 -0
  26. package/dist/internal/bytes.d.ts.map +1 -0
  27. package/dist/internal/bytes.js +17 -0
  28. package/dist/internal/canonicalBase64.d.ts +13 -5
  29. package/dist/internal/canonicalBase64.d.ts.map +1 -1
  30. package/dist/internal/canonicalBase64.js +28 -6
  31. package/dist/internal/image.d.ts +76 -90
  32. package/dist/internal/image.d.ts.map +1 -1
  33. package/dist/internal/image.js +98 -89
  34. package/dist/internal/liveImage.d.ts +286 -0
  35. package/dist/internal/liveImage.d.ts.map +1 -0
  36. package/dist/internal/liveImage.js +176 -0
  37. package/dist/internal/metadata.d.ts +28 -0
  38. package/dist/internal/metadata.d.ts.map +1 -0
  39. package/dist/internal/metadata.js +29 -0
  40. package/dist/internal/snapshotDelta.d.ts.map +1 -1
  41. package/dist/internal/snapshotDelta.js +346 -300
  42. package/dist/internal/snapshotDeltaModel.d.ts +2 -2
  43. package/dist/internal/snapshotDeltaModel.d.ts.map +1 -1
  44. package/dist/internal/snapshotDeltaModel.js +3 -1
  45. package/dist/internal/virtualFileSystem/errors.d.ts +6 -20
  46. package/dist/internal/virtualFileSystem/errors.d.ts.map +1 -1
  47. package/dist/internal/virtualFileSystem/errors.js +22 -7
  48. package/dist/internal/virtualFileSystem/fixture.d.ts +4 -1
  49. package/dist/internal/virtualFileSystem/fixture.d.ts.map +1 -1
  50. package/dist/internal/virtualFileSystem/fixture.js +80 -74
  51. package/dist/internal/virtualFileSystem/overlayContent.d.ts.map +1 -1
  52. package/dist/internal/virtualFileSystem/overlayContent.js +9 -2
  53. package/dist/internal/virtualFileSystem/overlayDiff.d.ts +2 -11
  54. package/dist/internal/virtualFileSystem/overlayDiff.d.ts.map +1 -1
  55. package/dist/internal/virtualFileSystem/overlayDiff.js +45 -71
  56. package/dist/internal/virtualFileSystem/path.d.ts +15 -14
  57. package/dist/internal/virtualFileSystem/path.d.ts.map +1 -1
  58. package/dist/internal/virtualFileSystem/path.js +57 -54
  59. package/dist/internal/virtualFileSystem/stagedState.d.ts +22 -0
  60. package/dist/internal/virtualFileSystem/stagedState.d.ts.map +1 -0
  61. package/dist/internal/virtualFileSystem/stagedState.js +62 -0
  62. package/dist/internal/virtualFileSystem/testHooks.d.ts.map +1 -1
  63. package/dist/internal/virtualFileSystem/watchHub.d.ts +3 -3
  64. package/dist/internal/virtualFileSystem/watchHub.d.ts.map +1 -1
  65. package/dist/internal/virtualFileSystem/watchHub.js +45 -24
  66. package/dist/internal/virtualFileSystem.d.ts +307 -23
  67. package/dist/internal/virtualFileSystem.d.ts.map +1 -1
  68. package/dist/internal/virtualFileSystem.js +1738 -525
  69. package/package.json +23 -5
@@ -0,0 +1,176 @@
1
+ // Private, versioned image for a live volume. Unlike a public snapshot, this
2
+ // retains inode numbers, revisions, allocator state, and open unlinked files.
3
+ import * as ByteSize from "effect/ByteSize";
4
+ import * as Effect from "effect/Effect";
5
+ import * as Result from "effect/Result";
6
+ import * as Schema from "effect/Schema";
7
+ import * as SchemaTransformation from "effect/SchemaTransformation";
8
+ import { ImageError } from "../Snapshot.js";
9
+ import { CanonicalBase64 } from "./canonicalBase64.js";
10
+ import { StoredMetadata } from "./metadata.js";
11
+ const NaturalBigInt = Schema.String.check(Schema.isPattern(/^(0|[1-9][0-9]{0,127})$/)).pipe(Schema.decodeTo(Schema.BigInt, SchemaTransformation.bigintFromString));
12
+ const Common = {
13
+ ino: NaturalBigInt,
14
+ revision: NaturalBigInt,
15
+ lineage: Schema.optionalKey(Schema.String),
16
+ metadata: Schema.Struct({
17
+ ...StoredMetadata.fields,
18
+ nlink: Schema.Natural,
19
+ size: NaturalBigInt
20
+ })
21
+ };
22
+ /** @internal */
23
+ export const Record = Schema.TaggedUnion({
24
+ directory: {
25
+ ...Common,
26
+ entries: Schema.Array(Schema.Struct({ name: CanonicalBase64.Encoded, target: NaturalBigInt }))
27
+ },
28
+ file: { ...Common, data: CanonicalBase64.Encoded },
29
+ symlink: { ...Common, target: CanonicalBase64.Encoded }
30
+ });
31
+ /** @internal */
32
+ export const Document = Schema.Struct({
33
+ format: Schema.Literal("effect-vfs-live"),
34
+ version: Schema.Literal(1),
35
+ identity: Schema.String.check(Schema.isPattern(/^[0-9a-f]{32}$/)),
36
+ root: NaturalBigInt,
37
+ nextInode: NaturalBigInt,
38
+ revisionCounter: NaturalBigInt,
39
+ entries: Schema.Natural,
40
+ usedBytes: NaturalBigInt,
41
+ limits: Schema.Struct({
42
+ maxEntries: Schema.optionalKey(Schema.Natural),
43
+ maxBytes: Schema.optionalKey(NaturalBigInt),
44
+ maxFileBytes: Schema.optionalKey(NaturalBigInt),
45
+ maxPathBytes: Schema.optionalKey(NaturalBigInt)
46
+ }),
47
+ retainedFiles: Schema.Array(NaturalBigInt),
48
+ records: Schema.Array(Record)
49
+ });
50
+ const invalid = () => new ImageError({ code: "InvalidStructure", field: "liveImage" });
51
+ const validate = Effect.fnUntraced(function* (document) {
52
+ const records = new Map();
53
+ for (const record of document.records) {
54
+ if (record.ino < 1n || records.has(record.ino) || record.revision < 1n ||
55
+ record.revision > document.revisionCounter || record.ino >= document.nextInode)
56
+ return yield* invalid();
57
+ records.set(record.ino, record);
58
+ }
59
+ const root = records.get(document.root);
60
+ if (document.root !== 1n || root?._tag !== "directory" || document.revisionCounter < 1n) {
61
+ return yield* invalid();
62
+ }
63
+ const links = new Map();
64
+ const pending = [[root, 1n]];
65
+ const seenDirectories = new Set();
66
+ const reachable = new Set();
67
+ let entries = 0;
68
+ for (let index = 0; index < pending.length; index++) {
69
+ const current = pending[index];
70
+ if (current === undefined)
71
+ return yield* invalid();
72
+ const [directory, pathBytes] = current;
73
+ if (seenDirectories.has(directory.ino))
74
+ return yield* invalid();
75
+ seenDirectories.add(directory.ino);
76
+ reachable.add(directory.ino);
77
+ const names = new Set();
78
+ for (const entry of directory.entries) {
79
+ if (names.has(entry.name) || entry.target === document.root)
80
+ return yield* invalid();
81
+ names.add(entry.name);
82
+ const name = yield* CanonicalBase64.decode(entry.name);
83
+ if (name.length < 1 || name.length > 255 || name.includes(0) || name.includes(47) ||
84
+ (name.length === 1 && name[0] === 46) ||
85
+ (name.length === 2 && name[0] === 46 && name[1] === 46))
86
+ return yield* invalid();
87
+ const childPathBytes = pathBytes + BigInt(name.length) + (pathBytes === 1n ? 0n : 1n);
88
+ if (document.limits.maxPathBytes !== undefined && childPathBytes > document.limits.maxPathBytes) {
89
+ return yield* invalid();
90
+ }
91
+ const target = records.get(entry.target);
92
+ if (target === undefined)
93
+ return yield* invalid();
94
+ entries++;
95
+ links.set(target.ino, (links.get(target.ino) ?? 0) + 1);
96
+ reachable.add(target.ino);
97
+ if (Record.guards.directory(target))
98
+ pending.push([target, childPathBytes]);
99
+ }
100
+ }
101
+ if (entries !== document.entries || links.has(root.ino))
102
+ return yield* invalid();
103
+ const retained = new Set(document.retainedFiles);
104
+ if (retained.size !== document.retainedFiles.length)
105
+ return yield* invalid();
106
+ let usedBytes = 0n;
107
+ for (const record of document.records) {
108
+ const actualLinks = links.get(record.ino) ?? 0;
109
+ if (Record.guards.directory(record)) {
110
+ const childDirectories = record.entries.filter((entry) => {
111
+ const child = records.get(entry.target);
112
+ return child !== undefined && Record.guards.directory(child);
113
+ }).length;
114
+ if (!seenDirectories.has(record.ino) || (record.ino !== root.ino && actualLinks !== 1) ||
115
+ record.metadata.nlink !== 2 + childDirectories || record.metadata.size !== 0n)
116
+ return yield* invalid();
117
+ }
118
+ else {
119
+ const payload = Record.guards.file(record) ? record.data : record.target;
120
+ const length = BigInt(CanonicalBase64.decodedLength(payload));
121
+ if (record.metadata.size !== length || record.metadata.nlink !== actualLinks)
122
+ return yield* invalid();
123
+ if (Record.guards.file(record)) {
124
+ if (document.limits.maxFileBytes !== undefined && length > document.limits.maxFileBytes) {
125
+ return yield* invalid();
126
+ }
127
+ if (actualLinks === 0 && !retained.has(record.ino))
128
+ return yield* invalid();
129
+ if (actualLinks > 0 && retained.has(record.ino))
130
+ return yield* invalid();
131
+ }
132
+ else {
133
+ if (actualLinks === 0 || retained.has(record.ino) || (yield* CanonicalBase64.decode(record.target)).includes(0)) {
134
+ return yield* invalid();
135
+ }
136
+ }
137
+ usedBytes += length;
138
+ }
139
+ if (!reachable.has(record.ino) && !retained.has(record.ino))
140
+ return yield* invalid();
141
+ }
142
+ if (document.limits.maxPathBytes !== undefined && document.limits.maxPathBytes < 1n)
143
+ return yield* invalid();
144
+ if (usedBytes !== document.usedBytes ||
145
+ (document.limits.maxEntries !== undefined && entries > document.limits.maxEntries) ||
146
+ (document.limits.maxBytes !== undefined && usedBytes > document.limits.maxBytes))
147
+ return yield* invalid();
148
+ for (const ino of retained) {
149
+ if (records.get(ino)?._tag !== "file" || reachable.has(ino))
150
+ return yield* invalid();
151
+ }
152
+ return document;
153
+ });
154
+ /** @internal */
155
+ export const encode = Effect.fnUntraced(function* (document) {
156
+ const text = yield* Schema.encodeEffect(Schema.fromJsonString(Document))(yield* validate(document)).pipe(Effect.mapError(() => new ImageError({ code: "InvalidStructure", field: "liveImage" })));
157
+ return new TextEncoder().encode(text);
158
+ });
159
+ /** @internal */
160
+ export const decode = Effect.fnUntraced(function* (bytes, maxEncodedBytes) {
161
+ if (!(bytes instanceof Uint8Array) || !(bytes.buffer instanceof ArrayBuffer)) {
162
+ return yield* new ImageError({ code: "InvalidEncoding", field: "liveImage" });
163
+ }
164
+ if (ByteSize.isGreaterThan(ByteSize.bytes(bytes.byteLength), maxEncodedBytes)) {
165
+ return yield* new ImageError({ code: "LimitExceeded", field: "encodedBytes" });
166
+ }
167
+ const text = yield* Effect.try({
168
+ try: () => new TextDecoder("utf-8", { fatal: true }).decode(new Uint8Array(bytes)),
169
+ catch: () => new ImageError({ code: "InvalidEncoding", field: "liveImage" })
170
+ });
171
+ const parsed = yield* Schema.decodeEffect(Schema.fromJsonString(Schema.Unknown))(text).pipe(Effect.mapError(() => new ImageError({ code: "InvalidEncoding", field: "liveImage" })));
172
+ const document = Schema.decodeUnknownResult(Document, { onExcessProperty: "error" })(parsed);
173
+ if (Result.isFailure(document))
174
+ return yield* new ImageError({ code: "InvalidStructure", field: "liveImage" });
175
+ return yield* validate(document.success);
176
+ });
@@ -0,0 +1,28 @@
1
+ import * as Schema from "effect/Schema";
2
+ /** @internal */
3
+ export declare const Timestamp: Schema.BigInt;
4
+ /** @internal */
5
+ export declare const TimestampFromString: Schema.decodeTo<Schema.BigInt, Schema.String, never, never>;
6
+ /** @internal */
7
+ export declare const WireStoredMetadata: Schema.Struct<{
8
+ readonly uid: Schema.Natural;
9
+ readonly gid: Schema.Natural;
10
+ readonly mode: Schema.Natural;
11
+ readonly atimeNs: Schema.String;
12
+ readonly mtimeNs: Schema.String;
13
+ readonly ctimeNs: Schema.String;
14
+ readonly birthtimeNs: Schema.String;
15
+ }>;
16
+ /** @internal */
17
+ export declare const StoredMetadata: Schema.Struct<{
18
+ readonly uid: Schema.Natural;
19
+ readonly gid: Schema.Natural;
20
+ readonly mode: Schema.Natural;
21
+ readonly atimeNs: Schema.decodeTo<Schema.BigInt, Schema.String, never, never>;
22
+ readonly mtimeNs: Schema.decodeTo<Schema.BigInt, Schema.String, never, never>;
23
+ readonly ctimeNs: Schema.decodeTo<Schema.BigInt, Schema.String, never, never>;
24
+ readonly birthtimeNs: Schema.decodeTo<Schema.BigInt, Schema.String, never, never>;
25
+ }>;
26
+ /** @internal */
27
+ export type StoredMetadata = typeof StoredMetadata.Type;
28
+ //# sourceMappingURL=metadata.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"metadata.d.ts","sourceRoot":"","sources":["../../src/internal/metadata.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AAKvC,gBAAgB;AAChB,eAAO,MAAM,SAAS,eAGrB,CAAA;AAED,gBAAgB;AAChB,eAAO,MAAM,mBAAmB,6DAE0C,CAAA;AAE1E,gBAAgB;AAChB,eAAO,MAAM,kBAAkB;;;;;;;;EAQ7B,CAAA;AAEF,gBAAgB;AAChB,eAAO,MAAM,cAAc;;;;;;;;EAQzB,CAAA;AAEF,gBAAgB;AAChB,MAAM,MAAM,cAAc,GAAG,OAAO,cAAc,CAAC,IAAI,CAAA"}
@@ -0,0 +1,29 @@
1
+ import * as Schema from "effect/Schema";
2
+ import * as SchemaTransformation from "effect/SchemaTransformation";
3
+ const MAX_TIMESTAMP = 10n ** 128n - 1n;
4
+ /** @internal */
5
+ export const Timestamp = Schema.BigInt.check(Schema.isGreaterThanOrEqualToBigInt(-MAX_TIMESTAMP), Schema.isLessThanOrEqualToBigInt(MAX_TIMESTAMP));
6
+ /** @internal */
7
+ export const TimestampFromString = Schema.String
8
+ .check(Schema.isPattern(/^-?[0-9]{1,128}$/))
9
+ .pipe(Schema.decodeTo(Timestamp, SchemaTransformation.bigintFromString));
10
+ /** @internal */
11
+ export const WireStoredMetadata = Schema.Struct({
12
+ uid: Schema.Natural,
13
+ gid: Schema.Natural,
14
+ mode: Schema.Natural.check(Schema.isLessThanOrEqualTo(0o7777)),
15
+ atimeNs: Schema.String,
16
+ mtimeNs: Schema.String,
17
+ ctimeNs: Schema.String,
18
+ birthtimeNs: Schema.String
19
+ });
20
+ /** @internal */
21
+ export const StoredMetadata = Schema.Struct({
22
+ uid: Schema.Natural,
23
+ gid: Schema.Natural,
24
+ mode: Schema.Natural.check(Schema.isLessThanOrEqualTo(0o7777)),
25
+ atimeNs: TimestampFromString,
26
+ mtimeNs: TimestampFromString,
27
+ ctimeNs: TimestampFromString,
28
+ birthtimeNs: TimestampFromString
29
+ });
@@ -1 +1 @@
1
- {"version":3,"file":"snapshotDelta.d.ts","sourceRoot":"","sources":["../../src/internal/snapshotDelta.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,QAAQ,MAAM,iBAAiB,CAAA;AAC3C,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AACvC,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AAKvC,OAAO,EAAE,UAAU,EAAE,KAAK,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AAC1D,OAAO,EAGL,KAAK,aAAa,EAClB,kBAAkB,EAInB,MAAM,qBAAqB,CAAA;AA6e5B,gBAAgB;AAChB,eAAO,MAAM,aAAa;;;;;;;;;;;4GAgDzB,CAAA;AAyBD,gBAAgB;AAChB,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;;;;;;;;;;;;;;oGAgBhC,CAAA;AAED,gBAAgB;AAChB,eAAO,MAAM,mBAAmB;;;;;;;;;;;+DAS9B,CAAA;AAEF,gBAAgB;AAChB,eAAO,MAAM,mBAAmB;;;;;;;;;;;qDAkB9B,CAAA;AAEF,gBAAgB;AAChB,eAAO,MAAM,kBAAkB;;;;;;;;;;;4HAI9B,CAAA"}
1
+ {"version":3,"file":"snapshotDelta.d.ts","sourceRoot":"","sources":["../../src/internal/snapshotDelta.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,QAAQ,MAAM,iBAAiB,CAAA;AAC3C,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AACvC,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AAOvC,OAAO,EAAE,UAAU,EAAE,KAAK,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AAC1D,OAAO,EAGL,KAAK,aAAa,EAClB,kBAAkB,EAInB,MAAM,qBAAqB,CAAA;AAmuB5B,gBAAgB;AAChB,eAAO,MAAM,aAAa;;;;;;;;;;;4GAgDzB,CAAA;AAyBD,gBAAgB;AAChB,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;;;;;;;;;;;;;;oGAoBhC,CAAA;AAED,gBAAgB;AAChB,eAAO,MAAM,mBAAmB;;;;;;;;;;;+DAe9B,CAAA;AAEF,gBAAgB;AAChB,eAAO,MAAM,mBAAmB;;;;;;;;;;;qDAkC9B,CAAA;AAEF,gBAAgB;AAChB,eAAO,MAAM,kBAAkB;;;;;;;;;;;4HAI9B,CAAA"}