@effect-vfs/core 0.2.0 → 0.3.1

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 (67) hide show
  1. package/dist/BytePath.d.ts +49 -5
  2. package/dist/BytePath.d.ts.map +1 -1
  3. package/dist/BytePath.js +9 -9
  4. package/dist/Snapshot.d.ts +61 -5
  5. package/dist/Snapshot.d.ts.map +1 -1
  6. package/dist/Snapshot.js +60 -5
  7. package/dist/SnapshotDelta.d.ts +208 -31
  8. package/dist/SnapshotDelta.d.ts.map +1 -1
  9. package/dist/SnapshotDelta.js +162 -25
  10. package/dist/VirtualFileSystem.d.ts +1035 -348
  11. package/dist/VirtualFileSystem.d.ts.map +1 -1
  12. package/dist/VirtualFileSystem.js +620 -1772
  13. package/dist/index.d.ts +1 -1
  14. package/dist/index.js +1 -1
  15. package/dist/internal/bytePath.d.ts +10 -0
  16. package/dist/internal/bytePath.d.ts.map +1 -0
  17. package/dist/internal/bytePath.js +41 -0
  18. package/dist/internal/bytes.d.ts +6 -0
  19. package/dist/internal/bytes.d.ts.map +1 -0
  20. package/dist/internal/bytes.js +17 -0
  21. package/dist/internal/canonicalBase64.d.ts +14 -6
  22. package/dist/internal/canonicalBase64.d.ts.map +1 -1
  23. package/dist/internal/canonicalBase64.js +29 -7
  24. package/dist/internal/image.d.ts +85 -104
  25. package/dist/internal/image.d.ts.map +1 -1
  26. package/dist/internal/image.js +99 -95
  27. package/dist/internal/metadata.d.ts +28 -0
  28. package/dist/internal/metadata.d.ts.map +1 -0
  29. package/dist/internal/metadata.js +29 -0
  30. package/dist/internal/snapshotDelta.d.ts +0 -1
  31. package/dist/internal/snapshotDelta.d.ts.map +1 -1
  32. package/dist/internal/snapshotDelta.js +358 -325
  33. package/dist/internal/snapshotDeltaModel.d.ts +10 -0
  34. package/dist/internal/snapshotDeltaModel.d.ts.map +1 -0
  35. package/dist/internal/snapshotDeltaModel.js +15 -0
  36. package/dist/internal/virtualFileSystem/errors.d.ts +31 -0
  37. package/dist/internal/virtualFileSystem/errors.d.ts.map +1 -0
  38. package/dist/internal/virtualFileSystem/errors.js +51 -0
  39. package/dist/internal/virtualFileSystem/fixture.d.ts +63 -0
  40. package/dist/internal/virtualFileSystem/fixture.d.ts.map +1 -0
  41. package/dist/internal/virtualFileSystem/fixture.js +144 -0
  42. package/dist/internal/{content.d.ts → virtualFileSystem/overlayContent.d.ts} +4 -14
  43. package/dist/internal/virtualFileSystem/overlayContent.d.ts.map +1 -0
  44. package/dist/internal/virtualFileSystem/overlayContent.js +29 -0
  45. package/dist/internal/{overlayChanges.d.ts → virtualFileSystem/overlayDiff.d.ts} +3 -12
  46. package/dist/internal/virtualFileSystem/overlayDiff.d.ts.map +1 -0
  47. package/dist/internal/virtualFileSystem/overlayDiff.js +120 -0
  48. package/dist/internal/virtualFileSystem/path.d.ts +44 -0
  49. package/dist/internal/virtualFileSystem/path.d.ts.map +1 -0
  50. package/dist/internal/virtualFileSystem/path.js +127 -0
  51. package/dist/internal/virtualFileSystem/testHooks.d.ts +20 -0
  52. package/dist/internal/virtualFileSystem/testHooks.d.ts.map +1 -0
  53. package/dist/internal/virtualFileSystem/testHooks.js +23 -0
  54. package/dist/internal/virtualFileSystem/watchHub.d.ts +16 -0
  55. package/dist/internal/virtualFileSystem/watchHub.d.ts.map +1 -0
  56. package/dist/internal/virtualFileSystem/watchHub.js +33 -0
  57. package/dist/internal/virtualFileSystem.d.ts +366 -0
  58. package/dist/internal/virtualFileSystem.d.ts.map +1 -0
  59. package/dist/internal/virtualFileSystem.js +1755 -0
  60. package/package.json +15 -5
  61. package/dist/internal/content.d.ts.map +0 -1
  62. package/dist/internal/content.js +0 -26
  63. package/dist/internal/overlayChanges.d.ts.map +0 -1
  64. package/dist/internal/overlayChanges.js +0 -146
  65. package/dist/internal/overlayTesting.d.ts +0 -12
  66. package/dist/internal/overlayTesting.d.ts.map +0 -1
  67. package/dist/internal/overlayTesting.js +0 -12
@@ -0,0 +1,10 @@
1
+ import type { SnapshotDelta } from "../SnapshotDelta.js";
2
+ /** @internal */
3
+ export declare const SnapshotDeltaTypeId: "@effect-vfs/core/SnapshotDelta";
4
+ /** @internal */
5
+ export declare const make: (value: WeakKey) => SnapshotDelta;
6
+ /** @internal */
7
+ export declare const value: (delta: SnapshotDelta) => WeakKey | undefined;
8
+ /** @internal */
9
+ export declare const isSnapshotDelta: (input: unknown) => input is SnapshotDelta;
10
+ //# sourceMappingURL=snapshotDeltaModel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"snapshotDeltaModel.d.ts","sourceRoot":"","sources":["../../src/internal/snapshotDeltaModel.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAA;AAExD,gBAAgB;AAChB,eAAO,MAAM,mBAAmB,EAAG,gCAAyC,CAAA;AAI5E,gBAAgB;AAChB,eAAO,MAAM,IAAI,UAAW,OAAO,KAAG,aAKrC,CAAA;AAED,gBAAgB;AAChB,eAAO,MAAM,KAAK,UAAW,aAAa,KAAG,OAAO,GAAG,SAAsC,CAAA;AAE7F,gBAAgB;AAChB,eAAO,MAAM,eAAe,UAAW,OAAO,KAAG,KAAK,IAAI,aACF,CAAA"}
@@ -0,0 +1,15 @@
1
+ // Opaque snapshot-delta representation and authenticity checks.
2
+ import * as Predicate from "effect/Predicate";
3
+ /** @internal */
4
+ export const SnapshotDeltaTypeId = "@effect-vfs/core/SnapshotDelta";
5
+ const snapshotDeltas = new WeakMap();
6
+ /** @internal */
7
+ export const make = (value) => {
8
+ const delta = Object.freeze({ [SnapshotDeltaTypeId]: SnapshotDeltaTypeId });
9
+ snapshotDeltas.set(delta, value);
10
+ return delta;
11
+ };
12
+ /** @internal */
13
+ export const value = (delta) => snapshotDeltas.get(delta);
14
+ /** @internal */
15
+ export const isSnapshotDelta = (input) => Predicate.isObject(input) && snapshotDeltas.has(input);
@@ -0,0 +1,31 @@
1
+ import * as Result from "effect/Result";
2
+ import * as Schema from "effect/Schema";
3
+ import type { PathInput } from "../../VirtualFileSystem.js";
4
+ /** @internal */
5
+ export declare const FsCode: Schema.Literals<readonly ["NotFound", "AlreadyExists", "NotEmpty", "NotDirectory", "AccessDenied", "InvalidHandle", "ForeignHandle", "InvalidReference", "ForeignReference", "StaleReference", "ClosedCaller", "InvalidArgument", "InvalidPathEncoding", "PathTooLong", "NoSpace", "IsDirectory", "FileTooLarge", "NoData", "SymlinkLoop", "UnrepresentableName"]>;
6
+ /** @internal */
7
+ export type FsCode = typeof FsCode.Type;
8
+ declare const FsError_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
9
+ readonly _tag: "FsError";
10
+ } & Readonly<A>;
11
+ /** @internal */
12
+ export declare class FsError extends FsError_base<{
13
+ readonly code: FsCode;
14
+ readonly operation: string;
15
+ readonly path?: PathInput;
16
+ }> {
17
+ get message(): string;
18
+ }
19
+ declare const ConfigurationError_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
20
+ readonly _tag: "ConfigurationError";
21
+ } & Readonly<A>;
22
+ /** @internal */
23
+ export declare class ConfigurationError extends ConfigurationError_base<{
24
+ readonly field: string;
25
+ }> {
26
+ get message(): string;
27
+ }
28
+ /** @internal */
29
+ export declare const decodeConfiguration: <A>(schema: Schema.Codec<A>, value: typeof Schema.Unknown.Type) => Result.Result<A, ConfigurationError>;
30
+ export {};
31
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../../src/internal/virtualFileSystem/errors.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AACvC,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AAEvC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAA;AAE3D,gBAAgB;AAChB,eAAO,MAAM,MAAM,oWAqBjB,CAAA;AAEF,gBAAgB;AAChB,MAAM,MAAM,MAAM,GAAG,OAAO,MAAM,CAAC,IAAI,CAAA;;;;AAEvC,gBAAgB;AAChB,qBAAa,OAAQ,SAAQ,aAA4B;IACvD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,QAAQ,CAAC,IAAI,CAAC,EAAE,SAAS,CAAA;CAC1B,CAAC;IAEA,IAAa,OAAO,IAAI,MAAM,CAE7B;CACF;;;;AAED,gBAAgB;AAChB,qBAAa,kBAAmB,SAAQ,wBAAuC;IAC7E,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;CACvB,CAAC;IACA,IAAa,OAAO,IAAI,MAAM,CAE7B;CACF;AAWD,gBAAgB;AAChB,eAAO,MAAM,mBAAmB,GAAI,CAAC,UAAU,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,OAAO,MAAM,CAAC,OAAO,CAAC,IAAI,yCAG9F,CAAA"}
@@ -0,0 +1,51 @@
1
+ // Virtual filesystem failure schemas and identities shared by the runtime implementation.
2
+ import * as Data from "effect/Data";
3
+ import * as Predicate from "effect/Predicate";
4
+ import * as Result from "effect/Result";
5
+ import * as Schema from "effect/Schema";
6
+ /** @internal */
7
+ export const FsCode = Schema.Literals([
8
+ "NotFound",
9
+ "AlreadyExists",
10
+ "NotEmpty",
11
+ "NotDirectory",
12
+ "AccessDenied",
13
+ "InvalidHandle",
14
+ "ForeignHandle",
15
+ "InvalidReference",
16
+ "ForeignReference",
17
+ "StaleReference",
18
+ "ClosedCaller",
19
+ "InvalidArgument",
20
+ "InvalidPathEncoding",
21
+ "PathTooLong",
22
+ "NoSpace",
23
+ "IsDirectory",
24
+ "FileTooLarge",
25
+ "NoData",
26
+ "SymlinkLoop",
27
+ "UnrepresentableName"
28
+ ]);
29
+ /** @internal */
30
+ export class FsError extends Data.TaggedError("FsError") {
31
+ // The path is omitted on purpose: byte paths are not printable and may carry user data.
32
+ get message() {
33
+ return `${this.operation} failed with ${this.code}`;
34
+ }
35
+ }
36
+ /** @internal */
37
+ export class ConfigurationError extends Data.TaggedError("ConfigurationError") {
38
+ get message() {
39
+ return `Invalid option: ${this.field}`;
40
+ }
41
+ }
42
+ // Names the offending option so the caller learns which key it got wrong, not just that decoding failed.
43
+ const configurationField = (issue) => {
44
+ if (Predicate.isTagged("Pointer")(issue))
45
+ return issue.path.map(String).join(".");
46
+ if (Predicate.isTagged("Composite")(issue))
47
+ return configurationField(issue.issues[0]);
48
+ return "options";
49
+ };
50
+ /** @internal */
51
+ export const decodeConfiguration = (schema, value) => Schema.decodeUnknownResult(schema, { onExcessProperty: "error" })(value).pipe(Result.mapError((error) => new ConfigurationError({ field: configurationField(error.issue) })));
@@ -0,0 +1,63 @@
1
+ import * as Effect from "effect/Effect";
2
+ import { ImageError } from "../../Snapshot.js";
3
+ /** @internal */
4
+ export declare const fromFixture: (fixture: {
5
+ readonly rootMetadata?: {
6
+ readonly uid?: number;
7
+ readonly gid?: number;
8
+ readonly mode?: number;
9
+ readonly atimeNs?: bigint;
10
+ readonly mtimeNs?: bigint;
11
+ readonly ctimeNs?: bigint;
12
+ readonly birthtimeNs?: bigint;
13
+ };
14
+ readonly entries: readonly ({
15
+ readonly kind: "directory";
16
+ readonly path: string | import("../../BytePath.js").BytePath;
17
+ readonly metadata?: {
18
+ readonly uid?: number;
19
+ readonly gid?: number;
20
+ readonly mode?: number;
21
+ readonly atimeNs?: bigint;
22
+ readonly mtimeNs?: bigint;
23
+ readonly ctimeNs?: bigint;
24
+ readonly birthtimeNs?: bigint;
25
+ };
26
+ } | {
27
+ readonly kind: "file";
28
+ readonly path: string | import("../../BytePath.js").BytePath;
29
+ readonly bytes: Uint8Array<ArrayBufferLike>;
30
+ readonly metadata?: {
31
+ readonly uid?: number;
32
+ readonly gid?: number;
33
+ readonly mode?: number;
34
+ readonly atimeNs?: bigint;
35
+ readonly mtimeNs?: bigint;
36
+ readonly ctimeNs?: bigint;
37
+ readonly birthtimeNs?: bigint;
38
+ };
39
+ } | {
40
+ readonly kind: "symlink";
41
+ readonly path: string | import("../../BytePath.js").BytePath;
42
+ readonly target: string | import("../../BytePath.js").BytePath;
43
+ readonly metadata?: {
44
+ readonly uid?: number;
45
+ readonly gid?: number;
46
+ readonly mode?: number;
47
+ readonly atimeNs?: bigint;
48
+ readonly mtimeNs?: bigint;
49
+ readonly ctimeNs?: bigint;
50
+ readonly birthtimeNs?: bigint;
51
+ };
52
+ } | {
53
+ readonly kind: "hardLink";
54
+ readonly path: string | import("../../BytePath.js").BytePath;
55
+ readonly target: string | import("../../BytePath.js").BytePath;
56
+ })[];
57
+ }, options?: {
58
+ readonly maxEntries?: number;
59
+ readonly maxBytes?: import("effect/ByteSize").ByteSize;
60
+ readonly maxFileBytes?: import("effect/ByteSize").ByteSize;
61
+ readonly maxPathBytes?: import("effect/ByteSize").ByteSize;
62
+ } | undefined) => Effect.Effect<import("../../VirtualFileSystem.js").Volume, import("./errors.js").ConfigurationError | ImageError, never>;
63
+ //# sourceMappingURL=fixture.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fixture.d.ts","sourceRoot":"","sources":["../../../src/internal/virtualFileSystem/fixture.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AAGvC,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAA;AAmB9C,gBAAgB;AAChB,eAAO,MAAM,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;0IAiLvB,CAAA"}
@@ -0,0 +1,144 @@
1
+ // Fixture image construction.
2
+ import * as Effect from "effect/Effect";
3
+ import * as Result from "effect/Result";
4
+ import * as Schema from "effect/Schema";
5
+ import { ImageError } from "../../Snapshot.js";
6
+ import { CanonicalBase64 } from "../canonicalBase64.js";
7
+ import * as Image from "../image.js";
8
+ import { Fixture as FixtureSchema, FixtureEntry, makeVolume, VolumeOptions as VolumeOptionsSchema, VolumeSource } from "../virtualFileSystem.js";
9
+ import { decodeConfiguration, FsError } from "./errors.js";
10
+ import { inputBytes, isAttachedBytes, isDotComponent, nameBytes, preparePath } from "./path.js";
11
+ const DEFAULT_MODE = { directory: 0o755, file: 0o644, symlink: 0o777 };
12
+ const EPOCH_NS = 0n;
13
+ /** @internal */
14
+ export const fromFixture = Effect.fn("VirtualFileSystem.fromFixture")(function* (fixture, options) {
15
+ const config = decodeConfiguration(VolumeOptionsSchema, options ?? {});
16
+ if (Result.isFailure(config))
17
+ return yield* config.failure;
18
+ const decoded = Schema.decodeResult(FixtureSchema, { onExcessProperty: "error" })(fixture);
19
+ if (Result.isFailure(decoded))
20
+ return yield* new ImageError({ code: "InvalidStructure", field: "fixture" });
21
+ const source = decoded.success;
22
+ if (source.entries.some((entry) => FixtureEntry.match(entry, {
23
+ directory: () => false,
24
+ file: ({ bytes }) => !isAttachedBytes(bytes),
25
+ symlink: () => false,
26
+ hardLink: () => false
27
+ }))) {
28
+ return yield* new ImageError({ code: "InvalidEncoding", field: "bytes" });
29
+ }
30
+ const metadata = (kind, overrides) => ({
31
+ uid: overrides?.uid ?? 0,
32
+ gid: overrides?.gid ?? 0,
33
+ mode: overrides?.mode ?? DEFAULT_MODE[kind],
34
+ atimeNs: overrides?.atimeNs ?? EPOCH_NS,
35
+ mtimeNs: overrides?.mtimeNs ?? EPOCH_NS,
36
+ ctimeNs: overrides?.ctimeNs ?? EPOCH_NS,
37
+ birthtimeNs: overrides?.birthtimeNs ?? EPOCH_NS
38
+ });
39
+ const declarations = new Map();
40
+ const aliases = new Map();
41
+ const paths = new Map();
42
+ const root = Image.Record.cases.directory.make({
43
+ id: "root",
44
+ metadata: metadata("directory", source.rootMetadata),
45
+ entries: []
46
+ });
47
+ declarations.set("", root);
48
+ const fixturePath = (input) => preparePath(input, "fixture", config.success.maxPathBytes).pipe(Result.flatMap((path) => !path.absolute || path.components.length === 0 ||
49
+ path.components.some(isDotComponent)
50
+ ? Result.fail(new FsError({ code: "InvalidArgument", operation: "fixture", path: input })) :
51
+ Result.succeed(path.components)));
52
+ // Encode caller-owned byte buffers now: nothing below yields until Image.capture, so a caller
53
+ // cannot mutate them first.
54
+ for (const entry of source.entries) {
55
+ const parsed = fixturePath(entry.path);
56
+ if (Result.isFailure(parsed)) {
57
+ return yield* new ImageError({ code: "InvalidStructure", field: "path" });
58
+ }
59
+ const components = parsed.success;
60
+ const key = components.join("/");
61
+ if (paths.has(key))
62
+ return yield* new ImageError({ code: "InvalidStructure", field: "duplicate" });
63
+ paths.set(key, components);
64
+ const error = FixtureEntry.match(entry, {
65
+ hardLink: ({ target }) => {
66
+ const parsedTarget = fixturePath(target);
67
+ if (Result.isFailure(parsedTarget))
68
+ return new ImageError({ code: "InvalidStructure", field: "target" });
69
+ aliases.set(key, parsedTarget.success.join("/"));
70
+ },
71
+ directory: (entry) => {
72
+ declarations.set(key, Image.Record.cases.directory.make({
73
+ id: String(paths.size),
74
+ metadata: metadata("directory", entry.metadata),
75
+ entries: []
76
+ }));
77
+ },
78
+ file: (entry) => {
79
+ declarations.set(key, Image.Record.cases.file.make({
80
+ id: String(paths.size),
81
+ metadata: metadata("file", entry.metadata),
82
+ data: CanonicalBase64.encode(entry.bytes)
83
+ }));
84
+ },
85
+ symlink: ({ metadata: overrides, target: input }) => {
86
+ const target = inputBytes(input);
87
+ if (Result.isFailure(target)) {
88
+ return new ImageError({
89
+ code: target.failure === "InvalidPathEncoding" ? "InvalidEncoding" : "InvalidStructure",
90
+ field: "target"
91
+ });
92
+ }
93
+ if (target.success.includes(0))
94
+ return new ImageError({ code: "InvalidStructure", field: "target" });
95
+ declarations.set(key, Image.Record.cases.symlink.make({
96
+ id: String(paths.size),
97
+ metadata: metadata("symlink", overrides),
98
+ target: CanonicalBase64.encode(target.success)
99
+ }));
100
+ }
101
+ });
102
+ if (error instanceof ImageError)
103
+ return yield* error;
104
+ }
105
+ for (const [key] of aliases) {
106
+ let target = key;
107
+ const seen = new Set();
108
+ while (!declarations.has(target)) {
109
+ if (seen.has(target))
110
+ return yield* new ImageError({ code: "InvalidStructure", field: "hardLink" });
111
+ seen.add(target);
112
+ const next = aliases.get(target);
113
+ if (next === undefined)
114
+ return yield* new ImageError({ code: "InvalidStructure", field: "hardLink" });
115
+ target = next;
116
+ }
117
+ const node = declarations.get(target);
118
+ if (node === undefined || Image.Record.guards.directory(node)) {
119
+ return yield* new ImageError({ code: "InvalidStructure", field: "hardLink" });
120
+ }
121
+ for (const alias of seen)
122
+ declarations.set(alias, node);
123
+ }
124
+ const children = new Map();
125
+ for (const [key, components] of paths) {
126
+ const parent = declarations.get(components.slice(0, -1).join("/"));
127
+ const child = declarations.get(key);
128
+ const name = components.at(-1);
129
+ if (parent?._tag !== "directory" || child === undefined || name === undefined) {
130
+ return yield* new ImageError({ code: "InvalidStructure", field: "parent" });
131
+ }
132
+ const entries = children.get(parent.id) ?? [];
133
+ entries.push({ name: CanonicalBase64.encode(nameBytes(name)), target: child.id });
134
+ children.set(parent.id, entries);
135
+ }
136
+ const records = [...new Set(declarations.values())].map((record) => Image.Record.match(record, {
137
+ directory: (record) => ({ ...record, entries: children.get(record.id) ?? [] }),
138
+ file: (record) => record,
139
+ symlink: (record) => record
140
+ }));
141
+ const snapshot = yield* Image.capture({ format: "effect-vfs", version: 1, root: "root", records }, undefined, true);
142
+ const image = yield* Image.inspect(snapshot);
143
+ return yield* makeVolume(VolumeSource.Snapshot({ image }), config.success);
144
+ });
@@ -1,10 +1,5 @@
1
- /**
2
- * Immutable regular-file payloads shared by overlay workspaces.
3
- *
4
- * @internal
5
- */
6
- import type { Snapshot } from "../Snapshot.js";
7
- import * as Image from "./image.js";
1
+ import type { Snapshot } from "../../Snapshot.js";
2
+ import * as Image from "../image.js";
8
3
  /** @internal */
9
4
  export interface Content {
10
5
  readonly bytes: Uint8Array;
@@ -15,11 +10,6 @@ export declare const hasOverlayContents: (snapshot: Snapshot) => boolean;
15
10
  export declare const make: (bytes: Uint8Array) => Content;
16
11
  /** @internal */
17
12
  export declare const empty: () => Content;
18
- /**
19
- * Returns the immutable file payloads associated with the identity of a base
20
- * snapshot. The cache is weak so it cannot extend the snapshot's lifetime.
21
- *
22
- * @internal
23
- */
13
+ /** @internal */
24
14
  export declare const forOverlay: (snapshot: Snapshot, image: Image.Document) => ReadonlyMap<string, Content>;
25
- //# sourceMappingURL=content.d.ts.map
15
+ //# sourceMappingURL=overlayContent.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"overlayContent.d.ts","sourceRoot":"","sources":["../../../src/internal/virtualFileSystem/overlayContent.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAA;AAEjD,OAAO,KAAK,KAAK,MAAM,aAAa,CAAA;AAEpC,gBAAgB;AAChB,MAAM,WAAW,OAAO;IACtB,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAA;CAC3B;AAID,gBAAgB;AAChB,eAAO,MAAM,kBAAkB,aAAc,QAAQ,KAAG,OAAwC,CAAA;AAEhG,gBAAgB;AAChB,eAAO,MAAM,IAAI,UAAW,UAAU,KAAG,OAAsB,CAAA;AAE/D,gBAAgB;AAChB,eAAO,MAAM,KAAK,QAAO,OAAkC,CAAA;AAG3D,gBAAgB;AAChB,eAAO,MAAM,UAAU,aAAc,QAAQ,SAAS,KAAK,CAAC,QAAQ,KAAG,WAAW,CAAC,MAAM,EAAE,OAAO,CAiBjG,CAAA"}
@@ -0,0 +1,29 @@
1
+ // Immutable regular-file payloads shared by overlay workspaces.
2
+ import * as Result from "effect/Result";
3
+ import * as Schema from "effect/Schema";
4
+ import { CanonicalBase64 } from "../canonicalBase64.js";
5
+ import * as Image from "../image.js";
6
+ const overlayContents = new WeakMap();
7
+ /** @internal */
8
+ export const hasOverlayContents = (snapshot) => overlayContents.has(snapshot);
9
+ /** @internal */
10
+ export const make = (bytes) => ({ bytes });
11
+ /** @internal */
12
+ export const empty = () => make(new Uint8Array(0));
13
+ // The weak cache shares immutable payloads without extending the snapshot's lifetime.
14
+ /** @internal */
15
+ export const forOverlay = (snapshot, image) => {
16
+ const cached = overlayContents.get(snapshot);
17
+ if (cached !== undefined)
18
+ return cached;
19
+ const decoded = new Map();
20
+ for (const record of image.records) {
21
+ if (Image.Record.guards.file(record)) {
22
+ const bytes = Schema.decodeResult(CanonicalBase64.Bytes)(record.data);
23
+ if (Result.isSuccess(bytes))
24
+ decoded.set(record.id, make(bytes.success));
25
+ }
26
+ }
27
+ overlayContents.set(snapshot, decoded);
28
+ return decoded;
29
+ };
@@ -1,20 +1,11 @@
1
- /** @internal */
2
- export interface ObservationMetadata {
3
- readonly uid: number;
4
- readonly gid: number;
5
- readonly mode: number;
6
- readonly atimeNs: string;
7
- readonly mtimeNs: string;
8
- readonly ctimeNs: string;
9
- readonly birthtimeNs: string;
10
- }
1
+ import type { StoredMetadata } from "../metadata.js";
11
2
  /** @internal */
12
3
  export interface ObservationEntry {
13
4
  readonly path: Uint8Array;
14
5
  readonly lineage: string | undefined;
15
6
  readonly kind: "directory" | "file" | "symlink";
16
7
  readonly content: Uint8Array | undefined;
17
- readonly metadata: ObservationMetadata;
8
+ readonly metadata: StoredMetadata;
18
9
  }
19
10
  /** @internal */
20
11
  export type OverlayDifference = "content" | "mode" | "uid" | "gid" | "atimeNs" | "mtimeNs" | "ctimeNs" | "birthtimeNs";
@@ -47,4 +38,4 @@ export type RawOverlayChange = {
47
38
  };
48
39
  /** @internal */
49
40
  export declare const compareOverlay: (base: ReadonlyArray<ObservationEntry>, current: ReadonlyArray<ObservationEntry>, includeTimestamps?: boolean) => ReadonlyArray<RawOverlayChange>;
50
- //# sourceMappingURL=overlayChanges.d.ts.map
41
+ //# sourceMappingURL=overlayDiff.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"overlayDiff.d.ts","sourceRoot":"","sources":["../../../src/internal/virtualFileSystem/overlayDiff.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAA;AAEpD,gBAAgB;AAChB,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAA;IACzB,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,CAAA;IACpC,QAAQ,CAAC,IAAI,EAAE,WAAW,GAAG,MAAM,GAAG,SAAS,CAAA;IAC/C,QAAQ,CAAC,OAAO,EAAE,UAAU,GAAG,SAAS,CAAA;IACxC,QAAQ,CAAC,QAAQ,EAAE,cAAc,CAAA;CAClC;AAED,gBAAgB;AAChB,MAAM,MAAM,iBAAiB,GAAG,SAAS,GAAG,MAAM,GAAG,KAAK,GAAG,KAAK,GAAG,SAAS,GAAG,SAAS,GAAG,SAAS,GAAG,aAAa,CAAA;AAEtH,gBAAgB;AAChB,MAAM,MAAM,gBAAgB,GACxB;IAAE,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,gBAAgB,CAAC,MAAM,CAAC,CAAA;CAAE,GAC9F;IAAE,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,gBAAgB,CAAC,MAAM,CAAC,CAAA;CAAE,GAChG;IACA,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAA;IACzB,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAA;IACzB,QAAQ,CAAC,UAAU,EAAE,gBAAgB,CAAC,MAAM,CAAC,CAAA;IAC7C,QAAQ,CAAC,SAAS,EAAE,gBAAgB,CAAC,MAAM,CAAC,CAAA;IAC5C,QAAQ,CAAC,WAAW,EAAE,aAAa,CAAC,iBAAiB,CAAC,CAAA;CACvD,GACC;IACA,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAA;IACxB,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAA;IACzB,QAAQ,CAAC,EAAE,EAAE,UAAU,CAAA;IACvB,QAAQ,CAAC,IAAI,EAAE,gBAAgB,CAAC,MAAM,CAAC,CAAA;IACvC,QAAQ,CAAC,WAAW,EAAE,aAAa,CAAC,iBAAiB,CAAC,CAAA;CACvD,GACC;IACA,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAA;IACxB,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAA;IACzB,QAAQ,CAAC,IAAI,EAAE,gBAAgB,CAAC,MAAM,CAAC,CAAA;IACvC,QAAQ,CAAC,WAAW,EAAE,aAAa,CAAC,iBAAiB,CAAC,CAAA;CACvD,CAAA;AA4DH,gBAAgB;AAChB,eAAO,MAAM,cAAc,SACnB,aAAa,CAAC,gBAAgB,CAAC,WAC5B,aAAa,CAAC,gBAAgB,CAAC,kCAEvC,aAAa,CAAC,gBAAgB,CAgFhC,CAAA"}
@@ -0,0 +1,120 @@
1
+ import * as Data from "effect/Data";
2
+ import * as Encoding from "effect/Encoding";
3
+ import * as Order from "effect/Order";
4
+ import * as Predicate from "effect/Predicate";
5
+ import { bytesOrder, sameBytes } from "../bytes.js";
6
+ const RawOverlayChange = Data.taggedEnum();
7
+ const timestampFields = new Set(["atimeNs", "mtimeNs", "ctimeNs", "birthtimeNs"]);
8
+ const differenceOrder = [
9
+ "content",
10
+ "mode",
11
+ "uid",
12
+ "gid",
13
+ "atimeNs",
14
+ "mtimeNs",
15
+ "ctimeNs",
16
+ "birthtimeNs"
17
+ ];
18
+ const tagOrder = {
19
+ Added: 0,
20
+ Removed: 1,
21
+ Replaced: 2,
22
+ Renamed: 3,
23
+ Updated: 4
24
+ };
25
+ const key = Encoding.encodeHex;
26
+ // Output order: source path bytewise (a rename sorts by its `from`), then Added, Removed, Replaced, Renamed, Updated.
27
+ const sourcePath = (change) => Predicate.isTagged("Renamed")(change) ? change.from : change.path;
28
+ const changeOrder = Order.combine(Order.mapInput(bytesOrder, sourcePath), Order.mapInput(Order.Number, (change) => tagOrder[change._tag]));
29
+ const differences = (before, after, includeTimestamps) => Object.freeze(differenceOrder.filter((field) => {
30
+ if (!includeTimestamps && timestampFields.has(field))
31
+ return false;
32
+ // A kind change always counts as a content change, even when the bytes happen to match.
33
+ if (field === "content")
34
+ return before.kind !== after.kind || !sameBytes(before.content, after.content);
35
+ return before.metadata[field] !== after.metadata[field];
36
+ }));
37
+ const byLineage = (entries) => {
38
+ const result = new Map();
39
+ for (const entry of entries) {
40
+ if (entry.lineage === undefined)
41
+ continue;
42
+ const group = result.get(entry.lineage);
43
+ if (group === undefined)
44
+ result.set(entry.lineage, new Map([[key(entry.path), entry]]));
45
+ else
46
+ group.set(key(entry.path), entry);
47
+ }
48
+ return result;
49
+ };
50
+ /** @internal */
51
+ export const compareOverlay = (base, current, includeTimestamps = false) => {
52
+ const basePaths = new Map(base.map((entry) => [key(entry.path), entry]));
53
+ const currentPaths = new Map(current.map((entry) => [key(entry.path), entry]));
54
+ const baseLineages = byLineage(base);
55
+ const currentLineages = byLineage(current);
56
+ const renameSources = new Set();
57
+ const renameTargets = new Set();
58
+ const changes = [];
59
+ for (const [lineage, beforeEntries] of baseLineages) {
60
+ const afterEntries = currentLineages.get(lineage);
61
+ if (afterEntries === undefined)
62
+ continue;
63
+ const removed = [];
64
+ for (const [pathKey, entry] of beforeEntries) {
65
+ if (!afterEntries.has(pathKey))
66
+ removed.push(entry);
67
+ }
68
+ const added = [];
69
+ for (const [pathKey, entry] of afterEntries) {
70
+ if (!beforeEntries.has(pathKey))
71
+ added.push(entry);
72
+ }
73
+ if (removed.length !== 1 || added.length !== 1)
74
+ continue;
75
+ const before = removed[0];
76
+ const after = added[0];
77
+ renameSources.add(key(before.path));
78
+ renameTargets.add(key(after.path));
79
+ changes.push(RawOverlayChange.Renamed({
80
+ from: before.path.slice(),
81
+ to: after.path.slice(),
82
+ kind: after.kind,
83
+ differences: differences(before, after, includeTimestamps)
84
+ }));
85
+ }
86
+ for (const before of base) {
87
+ const pathKey = key(before.path);
88
+ if (renameSources.has(pathKey))
89
+ continue;
90
+ const after = currentPaths.get(pathKey);
91
+ // Gone, or the path's current occupant arrived by rename: the base entry was removed either way.
92
+ if (after === undefined || renameTargets.has(pathKey)) {
93
+ changes.push(RawOverlayChange.Removed({ path: before.path.slice(), kind: before.kind }));
94
+ continue;
95
+ }
96
+ if (before.lineage === undefined || before.lineage !== after.lineage) {
97
+ changes.push(RawOverlayChange.Replaced({
98
+ path: before.path.slice(),
99
+ beforeKind: before.kind,
100
+ afterKind: after.kind,
101
+ differences: differences(before, after, includeTimestamps)
102
+ }));
103
+ continue;
104
+ }
105
+ const changed = differences(before, after, includeTimestamps);
106
+ if (changed.length > 0) {
107
+ changes.push(RawOverlayChange.Updated({ path: before.path.slice(), kind: after.kind, differences: changed }));
108
+ }
109
+ }
110
+ for (const after of current) {
111
+ const pathKey = key(after.path);
112
+ // A new path, or one whose base occupant was renamed away; rename targets were already reported.
113
+ if (!renameTargets.has(pathKey) && (!basePaths.has(pathKey) || renameSources.has(pathKey))) {
114
+ changes.push(RawOverlayChange.Added({ path: after.path.slice(), kind: after.kind }));
115
+ }
116
+ }
117
+ changes.sort(changeOrder);
118
+ // Every record owns its byte buffers and is frozen, so callers may wrap paths without copying.
119
+ return Object.freeze(changes.map((change) => Object.freeze(change)));
120
+ };
@@ -0,0 +1,44 @@
1
+ import * as ByteSize from "effect/ByteSize";
2
+ import * as Effect from "effect/Effect";
3
+ import * as Result from "effect/Result";
4
+ import type { BytePath } from "../../BytePath.js";
5
+ import type { PathInput } from "../../VirtualFileSystem.js";
6
+ import { FsError } from "./errors.js";
7
+ /** @internal */
8
+ export declare const SLASH_HEX = "2f";
9
+ /** @internal */
10
+ export declare const DOT_HEX = "2e";
11
+ /** @internal */
12
+ export declare const DOT_DOT_HEX = "2e2e";
13
+ /** @internal */
14
+ export declare const SLASH_BYTE = 47;
15
+ /** @internal */
16
+ export declare const MAX_NAME_BYTES = 255;
17
+ /** @internal */
18
+ export declare const isDotComponent: (name: string | undefined) => name is undefined | typeof DOT_HEX | typeof DOT_DOT_HEX;
19
+ /** @internal */
20
+ export declare const ownedPath: (bytes: Uint8Array) => BytePath;
21
+ /** @internal */
22
+ export declare const strictString: (bytes: Uint8Array, operation: string) => Effect.Effect<string, FsError, never>;
23
+ /** @internal */
24
+ export declare const nameBytes: (name: string) => Uint8Array;
25
+ /** @internal */
26
+ export declare const isAttachedBytes: (bytes: Uint8Array) => boolean;
27
+ /** @internal */
28
+ export declare const pathFromBytes: (bytes: Uint8Array<ArrayBufferLike>) => Effect.Effect<BytePath, FsError, never>;
29
+ /** @internal */
30
+ export declare const pathToBytes: (path: BytePath) => Effect.Effect<Uint8Array<ArrayBuffer>, FsError, never>;
31
+ /** @internal */
32
+ export interface PreparedPath {
33
+ readonly input: PathInput;
34
+ readonly absolute: boolean;
35
+ readonly trailingSlash: boolean;
36
+ readonly bytes: Uint8Array;
37
+ readonly suffixes: ReadonlyArray<Uint8Array>;
38
+ readonly components: ReadonlyArray<string>;
39
+ }
40
+ /** @internal */
41
+ export declare const inputBytes: (input: PathInput) => Result.Result<Uint8Array, "InvalidPathEncoding" | "InvalidArgument">;
42
+ /** @internal */
43
+ export declare const preparePath: (input: PathInput, operation: string, maxPathBytes: ByteSize.ByteSize | undefined) => Result.Result<PreparedPath, FsError>;
44
+ //# sourceMappingURL=path.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"path.d.ts","sourceRoot":"","sources":["../../../src/internal/virtualFileSystem/path.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,QAAQ,MAAM,iBAAiB,CAAA;AAC3C,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AAGvC,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AACvC,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAA;AACjD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAA;AAE3D,OAAO,EAAE,OAAO,EAAE,MAAM,aAAa,CAAA;AAGrC,gBAAgB;AAChB,eAAO,MAAM,SAAS,OAAO,CAAA;AAE7B,gBAAgB;AAChB,eAAO,MAAM,OAAO,OAAO,CAAA;AAE3B,gBAAgB;AAChB,eAAO,MAAM,WAAW,SAAS,CAAA;AAEjC,gBAAgB;AAChB,eAAO,MAAM,UAAU,KAAK,CAAA;AAG5B,gBAAgB;AAChB,eAAO,MAAM,cAAc,MAAM,CAAA;AAKjC,gBAAgB;AAChB,eAAO,MAAM,cAAc,SAAU,MAAM,GAAG,SAAS,KAAG,IAAI,IAAI,SAAS,GAAG,OAAO,OAAO,GAAG,OAAO,WACtC,CAAA;AAEhE,gBAAgB;AAChB,eAAO,MAAM,SAAS,UAAW,UAAU,KAAG,QAA+B,CAAA;AAE7E,gBAAgB;AAChB,eAAO,MAAM,YAAY,UAAW,UAAU,aAAa,MAAM,0CAI7D,CAAA;AAEJ,gBAAgB;AAChB,eAAO,MAAM,SAAS,SAAU,MAAM,KAAG,UAAyD,CAAA;AAgBlG,gBAAgB;AAChB,eAAO,MAAM,eAAe,UAAW,UAAU,KAAG,OAC2C,CAAA;AAE/F,gBAAgB;AAChB,eAAO,MAAM,aAAa,iFASxB,CAAA;AAEF,gBAAgB;AAChB,eAAO,MAAM,WAAW,4EAMtB,CAAA;AAEF,gBAAgB;AAChB,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAA;IACzB,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAA;IAC1B,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAA;IAC/B,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAA;IAC1B,QAAQ,CAAC,QAAQ,EAAE,aAAa,CAAC,UAAU,CAAC,CAAA;IAC5C,QAAQ,CAAC,UAAU,EAAE,aAAa,CAAC,MAAM,CAAC,CAAA;CAC3C;AAoBD,gBAAgB;AAChB,eAAO,MAAM,UAAU,UAAW,SAAS,KAAG,MAAM,CAAC,MAAM,CAAC,UAAU,EAAE,qBAAqB,GAAG,iBAAiB,CAG5B,CAAA;AAErF,gBAAgB;AAChB,eAAO,MAAM,WAAW,UACf,SAAS,aACL,MAAM,gBACH,QAAQ,CAAC,QAAQ,GAAG,SAAS,KAC1C,MAAM,CAAC,MAAM,CAAC,YAAY,EAAE,OAAO,CAgDrC,CAAA"}