@effect-vfs/core 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +36 -149
- package/dist/BytePath.d.ts +34 -2
- package/dist/BytePath.d.ts.map +1 -1
- package/dist/BytePath.js +1 -1
- package/dist/LiveVolume.d.ts +101 -0
- package/dist/LiveVolume.d.ts.map +1 -0
- package/dist/LiveVolume.js +72 -0
- package/dist/Snapshot.d.ts +60 -2
- package/dist/Snapshot.d.ts.map +1 -1
- package/dist/Snapshot.js +59 -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 +1224 -90
- package/dist/VirtualFileSystem.d.ts.map +1 -1
- package/dist/VirtualFileSystem.js +678 -20
- package/dist/VirtualFileSystemError.d.ts +54 -0
- package/dist/VirtualFileSystemError.d.ts.map +1 -0
- package/dist/VirtualFileSystemError.js +49 -0
- package/dist/index.d.ts +15 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +15 -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/liveImage.d.ts +286 -0
- package/dist/internal/liveImage.d.ts.map +1 -0
- package/dist/internal/liveImage.js +176 -0
- 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 +6 -20
- package/dist/internal/virtualFileSystem/errors.d.ts.map +1 -1
- package/dist/internal/virtualFileSystem/errors.js +22 -7
- package/dist/internal/virtualFileSystem/fixture.d.ts +4 -1
- package/dist/internal/virtualFileSystem/fixture.d.ts.map +1 -1
- package/dist/internal/virtualFileSystem/fixture.js +80 -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/stagedState.d.ts +22 -0
- package/dist/internal/virtualFileSystem/stagedState.d.ts.map +1 -0
- package/dist/internal/virtualFileSystem/stagedState.js +62 -0
- package/dist/internal/virtualFileSystem/testHooks.d.ts.map +1 -1
- package/dist/internal/virtualFileSystem/watchHub.d.ts +3 -3
- package/dist/internal/virtualFileSystem/watchHub.d.ts.map +1 -1
- package/dist/internal/virtualFileSystem/watchHub.js +45 -24
- package/dist/internal/virtualFileSystem.d.ts +307 -23
- package/dist/internal/virtualFileSystem.d.ts.map +1 -1
- package/dist/internal/virtualFileSystem.js +1738 -525
- package/package.json +23 -5
|
@@ -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"}
|
|
@@ -2,33 +2,41 @@
|
|
|
2
2
|
import * as ByteSize from "effect/ByteSize";
|
|
3
3
|
import * as Effect from "effect/Effect";
|
|
4
4
|
import * as Encoding from "effect/Encoding";
|
|
5
|
+
import * as Predicate from "effect/Predicate";
|
|
5
6
|
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
7
|
import { getBytes as getBytePathBytes, make as makeBytePath } from "../bytePath.js";
|
|
10
|
-
import {
|
|
8
|
+
import { FsError } from "./errors.js";
|
|
9
|
+
// Components are hex-encoded bytes so names compare as bytes, not text: 2f is "/", 2e is ".", 2e2e is "..".
|
|
11
10
|
/** @internal */
|
|
12
|
-
export const
|
|
11
|
+
export const SLASH_HEX = "2f";
|
|
13
12
|
/** @internal */
|
|
14
|
-
export const
|
|
15
|
-
|
|
16
|
-
|
|
13
|
+
export const DOT_HEX = "2e";
|
|
14
|
+
/** @internal */
|
|
15
|
+
export const DOT_DOT_HEX = "2e2e";
|
|
16
|
+
/** @internal */
|
|
17
|
+
export const SLASH_BYTE = 47;
|
|
18
|
+
// POSIX NAME_MAX: the longest single path component.
|
|
19
|
+
/** @internal */
|
|
20
|
+
export const MAX_NAME_BYTES = 255;
|
|
21
|
+
// A missing final component is rejected wherever "." and ".." are, so it counts as a dot component.
|
|
22
|
+
// Callers pass the code POSIX gives their operation, so the codes differ on purpose: EEXIST for
|
|
23
|
+
// create (link, symlink, mkdir), EISDIR for open and unlink, EINVAL for rename and rmdir.
|
|
24
|
+
/** @internal */
|
|
25
|
+
export const isDotComponent = (name) => name === undefined || name === DOT_HEX || name === DOT_DOT_HEX;
|
|
26
|
+
/** @internal */
|
|
27
|
+
export const ownedPath = (bytes) => makeBytePath(bytes);
|
|
17
28
|
/** @internal */
|
|
18
29
|
export const strictString = (bytes, operation) => Effect.try({
|
|
19
30
|
try: () => new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(bytes),
|
|
20
|
-
catch: () =>
|
|
31
|
+
catch: () => new FsError({ code: "UnrepresentableName", operation })
|
|
21
32
|
});
|
|
22
33
|
/** @internal */
|
|
23
|
-
export const nameBytes = (name) =>
|
|
24
|
-
return Result.getOrThrow(Encoding.decodeHex(name));
|
|
25
|
-
};
|
|
34
|
+
export const nameBytes = (name) => Result.getOrThrow(Encoding.decodeHex(name));
|
|
26
35
|
// A zero-length view distinguishes a detached buffer from a valid empty buffer.
|
|
27
|
-
|
|
28
|
-
export const attachedBuffer = (bytes) => {
|
|
36
|
+
const attachedBuffer = (bytes) => {
|
|
29
37
|
try {
|
|
30
|
-
new Uint8Array(bytes.buffer, bytes.byteOffset, 0);
|
|
31
|
-
return
|
|
38
|
+
const probe = new Uint8Array(bytes.buffer, bytes.byteOffset, 0);
|
|
39
|
+
return probe.byteLength === 0;
|
|
32
40
|
}
|
|
33
41
|
catch (error) {
|
|
34
42
|
if (error instanceof TypeError)
|
|
@@ -36,27 +44,29 @@ export const attachedBuffer = (bytes) => {
|
|
|
36
44
|
throw error;
|
|
37
45
|
}
|
|
38
46
|
};
|
|
47
|
+
// Accepts any Uint8Array, subclasses such as Node's Buffer included; rejects views over a
|
|
48
|
+
// SharedArrayBuffer or a detached buffer before the bytes are copied or trusted.
|
|
49
|
+
/** @internal */
|
|
50
|
+
export const isAttachedBytes = (bytes) => Predicate.isUint8Array(bytes) && bytes.buffer instanceof ArrayBuffer && attachedBuffer(bytes);
|
|
39
51
|
/** @internal */
|
|
40
52
|
export const pathFromBytes = Effect.fn("VirtualFileSystem.pathFromBytes")(function* (bytes) {
|
|
41
|
-
if (!(bytes
|
|
42
|
-
return yield*
|
|
43
|
-
}
|
|
44
|
-
if (!attachedBuffer(bytes))
|
|
45
|
-
return yield* failure("InvalidArgument", "pathFromBytes");
|
|
53
|
+
if (!isAttachedBytes(bytes))
|
|
54
|
+
return yield* new FsError({ code: "InvalidArgument", operation: "pathFromBytes" });
|
|
46
55
|
const owned = new Uint8Array(bytes);
|
|
47
|
-
if (owned.length === 0 || owned.includes(0))
|
|
48
|
-
return yield*
|
|
56
|
+
if (owned.length === 0 || owned.includes(0)) {
|
|
57
|
+
return yield* new FsError({ code: "InvalidArgument", operation: "pathFromBytes" });
|
|
58
|
+
}
|
|
49
59
|
return makeBytePath(owned);
|
|
50
60
|
});
|
|
51
61
|
/** @internal */
|
|
52
62
|
export const pathToBytes = Effect.fn("VirtualFileSystem.pathToBytes")(function* (path) {
|
|
53
63
|
const bytes = getBytePathBytes(path);
|
|
54
64
|
if (bytes === undefined)
|
|
55
|
-
return yield*
|
|
65
|
+
return yield* new FsError({ code: "InvalidArgument", operation: "pathToBytes" });
|
|
56
66
|
return new Uint8Array(bytes);
|
|
57
67
|
});
|
|
58
|
-
|
|
59
|
-
|
|
68
|
+
// Hand-rolled because String.prototype.isWellFormed is ES2024 and the package targets ES2023.
|
|
69
|
+
const wellFormed = (value) => {
|
|
60
70
|
for (let index = 0; index < value.length; index++) {
|
|
61
71
|
const code = value.charCodeAt(index);
|
|
62
72
|
if (code >= 0xd800 && code <= 0xdbff) {
|
|
@@ -69,36 +79,38 @@ export const wellFormed = (value) => {
|
|
|
69
79
|
}
|
|
70
80
|
return true;
|
|
71
81
|
};
|
|
82
|
+
const UTF8_ENCODER = new TextEncoder();
|
|
83
|
+
// Strings must be well-formed UTF-16 before encoding; BytePaths must belong to this package.
|
|
84
|
+
/** @internal */
|
|
85
|
+
export const inputBytes = (input) => Predicate.isString(input)
|
|
86
|
+
? wellFormed(input) ? Result.succeed(UTF8_ENCODER.encode(input)) : Result.fail("InvalidPathEncoding")
|
|
87
|
+
: Result.fromNullishOr(getBytePathBytes(input), () => "InvalidArgument");
|
|
72
88
|
/** @internal */
|
|
73
89
|
export const preparePath = (input, operation, maxPathBytes) => {
|
|
74
|
-
|
|
75
|
-
if (
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
90
|
+
const encoded = inputBytes(input);
|
|
91
|
+
if (Result.isFailure(encoded)) {
|
|
92
|
+
return Result.fail(encoded.failure === "InvalidPathEncoding"
|
|
93
|
+
? new FsError({ code: "InvalidPathEncoding", operation, path: input })
|
|
94
|
+
: new FsError({ code: "InvalidArgument", operation }));
|
|
79
95
|
}
|
|
80
|
-
|
|
81
|
-
bytes = getBytePathBytes(input);
|
|
82
|
-
}
|
|
83
|
-
if (bytes === undefined)
|
|
84
|
-
return Result.fail(failure("InvalidArgument", operation));
|
|
96
|
+
const bytes = encoded.success;
|
|
85
97
|
if (bytes.length === 0)
|
|
86
|
-
return Result.fail(
|
|
98
|
+
return Result.fail(new FsError({ code: "NotFound", operation, path: input }));
|
|
87
99
|
if (bytes.includes(0))
|
|
88
|
-
return Result.fail(
|
|
100
|
+
return Result.fail(new FsError({ code: "InvalidArgument", operation, path: input }));
|
|
89
101
|
if (maxPathBytes !== undefined && ByteSize.isGreaterThan(ByteSize.bytes(bytes.length), maxPathBytes)) {
|
|
90
|
-
return Result.fail(
|
|
102
|
+
return Result.fail(new FsError({ code: "PathTooLong", operation, path: input }));
|
|
91
103
|
}
|
|
92
104
|
const components = [];
|
|
93
105
|
const suffixes = [];
|
|
94
106
|
let start = 0;
|
|
95
107
|
for (let index = 0; index <= bytes.length; index++) {
|
|
96
|
-
if (index !== bytes.length && bytes[index] !==
|
|
108
|
+
if (index !== bytes.length && bytes[index] !== SLASH_BYTE)
|
|
97
109
|
continue;
|
|
98
110
|
if (index > start) {
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
111
|
+
if (index - start > MAX_NAME_BYTES) {
|
|
112
|
+
return Result.fail(new FsError({ code: "PathTooLong", operation, path: input }));
|
|
113
|
+
}
|
|
102
114
|
components.push(Encoding.encodeHex(bytes.subarray(start, index)));
|
|
103
115
|
suffixes.push(bytes.subarray(index));
|
|
104
116
|
}
|
|
@@ -106,19 +118,10 @@ export const preparePath = (input, operation, maxPathBytes) => {
|
|
|
106
118
|
}
|
|
107
119
|
return Result.succeed({
|
|
108
120
|
input,
|
|
109
|
-
absolute: bytes[0] ===
|
|
110
|
-
trailingSlash: bytes.at(-1) ===
|
|
121
|
+
absolute: bytes[0] === SLASH_BYTE,
|
|
122
|
+
trailingSlash: bytes.at(-1) === SLASH_BYTE,
|
|
111
123
|
bytes,
|
|
112
124
|
suffixes,
|
|
113
125
|
components
|
|
114
126
|
});
|
|
115
127
|
};
|
|
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,22 @@
|
|
|
1
|
+
import * as Effect from "effect/Effect";
|
|
2
|
+
import { FsError } from "./errors.js";
|
|
3
|
+
/** @internal */
|
|
4
|
+
export type CommitOutcome = "committed" | "rejected" | "unknown";
|
|
5
|
+
/** @internal */
|
|
6
|
+
export interface CommitProvider<State> {
|
|
7
|
+
/** Provides a gate-coordinated shutdown effect once staging is ready. */
|
|
8
|
+
readonly onReady?: (shutdown: Effect.Effect<void>) => void;
|
|
9
|
+
/** Checks and encodes a candidate before the commit boundary. Failure leaves the volume available. */
|
|
10
|
+
readonly prepare?: (candidate: State) => Effect.Effect<void, FsError>;
|
|
11
|
+
/** Classifies a candidate as committed, definitely rejected, or uncertain. */
|
|
12
|
+
readonly commit: (candidate: State) => Effect.Effect<CommitOutcome>;
|
|
13
|
+
}
|
|
14
|
+
/** @internal */
|
|
15
|
+
export declare const makeStagedState: <State, Event = never>(initial: State, copy: (current: State) => Effect.Effect<State>, rawProvider: CommitProvider<State>, publish?: (candidate: State, events: ReadonlyArray<Event>) => void) => {
|
|
16
|
+
read: <A, E, R>(operation: string, inspect: (state: Readonly<State>) => Effect.Effect<A, E, R>) => Effect.Effect<A, E | FsError, R>;
|
|
17
|
+
mutate: <A, E, R>(operation: string, change: (candidate: State, emit: (event: Event) => void) => Effect.Effect<A, E, R>, onStorageFailure?: () => void) => Effect.Effect<A, E | FsError, R>;
|
|
18
|
+
coordinate: <A, E, R>(effect: Effect.Effect<A, E, R>) => Effect.Effect<A, E, R>;
|
|
19
|
+
checkAvailable: (operation: string) => Effect.Effect<void, FsError, never>;
|
|
20
|
+
shutdown: Effect.Effect<void, never, never>;
|
|
21
|
+
};
|
|
22
|
+
//# sourceMappingURL=stagedState.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stagedState.d.ts","sourceRoot":"","sources":["../../../src/internal/virtualFileSystem/stagedState.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AAGvC,OAAO,EAAE,OAAO,EAAE,MAAM,aAAa,CAAA;AAErC,gBAAgB;AAChB,MAAM,MAAM,aAAa,GAAG,WAAW,GAAG,UAAU,GAAG,SAAS,CAAA;AAEhE,gBAAgB;AAChB,MAAM,WAAW,cAAc,CAAC,KAAK;IACnC,yEAAyE;IACzE,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,IAAI,CAAA;IAC1D,sGAAsG;IACtG,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,KAAK,KAAK,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,CAAA;IACrE,8EAA8E;IAC9E,QAAQ,CAAC,MAAM,EAAE,CAAC,SAAS,EAAE,KAAK,KAAK,MAAM,CAAC,MAAM,CAAC,aAAa,CAAC,CAAA;CACpE;AAED,gBAAgB;AAChB,eAAO,MAAM,eAAe,GAAI,KAAK,EAAE,KAAK,mBACjC,KAAK,QAER,CAAC,OAAO,EAAE,KAAK,KAAK,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,eACjC,cAAc,CAAC,KAAK,CAAC,YACxB,CAAC,SAAS,EAAE,KAAK,EAAE,MAAM,EAAE,aAAa,CAAC,KAAK,CAAC,KAAK,IAAI;WAyBpD,CAAC,EAAE,CAAC,EAAE,CAAC,aAAa,MAAM,WAAW,CAAC,KAAK,EAAE,QAAQ,CAAC,KAAK,CAAC,KAAK,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;aAOrF,CAAC,EAAE,CAAC,EAAE,CAAC,aACV,MAAM,UACT,CAAC,SAAS,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,KAAK,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,qBAC/D,MAAM,IAAI;iBAhBX,CAAC,EAAE,CAAC,EAAE,CAAC,UAAU,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;gCAPxB,MAAM;;CA0E1C,CAAA"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import * as Effect from "effect/Effect";
|
|
2
|
+
import * as Exit from "effect/Exit";
|
|
3
|
+
import * as Semaphore from "effect/Semaphore";
|
|
4
|
+
import { FsError } from "./errors.js";
|
|
5
|
+
/** @internal */
|
|
6
|
+
export const makeStagedState = (initial,
|
|
7
|
+
// The copy must detach every mutable value that change can reach.
|
|
8
|
+
copy, rawProvider, publish) => {
|
|
9
|
+
const gate = Semaphore.makeUnsafe(1);
|
|
10
|
+
const provider = {
|
|
11
|
+
...rawProvider,
|
|
12
|
+
commit: (candidate) => Effect.suspend(() => rawProvider.commit(candidate))
|
|
13
|
+
};
|
|
14
|
+
let current = initial;
|
|
15
|
+
let available = true;
|
|
16
|
+
const checkAvailable = (operation) => Effect.suspend(() => available
|
|
17
|
+
? Effect.void
|
|
18
|
+
: Effect.fail(new FsError({ code: "VolumeUnavailable", operation })));
|
|
19
|
+
const coordinate = (effect) => gate.withPermit(effect);
|
|
20
|
+
const shutdown = coordinate(Effect.sync(() => {
|
|
21
|
+
available = false;
|
|
22
|
+
}));
|
|
23
|
+
const read = (operation, inspect) => coordinate(Effect.gen(function* () {
|
|
24
|
+
yield* checkAvailable(operation);
|
|
25
|
+
return yield* inspect(current);
|
|
26
|
+
}));
|
|
27
|
+
const mutate = (operation, change, onStorageFailure) => coordinate(Effect.uninterruptibleMask((restore) => Effect.gen(function* () {
|
|
28
|
+
yield* checkAvailable(operation);
|
|
29
|
+
const candidate = yield* restore(copy(current));
|
|
30
|
+
const events = [];
|
|
31
|
+
const value = yield* restore(change(candidate, (event) => events.push(event)));
|
|
32
|
+
if (provider.prepare !== undefined)
|
|
33
|
+
yield* restore(provider.prepare(candidate));
|
|
34
|
+
const committed = yield* Effect.exit(Effect.suspend(() => provider.commit(candidate)));
|
|
35
|
+
if (Exit.isFailure(committed)) {
|
|
36
|
+
available = false;
|
|
37
|
+
onStorageFailure?.();
|
|
38
|
+
return yield* new FsError({ code: "OutcomeUnknown", operation });
|
|
39
|
+
}
|
|
40
|
+
const outcome = committed.value;
|
|
41
|
+
if (outcome === "rejected") {
|
|
42
|
+
if (onStorageFailure !== undefined) {
|
|
43
|
+
available = false;
|
|
44
|
+
onStorageFailure();
|
|
45
|
+
}
|
|
46
|
+
return yield* new FsError({ code: "StorageRejected", operation });
|
|
47
|
+
}
|
|
48
|
+
if (outcome === "unknown") {
|
|
49
|
+
available = false;
|
|
50
|
+
onStorageFailure?.();
|
|
51
|
+
return yield* new FsError({ code: "OutcomeUnknown", operation });
|
|
52
|
+
}
|
|
53
|
+
current = candidate;
|
|
54
|
+
const published = yield* Effect.exit(Effect.sync(() => publish?.(candidate, events)));
|
|
55
|
+
if (Exit.isFailure(published)) {
|
|
56
|
+
available = false;
|
|
57
|
+
return yield* new FsError({ code: "OutcomeUnknown", operation });
|
|
58
|
+
}
|
|
59
|
+
return value;
|
|
60
|
+
})));
|
|
61
|
+
return { read, mutate, coordinate, checkAvailable, shutdown };
|
|
62
|
+
};
|
|
@@ -1 +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,
|
|
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,IAMpF,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,IAMlF,CAAA"}
|
|
@@ -6,11 +6,11 @@ export interface Coordinator {
|
|
|
6
6
|
<A, E, R>(effect: Effect.Effect<A, E, R>): Effect.Effect<A, E, R>;
|
|
7
7
|
}
|
|
8
8
|
/** @internal */
|
|
9
|
-
export interface WatchHub<A> {
|
|
9
|
+
export interface WatchHub<A, E = never> {
|
|
10
10
|
readonly publishUnsafe: (event: () => A) => void;
|
|
11
11
|
readonly publishManyUnsafe: (events: () => Iterable<A>) => void;
|
|
12
|
-
readonly subscribe: (afterSubscribe?: Effect.Effect<void>) => Effect.Effect<Stream.Stream<A>,
|
|
12
|
+
readonly subscribe: (afterSubscribe?: Effect.Effect<void>) => Effect.Effect<Stream.Stream<A>, E, Scope.Scope>;
|
|
13
13
|
}
|
|
14
14
|
/** @internal */
|
|
15
|
-
export declare const make: <A>(coordinate: Coordinator) => Effect.Effect<WatchHub<A>, never, never>;
|
|
15
|
+
export declare const make: <A, E = never>(coordinate: Coordinator, capacity: number, rescan: () => A, checkAvailable?: Effect.Effect<void, E, never> | undefined) => Effect.Effect<WatchHub<A, E>, never, never>;
|
|
16
16
|
//# sourceMappingURL=watchHub.d.ts.map
|
|
@@ -1 +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;
|
|
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,EAAE,CAAC,GAAG,KAAK;IACpC,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,CAAC,EAAE,KAAK,CAAC,KAAK,CAAC,CAAA;CAC9G;AAQD,gBAAgB;AAChB,eAAO,MAAM,IAAI,GAA+B,CAAC,EAAE,CAAC,mEAGpC,CAAC,4GAiEf,CAAA"}
|
|
@@ -1,33 +1,54 @@
|
|
|
1
1
|
import * as Effect from "effect/Effect";
|
|
2
|
-
import * as
|
|
2
|
+
import * as Queue from "effect/Queue";
|
|
3
3
|
import * as Stream from "effect/Stream";
|
|
4
4
|
/** @internal */
|
|
5
|
-
export const make = Effect.fnUntraced(function* (coordinate) {
|
|
6
|
-
const
|
|
7
|
-
|
|
5
|
+
export const make = Effect.fnUntraced(function* (coordinate, capacity, rescan, checkAvailable) {
|
|
6
|
+
const subscribers = new Set();
|
|
7
|
+
const publish = (event) => {
|
|
8
|
+
for (const subscriber of subscribers) {
|
|
9
|
+
if (subscriber.overflowed)
|
|
10
|
+
continue;
|
|
11
|
+
if (Queue.sizeUnsafe(subscriber.queue) < capacity - 1) {
|
|
12
|
+
Queue.offerUnsafe(subscriber.queue, event);
|
|
13
|
+
}
|
|
14
|
+
else {
|
|
15
|
+
subscriber.overflowed = true;
|
|
16
|
+
subscriber.marker = rescan();
|
|
17
|
+
Queue.offerUnsafe(subscriber.queue, subscriber.marker);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
};
|
|
8
21
|
const publishUnsafe = (event) => {
|
|
9
|
-
if (
|
|
10
|
-
|
|
11
|
-
PubSub.publishUnsafe(pubsub, event());
|
|
22
|
+
if (subscribers.size > 0)
|
|
23
|
+
publish(event());
|
|
12
24
|
};
|
|
13
25
|
const publishManyUnsafe = (events) => {
|
|
14
|
-
if (
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
26
|
+
if (subscribers.size > 0) {
|
|
27
|
+
for (const event of events())
|
|
28
|
+
publish(event);
|
|
29
|
+
}
|
|
18
30
|
};
|
|
19
|
-
const subscribe = (afterSubscribe) => Effect.
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
31
|
+
const subscribe = (afterSubscribe) => Effect.acquireRelease(coordinate(Effect.gen(function* () {
|
|
32
|
+
if (checkAvailable !== undefined)
|
|
33
|
+
yield* checkAvailable;
|
|
34
|
+
const queue = yield* Queue.bounded(capacity);
|
|
35
|
+
const subscriber = {
|
|
36
|
+
queue,
|
|
37
|
+
overflowed: false,
|
|
38
|
+
marker: undefined
|
|
39
|
+
};
|
|
40
|
+
if (afterSubscribe !== undefined)
|
|
41
|
+
yield* afterSubscribe;
|
|
42
|
+
subscribers.add(subscriber);
|
|
43
|
+
return subscriber;
|
|
44
|
+
})), (subscriber) => coordinate(Effect.sync(() => {
|
|
45
|
+
subscribers.delete(subscriber);
|
|
46
|
+
})).pipe(Effect.andThen(Queue.shutdown(subscriber.queue))), { interruptible: true }).pipe(Effect.map((subscriber) => Stream.fromEffectRepeat(Queue.take(subscriber.queue)).pipe(Stream.map((event) => {
|
|
47
|
+
if (subscriber.overflowed && event === subscriber.marker) {
|
|
48
|
+
subscriber.overflowed = false;
|
|
49
|
+
subscriber.marker = undefined;
|
|
50
|
+
}
|
|
51
|
+
return event;
|
|
52
|
+
}))));
|
|
32
53
|
return { publishUnsafe, publishManyUnsafe, subscribe };
|
|
33
54
|
});
|