@effect-vfs/core 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/BytePath.d.ts +18 -5
- package/dist/BytePath.d.ts.map +1 -1
- package/dist/BytePath.js +9 -9
- package/dist/Snapshot.d.ts +3 -3
- package/dist/Snapshot.d.ts.map +1 -1
- package/dist/Snapshot.js +3 -4
- package/dist/SnapshotDelta.d.ts +97 -30
- package/dist/SnapshotDelta.d.ts.map +1 -1
- package/dist/SnapshotDelta.js +51 -24
- package/dist/VirtualFileSystem.d.ts +122 -344
- package/dist/VirtualFileSystem.d.ts.map +1 -1
- package/dist/VirtualFileSystem.js +53 -1773
- package/dist/internal/bytePath.d.ts +10 -0
- package/dist/internal/bytePath.d.ts.map +1 -0
- package/dist/internal/bytePath.js +46 -0
- package/dist/internal/canonicalBase64.d.ts +3 -3
- package/dist/internal/canonicalBase64.d.ts.map +1 -1
- package/dist/internal/canonicalBase64.js +3 -3
- package/dist/internal/image.d.ts +21 -26
- package/dist/internal/image.d.ts.map +1 -1
- package/dist/internal/image.js +4 -9
- package/dist/internal/snapshotDelta.d.ts +0 -1
- package/dist/internal/snapshotDelta.d.ts.map +1 -1
- package/dist/internal/snapshotDelta.js +15 -28
- package/dist/internal/snapshotDeltaModel.d.ts +10 -0
- package/dist/internal/snapshotDeltaModel.d.ts.map +1 -0
- package/dist/internal/snapshotDeltaModel.js +13 -0
- package/dist/internal/virtualFileSystem/errors.d.ts +26 -0
- package/dist/internal/virtualFileSystem/errors.d.ts.map +1 -0
- package/dist/internal/virtualFileSystem/errors.js +32 -0
- package/dist/internal/virtualFileSystem/fixture.d.ts +63 -0
- package/dist/internal/virtualFileSystem/fixture.d.ts.map +1 -0
- package/dist/internal/virtualFileSystem/fixture.js +144 -0
- package/dist/internal/{content.d.ts → virtualFileSystem/overlayContent.d.ts} +4 -14
- package/dist/internal/virtualFileSystem/overlayContent.d.ts.map +1 -0
- package/dist/internal/{content.js → virtualFileSystem/overlayContent.js} +3 -7
- package/dist/internal/{overlayChanges.d.ts → virtualFileSystem/overlayDiff.d.ts} +1 -1
- package/dist/internal/virtualFileSystem/overlayDiff.d.ts.map +1 -0
- package/dist/internal/virtualFileSystem/path.d.ts +43 -0
- package/dist/internal/virtualFileSystem/path.d.ts.map +1 -0
- package/dist/internal/virtualFileSystem/path.js +124 -0
- package/dist/internal/virtualFileSystem/testHooks.d.ts +20 -0
- package/dist/internal/virtualFileSystem/testHooks.d.ts.map +1 -0
- package/dist/internal/virtualFileSystem/testHooks.js +23 -0
- package/dist/internal/virtualFileSystem/watchHub.d.ts +16 -0
- package/dist/internal/virtualFileSystem/watchHub.d.ts.map +1 -0
- package/dist/internal/virtualFileSystem/watchHub.js +33 -0
- package/dist/internal/virtualFileSystem.d.ts +290 -0
- package/dist/internal/virtualFileSystem.d.ts.map +1 -0
- package/dist/internal/virtualFileSystem.js +1677 -0
- package/package.json +1 -1
- package/dist/internal/content.d.ts.map +0 -1
- package/dist/internal/overlayChanges.d.ts.map +0 -1
- package/dist/internal/overlayTesting.d.ts +0 -12
- package/dist/internal/overlayTesting.d.ts.map +0 -1
- package/dist/internal/overlayTesting.js +0 -12
- /package/dist/internal/{overlayChanges.js → virtualFileSystem/overlayDiff.js} +0 -0
|
@@ -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 { getBytes as getBytePathBytes } from "../bytePath.js";
|
|
7
|
+
import * as Image from "../image.js";
|
|
8
|
+
import { Fixture as FixtureSchema, FixtureMetadata, makeVolume, VolumeOptions as VolumeOptionsSchema } from "../virtualFileSystem.js";
|
|
9
|
+
import { attachedBuffer, decodeConfiguration, failure, nameBytes, preparePath, wellFormed } from "./path.js";
|
|
10
|
+
/** @internal */
|
|
11
|
+
export const fromFixture = Effect.fn("VirtualFileSystem.fromFixture")(function* (fixture, options) {
|
|
12
|
+
const config = decodeConfiguration(VolumeOptionsSchema, options ?? {});
|
|
13
|
+
if (Result.isFailure(config))
|
|
14
|
+
return yield* config.failure;
|
|
15
|
+
const decoded = Schema.decodeResult(FixtureSchema, { onExcessProperty: "error" })(fixture);
|
|
16
|
+
if (Result.isFailure(decoded))
|
|
17
|
+
return yield* new ImageError({ code: "InvalidStructure", field: "fixture" });
|
|
18
|
+
const source = decoded.success;
|
|
19
|
+
for (const entry of source.entries) {
|
|
20
|
+
if (entry.kind === "file" && (!(entry.bytes.buffer instanceof ArrayBuffer) || !attachedBuffer(entry.bytes)))
|
|
21
|
+
return yield* new ImageError({ code: "InvalidEncoding", field: "bytes" });
|
|
22
|
+
}
|
|
23
|
+
const metadata = (kind, overrides) => ({
|
|
24
|
+
uid: overrides?.uid ?? 0,
|
|
25
|
+
gid: overrides?.gid ?? 0,
|
|
26
|
+
mode: overrides?.mode ?? (kind === "directory" ? 0o755 : kind === "file" ? 0o644 : 0o777),
|
|
27
|
+
atimeNs: String(overrides?.atimeNs ?? 0n),
|
|
28
|
+
mtimeNs: String(overrides?.mtimeNs ?? 0n),
|
|
29
|
+
ctimeNs: String(overrides?.ctimeNs ?? 0n),
|
|
30
|
+
birthtimeNs: String(overrides?.birthtimeNs ?? 0n)
|
|
31
|
+
});
|
|
32
|
+
const declarations = new Map();
|
|
33
|
+
const aliases = new Map();
|
|
34
|
+
const paths = new Map();
|
|
35
|
+
const root = {
|
|
36
|
+
id: "root",
|
|
37
|
+
kind: "directory",
|
|
38
|
+
metadata: metadata("directory", source.rootMetadata),
|
|
39
|
+
entries: []
|
|
40
|
+
};
|
|
41
|
+
declarations.set("", root);
|
|
42
|
+
const fixturePath = (input) => preparePath(input, "fixture", config.success.maxPathBytes).pipe(Result.flatMap((path) => !path.absolute || path.components.length === 0 ||
|
|
43
|
+
path.components.some((name) => name === "2e" || name === "2e2e")
|
|
44
|
+
? Result.fail(failure("InvalidArgument", "fixture", input)) :
|
|
45
|
+
Result.succeed(path.components)));
|
|
46
|
+
// All byte inputs become immutable strings before the first successful suspension.
|
|
47
|
+
for (const entry of source.entries) {
|
|
48
|
+
const parsed = fixturePath(entry.path);
|
|
49
|
+
if (Result.isFailure(parsed)) {
|
|
50
|
+
return yield* new ImageError({ code: "InvalidStructure", field: "path" });
|
|
51
|
+
}
|
|
52
|
+
const components = parsed.success;
|
|
53
|
+
const key = components.join("/");
|
|
54
|
+
if (paths.has(key))
|
|
55
|
+
return yield* new ImageError({ code: "InvalidStructure", field: "duplicate" });
|
|
56
|
+
paths.set(key, components);
|
|
57
|
+
if (entry.kind === "hardLink") {
|
|
58
|
+
const target = fixturePath(entry.target);
|
|
59
|
+
if (Result.isFailure(target))
|
|
60
|
+
return yield* new ImageError({ code: "InvalidStructure", field: "target" });
|
|
61
|
+
aliases.set(key, target.success.join("/"));
|
|
62
|
+
}
|
|
63
|
+
else if (entry.kind === "directory") {
|
|
64
|
+
declarations.set(key, {
|
|
65
|
+
id: String(paths.size),
|
|
66
|
+
kind: "directory",
|
|
67
|
+
metadata: metadata("directory", entry.metadata),
|
|
68
|
+
entries: []
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
else if (entry.kind === "file") {
|
|
72
|
+
declarations.set(key, {
|
|
73
|
+
id: String(paths.size),
|
|
74
|
+
kind: "file",
|
|
75
|
+
metadata: metadata("file", entry.metadata),
|
|
76
|
+
data: Image.base64(entry.bytes)
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
if (typeof entry.target === "string" && !wellFormed(entry.target)) {
|
|
81
|
+
return yield* new ImageError({
|
|
82
|
+
code: "InvalidEncoding",
|
|
83
|
+
field: "target"
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
const target = typeof entry.target === "string"
|
|
87
|
+
? new TextEncoder().encode(entry.target)
|
|
88
|
+
: getBytePathBytes(entry.target);
|
|
89
|
+
if (target === undefined || target.includes(0)) {
|
|
90
|
+
return yield* new ImageError({
|
|
91
|
+
code: "InvalidStructure",
|
|
92
|
+
field: "target"
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
declarations.set(key, {
|
|
96
|
+
id: String(paths.size),
|
|
97
|
+
kind: "symlink",
|
|
98
|
+
metadata: metadata("symlink", entry.metadata),
|
|
99
|
+
target: Image.base64(target)
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
for (const [key] of aliases) {
|
|
104
|
+
let target = key;
|
|
105
|
+
const seen = new Set();
|
|
106
|
+
while (!declarations.has(target)) {
|
|
107
|
+
if (seen.has(target))
|
|
108
|
+
return yield* new ImageError({ code: "InvalidStructure", field: "hardLink" });
|
|
109
|
+
seen.add(target);
|
|
110
|
+
const next = aliases.get(target);
|
|
111
|
+
if (next === undefined)
|
|
112
|
+
return yield* new ImageError({ code: "InvalidStructure", field: "hardLink" });
|
|
113
|
+
target = next;
|
|
114
|
+
}
|
|
115
|
+
const node = declarations.get(target);
|
|
116
|
+
if (node === undefined || node.kind === "directory") {
|
|
117
|
+
return yield* new ImageError({
|
|
118
|
+
code: "InvalidStructure",
|
|
119
|
+
field: "hardLink"
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
for (const alias of seen)
|
|
123
|
+
declarations.set(alias, node);
|
|
124
|
+
}
|
|
125
|
+
const children = new Map();
|
|
126
|
+
for (const [key, components] of paths) {
|
|
127
|
+
const parent = declarations.get(components.slice(0, -1).join("/"));
|
|
128
|
+
const child = declarations.get(key);
|
|
129
|
+
const name = components.at(-1);
|
|
130
|
+
if (parent?.kind !== "directory" || child === undefined || name === undefined) {
|
|
131
|
+
return yield* new ImageError({ code: "InvalidStructure", field: "parent" });
|
|
132
|
+
}
|
|
133
|
+
const entries = children.get(parent.id) ?? [];
|
|
134
|
+
entries.push({ name: Image.base64(nameBytes(name)), target: child.id });
|
|
135
|
+
children.set(parent.id, entries);
|
|
136
|
+
}
|
|
137
|
+
const records = [...new Set(declarations.values())].map((record) => record.kind === "directory" ? { ...record, entries: children.get(record.id) ?? [] } : record);
|
|
138
|
+
const snapshot = yield* Image.capture({ format: "effect-vfs", version: 1, root: "root", records });
|
|
139
|
+
const image = yield* Image.inspect(snapshot);
|
|
140
|
+
const result = yield* makeVolume({ _tag: "Snapshot", image }, config.success);
|
|
141
|
+
if (result._tag === "Overlay")
|
|
142
|
+
return yield* new ImageError({ code: "InvalidStructure" });
|
|
143
|
+
return result.volume;
|
|
144
|
+
});
|
|
@@ -1,10 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
*
|
|
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=
|
|
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":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAA;AACjD,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,CASjG,CAAA"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import * as Image from "
|
|
1
|
+
import * as Image from "../image.js";
|
|
2
2
|
const overlayContents = new WeakMap();
|
|
3
3
|
/** @internal */
|
|
4
4
|
export const hasOverlayContents = (snapshot) => overlayContents.has(snapshot);
|
|
@@ -6,12 +6,8 @@ export const hasOverlayContents = (snapshot) => overlayContents.has(snapshot);
|
|
|
6
6
|
export const make = (bytes) => ({ bytes });
|
|
7
7
|
/** @internal */
|
|
8
8
|
export const empty = () => make(new Uint8Array(0));
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
* snapshot. The cache is weak so it cannot extend the snapshot's lifetime.
|
|
12
|
-
*
|
|
13
|
-
* @internal
|
|
14
|
-
*/
|
|
9
|
+
// The weak cache shares immutable payloads without extending the snapshot's lifetime.
|
|
10
|
+
/** @internal */
|
|
15
11
|
export const forOverlay = (snapshot, image) => {
|
|
16
12
|
const cached = overlayContents.get(snapshot);
|
|
17
13
|
if (cached !== undefined)
|
|
@@ -47,4 +47,4 @@ export type RawOverlayChange = {
|
|
|
47
47
|
};
|
|
48
48
|
/** @internal */
|
|
49
49
|
export declare const compareOverlay: (base: ReadonlyArray<ObservationEntry>, current: ReadonlyArray<ObservationEntry>, includeTimestamps?: boolean) => ReadonlyArray<RawOverlayChange>;
|
|
50
|
-
//# sourceMappingURL=
|
|
50
|
+
//# sourceMappingURL=overlayDiff.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"overlayDiff.d.ts","sourceRoot":"","sources":["../../../src/internal/virtualFileSystem/overlayDiff.ts"],"names":[],"mappings":"AAGA,gBAAgB;AAChB,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;CAC7B;AAED,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,mBAAmB,CAAA;CACvC;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;AA4EH,gBAAgB;AAChB,eAAO,MAAM,cAAc,SACnB,aAAa,CAAC,gBAAgB,CAAC,WAC5B,aAAa,CAAC,gBAAgB,CAAC,kCAEvC,aAAa,CAAC,gBAAgB,CAoEhC,CAAA"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import * as ByteSize from "effect/ByteSize";
|
|
2
|
+
import * as Effect from "effect/Effect";
|
|
3
|
+
import * as Result from "effect/Result";
|
|
4
|
+
import * as Schema from "effect/Schema";
|
|
5
|
+
import { type BytePath } from "../../BytePath.js";
|
|
6
|
+
import type { PathInput } from "../../VirtualFileSystem.js";
|
|
7
|
+
import { ConfigurationError, type FsCode, FsError } from "./errors.js";
|
|
8
|
+
/** @internal */
|
|
9
|
+
export declare const failure: (code: FsCode, operation: string, path?: PathInput) => FsError;
|
|
10
|
+
/** @internal */
|
|
11
|
+
export declare const ownedPath: (bytes: Uint8Array) => BytePath;
|
|
12
|
+
/** @internal */
|
|
13
|
+
export declare const strictString: (bytes: Uint8Array, operation: string) => Effect.Effect<string, FsError, never>;
|
|
14
|
+
/** @internal */
|
|
15
|
+
export declare const nameBytes: (name: string) => Uint8Array;
|
|
16
|
+
/** @internal */
|
|
17
|
+
export declare const attachedBuffer: (bytes: Uint8Array) => boolean;
|
|
18
|
+
/** @internal */
|
|
19
|
+
export declare const pathFromBytes: (bytes: Uint8Array<ArrayBufferLike>) => Effect.Effect<BytePath, FsError, never>;
|
|
20
|
+
/** @internal */
|
|
21
|
+
export declare const pathToBytes: (path: BytePath) => Effect.Effect<Uint8Array<ArrayBuffer>, FsError, never>;
|
|
22
|
+
/** @internal */
|
|
23
|
+
export interface LookupOptions {
|
|
24
|
+
readonly followFinalSymlink?: boolean;
|
|
25
|
+
readonly allowMissing?: boolean;
|
|
26
|
+
readonly parentOnly?: boolean;
|
|
27
|
+
}
|
|
28
|
+
/** @internal */
|
|
29
|
+
export interface PreparedPath {
|
|
30
|
+
readonly input: PathInput;
|
|
31
|
+
readonly absolute: boolean;
|
|
32
|
+
readonly trailingSlash: boolean;
|
|
33
|
+
readonly bytes: Uint8Array;
|
|
34
|
+
readonly suffixes: ReadonlyArray<Uint8Array>;
|
|
35
|
+
readonly components: ReadonlyArray<string>;
|
|
36
|
+
}
|
|
37
|
+
/** @internal */
|
|
38
|
+
export declare const wellFormed: (value: string) => boolean;
|
|
39
|
+
/** @internal */
|
|
40
|
+
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
|
+
//# 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;AAEvC,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AACvC,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AAEvC,OAAO,EAAE,KAAK,QAAQ,EAAE,MAAM,mBAAmB,CAAA;AACjD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAA;AAE3D,OAAO,EAAE,kBAAkB,EAAE,KAAK,MAAM,EAAE,OAAO,EAAE,MAAM,aAAa,CAAA;AAEtE,gBAAgB;AAChB,eAAO,MAAM,OAAO,SAAU,MAAM,aAAa,MAAM,SAAS,SAAS,YACE,CAAA;AAE3E,gBAAgB;AAChB,eAAO,MAAM,SAAS,UAAW,UAAU,KAAG,QAE7C,CAAA;AAED,gBAAgB;AAChB,eAAO,MAAM,YAAY,UAAW,UAAU,aAAa,MAAM,0CAI7D,CAAA;AAEJ,gBAAgB;AAChB,eAAO,MAAM,SAAS,SAAU,MAAM,KAAG,UAExC,CAAA;AAGD,gBAAgB;AAChB,eAAO,MAAM,cAAc,UAAW,UAAU,KAAG,OAQlD,CAAA;AAED,gBAAgB;AAChB,eAAO,MAAM,aAAa,iFAQxB,CAAA;AAEF,gBAAgB;AAChB,eAAO,MAAM,WAAW,4EAItB,CAAA;AAEF,gBAAgB;AAChB,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,kBAAkB,CAAC,EAAE,OAAO,CAAA;IACrC,QAAQ,CAAC,YAAY,CAAC,EAAE,OAAO,CAAA;IAC/B,QAAQ,CAAC,UAAU,CAAC,EAAE,OAAO,CAAA;CAC9B;AAED,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;AAED,gBAAgB;AAChB,eAAO,MAAM,UAAU,UAAW,MAAM,KAAG,OAS1C,CAAA;AAED,gBAAgB;AAChB,eAAO,MAAM,WAAW,UACf,SAAS,aACL,MAAM,gBACH,QAAQ,CAAC,QAAQ,GAAG,SAAS,KAC1C,MAAM,CAAC,MAAM,CAAC,YAAY,EAAE,OAAO,CAmCrC,CAAA;AAQD,gBAAgB;AAChB,eAAO,MAAM,mBAAmB,GAAI,CAAC,UAAU,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,OAAO,yCAG3E,CAAA"}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
// Byte-preserving path validation and conversion.
|
|
2
|
+
import * as ByteSize from "effect/ByteSize";
|
|
3
|
+
import * as Effect from "effect/Effect";
|
|
4
|
+
import * as Encoding from "effect/Encoding";
|
|
5
|
+
import * as Result from "effect/Result";
|
|
6
|
+
import * as Schema from "effect/Schema";
|
|
7
|
+
import * as SchemaIssue from "effect/SchemaIssue";
|
|
8
|
+
import {} from "../../BytePath.js";
|
|
9
|
+
import { getBytes as getBytePathBytes, make as makeBytePath } from "../bytePath.js";
|
|
10
|
+
import { ConfigurationError, FsError } from "./errors.js";
|
|
11
|
+
/** @internal */
|
|
12
|
+
export const failure = (code, operation, path) => new FsError({ code, operation, ...(path === undefined ? {} : { path }) });
|
|
13
|
+
/** @internal */
|
|
14
|
+
export const ownedPath = (bytes) => {
|
|
15
|
+
return makeBytePath(bytes);
|
|
16
|
+
};
|
|
17
|
+
/** @internal */
|
|
18
|
+
export const strictString = (bytes, operation) => Effect.try({
|
|
19
|
+
try: () => new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(bytes),
|
|
20
|
+
catch: () => failure("UnrepresentableName", operation)
|
|
21
|
+
});
|
|
22
|
+
/** @internal */
|
|
23
|
+
export const nameBytes = (name) => {
|
|
24
|
+
return Result.getOrThrow(Encoding.decodeHex(name));
|
|
25
|
+
};
|
|
26
|
+
// A zero-length view distinguishes a detached buffer from a valid empty buffer.
|
|
27
|
+
/** @internal */
|
|
28
|
+
export const attachedBuffer = (bytes) => {
|
|
29
|
+
try {
|
|
30
|
+
new Uint8Array(bytes.buffer, bytes.byteOffset, 0);
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
33
|
+
catch (error) {
|
|
34
|
+
if (error instanceof TypeError)
|
|
35
|
+
return false;
|
|
36
|
+
throw error;
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
/** @internal */
|
|
40
|
+
export const pathFromBytes = Effect.fn("VirtualFileSystem.pathFromBytes")(function* (bytes) {
|
|
41
|
+
if (!(bytes instanceof Uint8Array) || !(bytes.buffer instanceof ArrayBuffer)) {
|
|
42
|
+
return yield* failure("InvalidArgument", "pathFromBytes");
|
|
43
|
+
}
|
|
44
|
+
if (!attachedBuffer(bytes))
|
|
45
|
+
return yield* failure("InvalidArgument", "pathFromBytes");
|
|
46
|
+
const owned = new Uint8Array(bytes);
|
|
47
|
+
if (owned.length === 0 || owned.includes(0))
|
|
48
|
+
return yield* failure("InvalidArgument", "pathFromBytes");
|
|
49
|
+
return makeBytePath(owned);
|
|
50
|
+
});
|
|
51
|
+
/** @internal */
|
|
52
|
+
export const pathToBytes = Effect.fn("VirtualFileSystem.pathToBytes")(function* (path) {
|
|
53
|
+
const bytes = getBytePathBytes(path);
|
|
54
|
+
if (bytes === undefined)
|
|
55
|
+
return yield* failure("InvalidArgument", "pathToBytes");
|
|
56
|
+
return new Uint8Array(bytes);
|
|
57
|
+
});
|
|
58
|
+
/** @internal */
|
|
59
|
+
export const wellFormed = (value) => {
|
|
60
|
+
for (let index = 0; index < value.length; index++) {
|
|
61
|
+
const code = value.charCodeAt(index);
|
|
62
|
+
if (code >= 0xd800 && code <= 0xdbff) {
|
|
63
|
+
const next = value.charCodeAt(++index);
|
|
64
|
+
if (!(next >= 0xdc00 && next <= 0xdfff))
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
else if (code >= 0xdc00 && code <= 0xdfff)
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
return true;
|
|
71
|
+
};
|
|
72
|
+
/** @internal */
|
|
73
|
+
export const preparePath = (input, operation, maxPathBytes) => {
|
|
74
|
+
let bytes;
|
|
75
|
+
if (typeof input === "string") {
|
|
76
|
+
if (!wellFormed(input))
|
|
77
|
+
return Result.fail(failure("InvalidPathEncoding", operation, input));
|
|
78
|
+
bytes = new TextEncoder().encode(input);
|
|
79
|
+
}
|
|
80
|
+
else if (typeof input === "object" && input !== null) {
|
|
81
|
+
bytes = getBytePathBytes(input);
|
|
82
|
+
}
|
|
83
|
+
if (bytes === undefined)
|
|
84
|
+
return Result.fail(failure("InvalidArgument", operation));
|
|
85
|
+
if (bytes.length === 0)
|
|
86
|
+
return Result.fail(failure("NotFound", operation, input));
|
|
87
|
+
if (bytes.includes(0))
|
|
88
|
+
return Result.fail(failure("InvalidArgument", operation, input));
|
|
89
|
+
if (maxPathBytes !== undefined && ByteSize.isGreaterThan(ByteSize.bytes(bytes.length), maxPathBytes)) {
|
|
90
|
+
return Result.fail(failure("PathTooLong", operation, input));
|
|
91
|
+
}
|
|
92
|
+
const components = [];
|
|
93
|
+
const suffixes = [];
|
|
94
|
+
let start = 0;
|
|
95
|
+
for (let index = 0; index <= bytes.length; index++) {
|
|
96
|
+
if (index !== bytes.length && bytes[index] !== 47)
|
|
97
|
+
continue;
|
|
98
|
+
if (index > start) {
|
|
99
|
+
// Provisional component bound from decision 0019; names are compared as bytes.
|
|
100
|
+
if (index - start > 255)
|
|
101
|
+
return Result.fail(failure("PathTooLong", operation, input));
|
|
102
|
+
components.push(Encoding.encodeHex(bytes.subarray(start, index)));
|
|
103
|
+
suffixes.push(bytes.subarray(index));
|
|
104
|
+
}
|
|
105
|
+
start = index + 1;
|
|
106
|
+
}
|
|
107
|
+
return Result.succeed({
|
|
108
|
+
input,
|
|
109
|
+
absolute: bytes[0] === 47,
|
|
110
|
+
trailingSlash: bytes.at(-1) === 47,
|
|
111
|
+
bytes,
|
|
112
|
+
suffixes,
|
|
113
|
+
components
|
|
114
|
+
});
|
|
115
|
+
};
|
|
116
|
+
const configurationField = (issue) => {
|
|
117
|
+
if (issue._tag === "Pointer")
|
|
118
|
+
return issue.path.map(String).join(".");
|
|
119
|
+
if (issue._tag === "Composite")
|
|
120
|
+
return configurationField(issue.issues[0]);
|
|
121
|
+
return "options";
|
|
122
|
+
};
|
|
123
|
+
/** @internal */
|
|
124
|
+
export const decodeConfiguration = (schema, value) => Schema.decodeUnknownResult(schema, { onExcessProperty: "error" })(value).pipe(Result.mapError((error) => new ConfigurationError({ field: configurationField(error.issue) })));
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type * as Effect from "effect/Effect";
|
|
2
|
+
import type { Snapshot } from "../../Snapshot.js";
|
|
3
|
+
import type { Volume } from "../../VirtualFileSystem.js";
|
|
4
|
+
/** @internal */
|
|
5
|
+
export interface ObservationHook {
|
|
6
|
+
readonly betweenSnapshotAndSummary: Effect.Effect<void>;
|
|
7
|
+
}
|
|
8
|
+
/** @internal */
|
|
9
|
+
export declare const getObservationHook: (snapshot: Snapshot) => ObservationHook | undefined;
|
|
10
|
+
/** @internal */
|
|
11
|
+
export declare const setObservationHook: (snapshot: Snapshot, hook: ObservationHook) => () => void;
|
|
12
|
+
/** @internal */
|
|
13
|
+
export interface RegistrationHook {
|
|
14
|
+
readonly afterSubscribe: Effect.Effect<void>;
|
|
15
|
+
}
|
|
16
|
+
/** @internal */
|
|
17
|
+
export declare const getRegistrationHook: (volume: Volume) => RegistrationHook | undefined;
|
|
18
|
+
/** @internal */
|
|
19
|
+
export declare const setRegistrationHook: (volume: Volume, hook: RegistrationHook) => () => void;
|
|
20
|
+
//# sourceMappingURL=testHooks.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"testHooks.d.ts","sourceRoot":"","sources":["../../../src/internal/virtualFileSystem/testHooks.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,MAAM,eAAe,CAAA;AAC5C,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAA;AACjD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,4BAA4B,CAAA;AAExD,gBAAgB;AAChB,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,yBAAyB,EAAE,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;CACxD;AAID,gBAAgB;AAChB,eAAO,MAAM,kBAAkB,aAAc,QAAQ,KAAG,eAAe,GAAG,SAA2C,CAAA;AAErH,gBAAgB;AAChB,eAAO,MAAM,kBAAkB,aAAc,QAAQ,QAAQ,eAAe,KAAG,MAAM,IAKpF,CAAA;AAED,gBAAgB;AAChB,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;CAC7C;AAID,gBAAgB;AAChB,eAAO,MAAM,mBAAmB,WAAY,MAAM,KAAG,gBAAgB,GAAG,SAA0C,CAAA;AAElH,gBAAgB;AAChB,eAAO,MAAM,mBAAmB,WAAY,MAAM,QAAQ,gBAAgB,KAAG,MAAM,IAKlF,CAAA"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// Package-internal synchronization hooks for deterministic volume tests.
|
|
2
|
+
const observationHooks = new WeakMap();
|
|
3
|
+
/** @internal */
|
|
4
|
+
export const getObservationHook = (snapshot) => observationHooks.get(snapshot);
|
|
5
|
+
/** @internal */
|
|
6
|
+
export const setObservationHook = (snapshot, hook) => {
|
|
7
|
+
observationHooks.set(snapshot, hook);
|
|
8
|
+
return () => {
|
|
9
|
+
if (observationHooks.get(snapshot) === hook)
|
|
10
|
+
observationHooks.delete(snapshot);
|
|
11
|
+
};
|
|
12
|
+
};
|
|
13
|
+
const registrationHooks = new WeakMap();
|
|
14
|
+
/** @internal */
|
|
15
|
+
export const getRegistrationHook = (volume) => registrationHooks.get(volume);
|
|
16
|
+
/** @internal */
|
|
17
|
+
export const setRegistrationHook = (volume, hook) => {
|
|
18
|
+
registrationHooks.set(volume, hook);
|
|
19
|
+
return () => {
|
|
20
|
+
if (registrationHooks.get(volume) === hook)
|
|
21
|
+
registrationHooks.delete(volume);
|
|
22
|
+
};
|
|
23
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import * as Effect from "effect/Effect";
|
|
2
|
+
import type * as Scope from "effect/Scope";
|
|
3
|
+
import * as Stream from "effect/Stream";
|
|
4
|
+
/** @internal */
|
|
5
|
+
export interface Coordinator {
|
|
6
|
+
<A, E, R>(effect: Effect.Effect<A, E, R>): Effect.Effect<A, E, R>;
|
|
7
|
+
}
|
|
8
|
+
/** @internal */
|
|
9
|
+
export interface WatchHub<A> {
|
|
10
|
+
readonly publishUnsafe: (event: () => A) => void;
|
|
11
|
+
readonly publishManyUnsafe: (events: () => Iterable<A>) => void;
|
|
12
|
+
readonly subscribe: (afterSubscribe?: Effect.Effect<void>) => Effect.Effect<Stream.Stream<A>, never, Scope.Scope>;
|
|
13
|
+
}
|
|
14
|
+
/** @internal */
|
|
15
|
+
export declare const make: <A>(coordinate: Coordinator) => Effect.Effect<WatchHub<A>, never, never>;
|
|
16
|
+
//# sourceMappingURL=watchHub.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"watchHub.d.ts","sourceRoot":"","sources":["../../../src/internal/virtualFileSystem/watchHub.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AAEvC,OAAO,KAAK,KAAK,KAAK,MAAM,cAAc,CAAA;AAC1C,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AAEvC,gBAAgB;AAChB,MAAM,WAAW,WAAW;IAC1B,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAA;CAClE;AAED,gBAAgB;AAChB,MAAM,WAAW,QAAQ,CAAC,CAAC;IACzB,QAAQ,CAAC,aAAa,EAAE,CAAC,KAAK,EAAE,MAAM,CAAC,KAAK,IAAI,CAAA;IAChD,QAAQ,CAAC,iBAAiB,EAAE,CAAC,MAAM,EAAE,MAAM,QAAQ,CAAC,CAAC,CAAC,KAAK,IAAI,CAAA;IAC/D,QAAQ,CAAC,SAAS,EAAE,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,CAAC,CAAA;CAClH;AAED,gBAAgB;AAChB,eAAO,MAAM,IAAI,GAA+B,CAAC,sEA+B/C,CAAA"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import * as Effect from "effect/Effect";
|
|
2
|
+
import * as PubSub from "effect/PubSub";
|
|
3
|
+
import * as Stream from "effect/Stream";
|
|
4
|
+
/** @internal */
|
|
5
|
+
export const make = Effect.fnUntraced(function* (coordinate) {
|
|
6
|
+
const pubsub = yield* PubSub.unbounded();
|
|
7
|
+
let activeSubscribers = 0;
|
|
8
|
+
const publishUnsafe = (event) => {
|
|
9
|
+
if (activeSubscribers === 0)
|
|
10
|
+
return;
|
|
11
|
+
PubSub.publishUnsafe(pubsub, event());
|
|
12
|
+
};
|
|
13
|
+
const publishManyUnsafe = (events) => {
|
|
14
|
+
if (activeSubscribers === 0)
|
|
15
|
+
return;
|
|
16
|
+
for (const event of events())
|
|
17
|
+
PubSub.publishUnsafe(pubsub, event);
|
|
18
|
+
};
|
|
19
|
+
const subscribe = (afterSubscribe) => Effect.uninterruptible(Effect.gen(function* () {
|
|
20
|
+
const subscription = yield* coordinate(Effect.gen(function* () {
|
|
21
|
+
const subscription = yield* PubSub.subscribe(pubsub);
|
|
22
|
+
if (afterSubscribe !== undefined)
|
|
23
|
+
yield* afterSubscribe;
|
|
24
|
+
activeSubscribers += 1;
|
|
25
|
+
return subscription;
|
|
26
|
+
}));
|
|
27
|
+
yield* Effect.addFinalizer(() => coordinate(Effect.sync(() => {
|
|
28
|
+
activeSubscribers -= 1;
|
|
29
|
+
})));
|
|
30
|
+
return Stream.fromSubscription(subscription);
|
|
31
|
+
}));
|
|
32
|
+
return { publishUnsafe, publishManyUnsafe, subscribe };
|
|
33
|
+
});
|