@effect-vfs/core 0.3.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.
- package/dist/BytePath.d.ts +33 -2
- package/dist/BytePath.d.ts.map +1 -1
- package/dist/BytePath.js +1 -1
- package/dist/Snapshot.d.ts +58 -2
- package/dist/Snapshot.d.ts.map +1 -1
- package/dist/Snapshot.js +57 -1
- package/dist/SnapshotDelta.d.ts +113 -3
- package/dist/SnapshotDelta.d.ts.map +1 -1
- package/dist/SnapshotDelta.js +112 -2
- package/dist/VirtualFileSystem.d.ts +919 -10
- package/dist/VirtualFileSystem.d.ts.map +1 -1
- package/dist/VirtualFileSystem.js +575 -7
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/internal/bytePath.d.ts.map +1 -1
- package/dist/internal/bytePath.js +5 -10
- package/dist/internal/bytes.d.ts +6 -0
- package/dist/internal/bytes.d.ts.map +1 -0
- package/dist/internal/bytes.js +17 -0
- package/dist/internal/canonicalBase64.d.ts +13 -5
- package/dist/internal/canonicalBase64.d.ts.map +1 -1
- package/dist/internal/canonicalBase64.js +28 -6
- package/dist/internal/image.d.ts +76 -90
- package/dist/internal/image.d.ts.map +1 -1
- package/dist/internal/image.js +98 -89
- package/dist/internal/metadata.d.ts +28 -0
- package/dist/internal/metadata.d.ts.map +1 -0
- package/dist/internal/metadata.js +29 -0
- package/dist/internal/snapshotDelta.d.ts.map +1 -1
- package/dist/internal/snapshotDelta.js +346 -300
- package/dist/internal/snapshotDeltaModel.d.ts +2 -2
- package/dist/internal/snapshotDeltaModel.d.ts.map +1 -1
- package/dist/internal/snapshotDeltaModel.js +3 -1
- package/dist/internal/virtualFileSystem/errors.d.ts +5 -0
- package/dist/internal/virtualFileSystem/errors.d.ts.map +1 -1
- package/dist/internal/virtualFileSystem/errors.js +19 -0
- package/dist/internal/virtualFileSystem/fixture.d.ts.map +1 -1
- package/dist/internal/virtualFileSystem/fixture.js +74 -74
- package/dist/internal/virtualFileSystem/overlayContent.d.ts.map +1 -1
- package/dist/internal/virtualFileSystem/overlayContent.js +9 -2
- package/dist/internal/virtualFileSystem/overlayDiff.d.ts +2 -11
- package/dist/internal/virtualFileSystem/overlayDiff.d.ts.map +1 -1
- package/dist/internal/virtualFileSystem/overlayDiff.js +45 -71
- package/dist/internal/virtualFileSystem/path.d.ts +15 -14
- package/dist/internal/virtualFileSystem/path.d.ts.map +1 -1
- package/dist/internal/virtualFileSystem/path.js +57 -54
- package/dist/internal/virtualFileSystem/testHooks.d.ts.map +1 -1
- package/dist/internal/virtualFileSystem/watchHub.d.ts.map +1 -1
- package/dist/internal/virtualFileSystem/watchHub.js +13 -13
- package/dist/internal/virtualFileSystem.d.ts +91 -15
- package/dist/internal/virtualFileSystem.d.ts.map +1 -1
- package/dist/internal/virtualFileSystem.js +470 -392
- package/package.json +15 -5
|
@@ -2,9 +2,9 @@ import type { SnapshotDelta } from "../SnapshotDelta.js";
|
|
|
2
2
|
/** @internal */
|
|
3
3
|
export declare const SnapshotDeltaTypeId: "@effect-vfs/core/SnapshotDelta";
|
|
4
4
|
/** @internal */
|
|
5
|
-
export declare const make: (value:
|
|
5
|
+
export declare const make: (value: WeakKey) => SnapshotDelta;
|
|
6
6
|
/** @internal */
|
|
7
|
-
export declare const value: (delta: SnapshotDelta) =>
|
|
7
|
+
export declare const value: (delta: SnapshotDelta) => WeakKey | undefined;
|
|
8
8
|
/** @internal */
|
|
9
9
|
export declare const isSnapshotDelta: (input: unknown) => input is SnapshotDelta;
|
|
10
10
|
//# sourceMappingURL=snapshotDeltaModel.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"snapshotDeltaModel.d.ts","sourceRoot":"","sources":["../../src/internal/snapshotDeltaModel.ts"],"names":[],"mappings":"
|
|
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"}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
// Opaque snapshot-delta representation and authenticity checks.
|
|
2
|
+
import * as Predicate from "effect/Predicate";
|
|
1
3
|
/** @internal */
|
|
2
4
|
export const SnapshotDeltaTypeId = "@effect-vfs/core/SnapshotDelta";
|
|
3
5
|
const snapshotDeltas = new WeakMap();
|
|
@@ -10,4 +12,4 @@ export const make = (value) => {
|
|
|
10
12
|
/** @internal */
|
|
11
13
|
export const value = (delta) => snapshotDeltas.get(delta);
|
|
12
14
|
/** @internal */
|
|
13
|
-
export const isSnapshotDelta = (input) =>
|
|
15
|
+
export const isSnapshotDelta = (input) => Predicate.isObject(input) && snapshotDeltas.has(input);
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import * as Result from "effect/Result";
|
|
1
2
|
import * as Schema from "effect/Schema";
|
|
2
3
|
import type { PathInput } from "../../VirtualFileSystem.js";
|
|
3
4
|
/** @internal */
|
|
@@ -13,6 +14,7 @@ export declare class FsError extends FsError_base<{
|
|
|
13
14
|
readonly operation: string;
|
|
14
15
|
readonly path?: PathInput;
|
|
15
16
|
}> {
|
|
17
|
+
get message(): string;
|
|
16
18
|
}
|
|
17
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 & {
|
|
18
20
|
readonly _tag: "ConfigurationError";
|
|
@@ -21,6 +23,9 @@ declare const ConfigurationError_base: new <A extends Record<string, any> = {}>(
|
|
|
21
23
|
export declare class ConfigurationError extends ConfigurationError_base<{
|
|
22
24
|
readonly field: string;
|
|
23
25
|
}> {
|
|
26
|
+
get message(): string;
|
|
24
27
|
}
|
|
28
|
+
/** @internal */
|
|
29
|
+
export declare const decodeConfiguration: <A>(schema: Schema.Codec<A>, value: typeof Schema.Unknown.Type) => Result.Result<A, ConfigurationError>;
|
|
25
30
|
export {};
|
|
26
31
|
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../../src/internal/virtualFileSystem/errors.ts"],"names":[],"mappings":"
|
|
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"}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
// Virtual filesystem failure schemas and identities shared by the runtime implementation.
|
|
2
2
|
import * as Data from "effect/Data";
|
|
3
|
+
import * as Predicate from "effect/Predicate";
|
|
4
|
+
import * as Result from "effect/Result";
|
|
3
5
|
import * as Schema from "effect/Schema";
|
|
4
6
|
/** @internal */
|
|
5
7
|
export const FsCode = Schema.Literals([
|
|
@@ -26,7 +28,24 @@ export const FsCode = Schema.Literals([
|
|
|
26
28
|
]);
|
|
27
29
|
/** @internal */
|
|
28
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
|
+
}
|
|
29
35
|
}
|
|
30
36
|
/** @internal */
|
|
31
37
|
export class ConfigurationError extends Data.TaggedError("ConfigurationError") {
|
|
38
|
+
get message() {
|
|
39
|
+
return `Invalid option: ${this.field}`;
|
|
40
|
+
}
|
|
32
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) })));
|
|
@@ -1 +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;
|
|
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"}
|
|
@@ -3,10 +3,13 @@ import * as Effect from "effect/Effect";
|
|
|
3
3
|
import * as Result from "effect/Result";
|
|
4
4
|
import * as Schema from "effect/Schema";
|
|
5
5
|
import { ImageError } from "../../Snapshot.js";
|
|
6
|
-
import {
|
|
6
|
+
import { CanonicalBase64 } from "../canonicalBase64.js";
|
|
7
7
|
import * as Image from "../image.js";
|
|
8
|
-
import { Fixture as FixtureSchema,
|
|
9
|
-
import {
|
|
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;
|
|
10
13
|
/** @internal */
|
|
11
14
|
export const fromFixture = Effect.fn("VirtualFileSystem.fromFixture")(function* (fixture, options) {
|
|
12
15
|
const config = decodeConfiguration(VolumeOptionsSchema, options ?? {});
|
|
@@ -16,34 +19,38 @@ export const fromFixture = Effect.fn("VirtualFileSystem.fromFixture")(function*
|
|
|
16
19
|
if (Result.isFailure(decoded))
|
|
17
20
|
return yield* new ImageError({ code: "InvalidStructure", field: "fixture" });
|
|
18
21
|
const source = decoded.success;
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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" });
|
|
22
29
|
}
|
|
23
30
|
const metadata = (kind, overrides) => ({
|
|
24
31
|
uid: overrides?.uid ?? 0,
|
|
25
32
|
gid: overrides?.gid ?? 0,
|
|
26
|
-
mode: overrides?.mode ??
|
|
27
|
-
atimeNs:
|
|
28
|
-
mtimeNs:
|
|
29
|
-
ctimeNs:
|
|
30
|
-
birthtimeNs:
|
|
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
|
|
31
38
|
});
|
|
32
39
|
const declarations = new Map();
|
|
33
40
|
const aliases = new Map();
|
|
34
41
|
const paths = new Map();
|
|
35
|
-
const root = {
|
|
42
|
+
const root = Image.Record.cases.directory.make({
|
|
36
43
|
id: "root",
|
|
37
|
-
kind: "directory",
|
|
38
44
|
metadata: metadata("directory", source.rootMetadata),
|
|
39
45
|
entries: []
|
|
40
|
-
};
|
|
46
|
+
});
|
|
41
47
|
declarations.set("", root);
|
|
42
48
|
const fixturePath = (input) => preparePath(input, "fixture", config.success.maxPathBytes).pipe(Result.flatMap((path) => !path.absolute || path.components.length === 0 ||
|
|
43
|
-
path.components.some(
|
|
44
|
-
? Result.fail(
|
|
49
|
+
path.components.some(isDotComponent)
|
|
50
|
+
? Result.fail(new FsError({ code: "InvalidArgument", operation: "fixture", path: input })) :
|
|
45
51
|
Result.succeed(path.components)));
|
|
46
|
-
//
|
|
52
|
+
// Encode caller-owned byte buffers now: nothing below yields until Image.capture, so a caller
|
|
53
|
+
// cannot mutate them first.
|
|
47
54
|
for (const entry of source.entries) {
|
|
48
55
|
const parsed = fixturePath(entry.path);
|
|
49
56
|
if (Result.isFailure(parsed)) {
|
|
@@ -54,51 +61,46 @@ export const fromFixture = Effect.fn("VirtualFileSystem.fromFixture")(function*
|
|
|
54
61
|
if (paths.has(key))
|
|
55
62
|
return yield* new ImageError({ code: "InvalidStructure", field: "duplicate" });
|
|
56
63
|
paths.set(key, components);
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
});
|
|
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
|
+
}));
|
|
94
100
|
}
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
metadata: metadata("symlink", entry.metadata),
|
|
99
|
-
target: Image.base64(target)
|
|
100
|
-
});
|
|
101
|
-
}
|
|
101
|
+
});
|
|
102
|
+
if (error instanceof ImageError)
|
|
103
|
+
return yield* error;
|
|
102
104
|
}
|
|
103
105
|
for (const [key] of aliases) {
|
|
104
106
|
let target = key;
|
|
@@ -113,11 +115,8 @@ export const fromFixture = Effect.fn("VirtualFileSystem.fromFixture")(function*
|
|
|
113
115
|
target = next;
|
|
114
116
|
}
|
|
115
117
|
const node = declarations.get(target);
|
|
116
|
-
if (node === undefined ||
|
|
117
|
-
return yield* new ImageError({
|
|
118
|
-
code: "InvalidStructure",
|
|
119
|
-
field: "hardLink"
|
|
120
|
-
});
|
|
118
|
+
if (node === undefined || Image.Record.guards.directory(node)) {
|
|
119
|
+
return yield* new ImageError({ code: "InvalidStructure", field: "hardLink" });
|
|
121
120
|
}
|
|
122
121
|
for (const alias of seen)
|
|
123
122
|
declarations.set(alias, node);
|
|
@@ -127,18 +126,19 @@ export const fromFixture = Effect.fn("VirtualFileSystem.fromFixture")(function*
|
|
|
127
126
|
const parent = declarations.get(components.slice(0, -1).join("/"));
|
|
128
127
|
const child = declarations.get(key);
|
|
129
128
|
const name = components.at(-1);
|
|
130
|
-
if (parent?.
|
|
129
|
+
if (parent?._tag !== "directory" || child === undefined || name === undefined) {
|
|
131
130
|
return yield* new ImageError({ code: "InvalidStructure", field: "parent" });
|
|
132
131
|
}
|
|
133
132
|
const entries = children.get(parent.id) ?? [];
|
|
134
|
-
entries.push({ name:
|
|
133
|
+
entries.push({ name: CanonicalBase64.encode(nameBytes(name)), target: child.id });
|
|
135
134
|
children.set(parent.id, entries);
|
|
136
135
|
}
|
|
137
|
-
const records = [...new Set(declarations.values())].map((record) =>
|
|
138
|
-
|
|
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);
|
|
139
142
|
const image = yield* Image.inspect(snapshot);
|
|
140
|
-
|
|
141
|
-
if (result._tag === "Overlay")
|
|
142
|
-
return yield* new ImageError({ code: "InvalidStructure" });
|
|
143
|
-
return result.volume;
|
|
143
|
+
return yield* makeVolume(VolumeSource.Snapshot({ image }), config.success);
|
|
144
144
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"overlayContent.d.ts","sourceRoot":"","sources":["../../../src/internal/virtualFileSystem/overlayContent.ts"],"names":[],"mappings":"
|
|
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"}
|
|
@@ -1,3 +1,7 @@
|
|
|
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";
|
|
1
5
|
import * as Image from "../image.js";
|
|
2
6
|
const overlayContents = new WeakMap();
|
|
3
7
|
/** @internal */
|
|
@@ -14,8 +18,11 @@ export const forOverlay = (snapshot, image) => {
|
|
|
14
18
|
return cached;
|
|
15
19
|
const decoded = new Map();
|
|
16
20
|
for (const record of image.records) {
|
|
17
|
-
if (
|
|
18
|
-
|
|
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
|
+
}
|
|
19
26
|
}
|
|
20
27
|
overlayContents.set(snapshot, decoded);
|
|
21
28
|
return decoded;
|
|
@@ -1,20 +1,11 @@
|
|
|
1
|
-
|
|
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:
|
|
8
|
+
readonly metadata: StoredMetadata;
|
|
18
9
|
}
|
|
19
10
|
/** @internal */
|
|
20
11
|
export type OverlayDifference = "content" | "mode" | "uid" | "gid" | "atimeNs" | "mtimeNs" | "ctimeNs" | "birthtimeNs";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"overlayDiff.d.ts","sourceRoot":"","sources":["../../../src/internal/virtualFileSystem/overlayDiff.ts"],"names":[],"mappings":"
|
|
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"}
|
|
@@ -1,5 +1,9 @@
|
|
|
1
|
+
import * as Data from "effect/Data";
|
|
1
2
|
import * as Encoding from "effect/Encoding";
|
|
2
|
-
import * as
|
|
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();
|
|
3
7
|
const timestampFields = new Set(["atimeNs", "mtimeNs", "ctimeNs", "birthtimeNs"]);
|
|
4
8
|
const differenceOrder = [
|
|
5
9
|
"content",
|
|
@@ -19,25 +23,17 @@ const tagOrder = {
|
|
|
19
23
|
Updated: 4
|
|
20
24
|
};
|
|
21
25
|
const key = Encoding.encodeHex;
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
if (difference !== 0)
|
|
27
|
-
return difference;
|
|
28
|
-
}
|
|
29
|
-
return left.length - right.length;
|
|
30
|
-
};
|
|
31
|
-
const sameBytes = (left, right) => {
|
|
32
|
-
return Equal.equals(left, right);
|
|
33
|
-
};
|
|
34
|
-
const differences = (before, after, includeTimestamps) => differenceOrder.filter((field) => {
|
|
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) => {
|
|
35
30
|
if (!includeTimestamps && timestampFields.has(field))
|
|
36
31
|
return false;
|
|
32
|
+
// A kind change always counts as a content change, even when the bytes happen to match.
|
|
37
33
|
if (field === "content")
|
|
38
34
|
return before.kind !== after.kind || !sameBytes(before.content, after.content);
|
|
39
35
|
return before.metadata[field] !== after.metadata[field];
|
|
40
|
-
});
|
|
36
|
+
}));
|
|
41
37
|
const byLineage = (entries) => {
|
|
42
38
|
const result = new Map();
|
|
43
39
|
for (const entry of entries) {
|
|
@@ -51,96 +47,74 @@ const byLineage = (entries) => {
|
|
|
51
47
|
}
|
|
52
48
|
return result;
|
|
53
49
|
};
|
|
54
|
-
const copyChange = (change) => {
|
|
55
|
-
switch (change._tag) {
|
|
56
|
-
case "Added":
|
|
57
|
-
case "Removed":
|
|
58
|
-
return Object.freeze({ ...change, path: change.path.slice() });
|
|
59
|
-
case "Replaced":
|
|
60
|
-
case "Updated":
|
|
61
|
-
return Object.freeze({
|
|
62
|
-
...change,
|
|
63
|
-
path: change.path.slice(),
|
|
64
|
-
differences: Object.freeze([...change.differences])
|
|
65
|
-
});
|
|
66
|
-
case "Renamed":
|
|
67
|
-
return Object.freeze({
|
|
68
|
-
...change,
|
|
69
|
-
from: change.from.slice(),
|
|
70
|
-
to: change.to.slice(),
|
|
71
|
-
differences: Object.freeze([...change.differences])
|
|
72
|
-
});
|
|
73
|
-
}
|
|
74
|
-
};
|
|
75
50
|
/** @internal */
|
|
76
51
|
export const compareOverlay = (base, current, includeTimestamps = false) => {
|
|
77
52
|
const basePaths = new Map(base.map((entry) => [key(entry.path), entry]));
|
|
78
53
|
const currentPaths = new Map(current.map((entry) => [key(entry.path), entry]));
|
|
79
54
|
const baseLineages = byLineage(base);
|
|
80
55
|
const currentLineages = byLineage(current);
|
|
81
|
-
const
|
|
82
|
-
const
|
|
56
|
+
const renameSources = new Set();
|
|
57
|
+
const renameTargets = new Set();
|
|
83
58
|
const changes = [];
|
|
84
59
|
for (const [lineage, beforeEntries] of baseLineages) {
|
|
85
60
|
const afterEntries = currentLineages.get(lineage);
|
|
86
61
|
if (afterEntries === undefined)
|
|
87
62
|
continue;
|
|
88
|
-
const removed = [
|
|
89
|
-
const
|
|
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
|
+
}
|
|
90
73
|
if (removed.length !== 1 || added.length !== 1)
|
|
91
74
|
continue;
|
|
92
75
|
const before = removed[0];
|
|
93
76
|
const after = added[0];
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
changes.push({
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
to: after.path,
|
|
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(),
|
|
100
82
|
kind: after.kind,
|
|
101
83
|
differences: differences(before, after, includeTimestamps)
|
|
102
|
-
});
|
|
84
|
+
}));
|
|
103
85
|
}
|
|
104
86
|
for (const before of base) {
|
|
105
87
|
const pathKey = key(before.path);
|
|
106
|
-
if (
|
|
88
|
+
if (renameSources.has(pathKey))
|
|
107
89
|
continue;
|
|
108
90
|
const after = currentPaths.get(pathKey);
|
|
109
|
-
|
|
110
|
-
|
|
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 }));
|
|
111
94
|
continue;
|
|
112
95
|
}
|
|
113
|
-
consumedCurrent.add(pathKey);
|
|
114
96
|
if (before.lineage === undefined || before.lineage !== after.lineage) {
|
|
115
|
-
changes.push({
|
|
116
|
-
|
|
117
|
-
path: before.path,
|
|
97
|
+
changes.push(RawOverlayChange.Replaced({
|
|
98
|
+
path: before.path.slice(),
|
|
118
99
|
beforeKind: before.kind,
|
|
119
100
|
afterKind: after.kind,
|
|
120
101
|
differences: differences(before, after, includeTimestamps)
|
|
121
|
-
});
|
|
102
|
+
}));
|
|
122
103
|
continue;
|
|
123
104
|
}
|
|
124
105
|
const changed = differences(before, after, includeTimestamps);
|
|
125
|
-
if (changed.length > 0)
|
|
126
|
-
changes.push({
|
|
106
|
+
if (changed.length > 0) {
|
|
107
|
+
changes.push(RawOverlayChange.Updated({ path: before.path.slice(), kind: after.kind, differences: changed }));
|
|
108
|
+
}
|
|
127
109
|
}
|
|
128
110
|
for (const after of current) {
|
|
129
111
|
const pathKey = key(after.path);
|
|
130
|
-
|
|
131
|
-
|
|
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 }));
|
|
132
115
|
}
|
|
133
116
|
}
|
|
134
|
-
changes.sort(
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
const pathOrder = comparePaths(leftPath, rightPath);
|
|
138
|
-
if (pathOrder !== 0)
|
|
139
|
-
return pathOrder;
|
|
140
|
-
const typeOrder = tagOrder[left._tag] - tagOrder[right._tag];
|
|
141
|
-
if (typeOrder !== 0)
|
|
142
|
-
return typeOrder;
|
|
143
|
-
return left._tag === "Renamed" && right._tag === "Renamed" ? comparePaths(left.to, right.to) : 0;
|
|
144
|
-
});
|
|
145
|
-
return Object.freeze(changes.map(copyChange));
|
|
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)));
|
|
146
120
|
};
|
|
@@ -1,12 +1,21 @@
|
|
|
1
1
|
import * as ByteSize from "effect/ByteSize";
|
|
2
2
|
import * as Effect from "effect/Effect";
|
|
3
3
|
import * as Result from "effect/Result";
|
|
4
|
-
import
|
|
5
|
-
import { type BytePath } from "../../BytePath.js";
|
|
4
|
+
import type { BytePath } from "../../BytePath.js";
|
|
6
5
|
import type { PathInput } from "../../VirtualFileSystem.js";
|
|
7
|
-
import {
|
|
6
|
+
import { FsError } from "./errors.js";
|
|
8
7
|
/** @internal */
|
|
9
|
-
export declare const
|
|
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;
|
|
10
19
|
/** @internal */
|
|
11
20
|
export declare const ownedPath: (bytes: Uint8Array) => BytePath;
|
|
12
21
|
/** @internal */
|
|
@@ -14,18 +23,12 @@ export declare const strictString: (bytes: Uint8Array, operation: string) => Eff
|
|
|
14
23
|
/** @internal */
|
|
15
24
|
export declare const nameBytes: (name: string) => Uint8Array;
|
|
16
25
|
/** @internal */
|
|
17
|
-
export declare const
|
|
26
|
+
export declare const isAttachedBytes: (bytes: Uint8Array) => boolean;
|
|
18
27
|
/** @internal */
|
|
19
28
|
export declare const pathFromBytes: (bytes: Uint8Array<ArrayBufferLike>) => Effect.Effect<BytePath, FsError, never>;
|
|
20
29
|
/** @internal */
|
|
21
30
|
export declare const pathToBytes: (path: BytePath) => Effect.Effect<Uint8Array<ArrayBuffer>, FsError, never>;
|
|
22
31
|
/** @internal */
|
|
23
|
-
export interface LookupOptions {
|
|
24
|
-
readonly followFinalSymlink?: boolean;
|
|
25
|
-
readonly allowMissing?: boolean;
|
|
26
|
-
readonly parentOnly?: boolean;
|
|
27
|
-
}
|
|
28
|
-
/** @internal */
|
|
29
32
|
export interface PreparedPath {
|
|
30
33
|
readonly input: PathInput;
|
|
31
34
|
readonly absolute: boolean;
|
|
@@ -35,9 +38,7 @@ export interface PreparedPath {
|
|
|
35
38
|
readonly components: ReadonlyArray<string>;
|
|
36
39
|
}
|
|
37
40
|
/** @internal */
|
|
38
|
-
export declare const
|
|
41
|
+
export declare const inputBytes: (input: PathInput) => Result.Result<Uint8Array, "InvalidPathEncoding" | "InvalidArgument">;
|
|
39
42
|
/** @internal */
|
|
40
43
|
export declare const preparePath: (input: PathInput, operation: string, maxPathBytes: ByteSize.ByteSize | undefined) => Result.Result<PreparedPath, FsError>;
|
|
41
|
-
/** @internal */
|
|
42
|
-
export declare const decodeConfiguration: <A>(schema: Schema.Codec<A>, value: unknown) => Result.Result<A, ConfigurationError>;
|
|
43
44
|
//# sourceMappingURL=path.d.ts.map
|
|
@@ -1 +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;
|
|
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"}
|