@effect-vfs/core 0.6.0 → 0.7.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 +14 -9
- package/dist/BytePath.d.ts +2 -1
- package/dist/BytePath.d.ts.map +1 -1
- package/dist/LiveVolume.d.ts +6 -3
- package/dist/LiveVolume.d.ts.map +1 -1
- package/dist/LiveVolume.js +2 -0
- package/dist/Snapshot.d.ts +2 -1
- package/dist/Snapshot.d.ts.map +1 -1
- package/dist/Snapshot.js +2 -1
- package/dist/Testing.d.ts +9 -5
- package/dist/Testing.d.ts.map +1 -1
- package/dist/Testing.js +7 -18
- package/dist/VfsError.d.ts +2 -1
- package/dist/VfsError.d.ts.map +1 -1
- package/dist/VfsError.js +2 -1
- package/dist/VirtualFileSystem.d.ts +89 -47
- package/dist/VirtualFileSystem.d.ts.map +1 -1
- package/dist/VirtualFileSystem.js +62 -29
- package/dist/internal/fixture.d.ts +1 -1
- package/dist/internal/fixture.d.ts.map +1 -1
- package/dist/internal/overlayDiff.d.ts.map +1 -1
- package/dist/internal/overlayDiff.js +4 -12
- package/dist/internal/snapshotDelta.d.ts.map +1 -1
- package/dist/internal/snapshotDelta.js +7 -13
- package/dist/internal/tree.d.ts.map +1 -1
- package/dist/internal/tree.js +12 -12
- package/dist/internal/virtualFileSystem.d.ts +13 -7
- package/dist/internal/virtualFileSystem.d.ts.map +1 -1
- package/dist/internal/virtualFileSystem.js +48 -50
- package/dist/internal/volumeState.d.ts +3 -2
- package/dist/internal/volumeState.d.ts.map +1 -1
- package/dist/internal/volumeState.js +28 -30
- package/package.json +7 -7
- package/dist/internal/stagedState.d.ts +0 -18
- package/dist/internal/stagedState.d.ts.map +0 -1
- package/dist/internal/stagedState.js +0 -18
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import * as ByteSize from "effect/ByteSize";
|
|
2
2
|
import * as Effect from "effect/Effect";
|
|
3
3
|
import * as Encoding from "effect/Encoding";
|
|
4
|
+
import * as Match from "effect/Match";
|
|
5
|
+
import * as Order from "effect/Order";
|
|
4
6
|
import * as Result from "effect/Result";
|
|
5
7
|
import * as Schema from "effect/Schema";
|
|
6
8
|
import { SnapshotChange, SnapshotDifference, SnapshotNodeKind } from "../SnapshotDelta.js";
|
|
@@ -261,17 +263,9 @@ const deltaNode = (target, path, node, withPayload) => {
|
|
|
261
263
|
return DeltaNode.cases.link.make({ to: CanonicalBase64.encode(first) }, UNCHECKED);
|
|
262
264
|
}
|
|
263
265
|
const metadata = storedMetadata(node.metadata);
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
return node.kind === "file"
|
|
268
|
-
? DeltaNode.cases.file.make({ metadata }, UNCHECKED)
|
|
269
|
-
: DeltaNode.cases.symlink.make({ metadata }, UNCHECKED);
|
|
270
|
-
}
|
|
271
|
-
const bytes = CanonicalBase64.encode(payloadOf(node));
|
|
272
|
-
return node.kind === "file"
|
|
273
|
-
? DeltaNode.cases.file.make({ metadata, content: InlineContent.make({ bytes }, UNCHECKED) }, UNCHECKED)
|
|
274
|
-
: DeltaNode.cases.symlink.make({ metadata, target: bytes }, UNCHECKED);
|
|
266
|
+
return Match.value(node).pipe(Match.discriminator("kind")("directory", () => DeltaNode.cases.directory.make({ metadata }, UNCHECKED)), Match.discriminator("kind")("file", (file) => DeltaNode.cases.file.make(withPayload
|
|
267
|
+
? { metadata, content: InlineContent.make({ bytes: CanonicalBase64.encode(file.data) }, UNCHECKED) }
|
|
268
|
+
: { metadata }, UNCHECKED)), Match.discriminator("kind")("symlink", (symlink) => DeltaNode.cases.symlink.make(withPayload ? { metadata, target: CanonicalBase64.encode(symlink.target) } : { metadata }, UNCHECKED)), Match.exhaustive);
|
|
275
269
|
};
|
|
276
270
|
const lookup = (get, entriesOf, path) => {
|
|
277
271
|
let parent = ROOT_INO;
|
|
@@ -365,7 +359,7 @@ const compare = (base, target) => {
|
|
|
365
359
|
}
|
|
366
360
|
}
|
|
367
361
|
}
|
|
368
|
-
return changes.sort((
|
|
362
|
+
return changes.sort(Order.mapInput(bytesOrder, (entry) => entry.path)).map(({ change }) => change);
|
|
369
363
|
};
|
|
370
364
|
const getDocument = (delta) => Effect.suspend(() => {
|
|
371
365
|
const value = SnapshotDeltaModel.value(delta);
|
|
@@ -622,7 +616,7 @@ const foldedIdentity = Effect.fnUntraced(function* (base, folded, identity, limi
|
|
|
622
616
|
// Digest children before their directories.
|
|
623
617
|
const ordered = [
|
|
624
618
|
...nodes.filter((node) => node.kind !== "directory"),
|
|
625
|
-
...directories.sort((
|
|
619
|
+
...directories.sort(Order.flip(Order.mapInput(Order.Number, depth)))
|
|
626
620
|
];
|
|
627
621
|
for (const node of ordered)
|
|
628
622
|
digests.set(node.ino, yield* Merkle.nodeDigest(node, digestOf, measure));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tree.d.ts","sourceRoot":"","sources":["../../src/internal/tree.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;
|
|
1
|
+
{"version":3,"file":"tree.d.ts","sourceRoot":"","sources":["../../src/internal/tree.ts"],"names":[],"mappings":"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"}
|
package/dist/internal/tree.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// Validate each tree line as it arrives; check cross-node graph rules after the final line.
|
|
2
2
|
import * as Effect from "effect/Effect";
|
|
3
3
|
import * as Encoding from "effect/Encoding";
|
|
4
|
+
import * as Match from "effect/Match";
|
|
4
5
|
import * as Predicate from "effect/Predicate";
|
|
5
6
|
import * as Result from "effect/Result";
|
|
6
7
|
import * as Schema from "effect/Schema";
|
|
@@ -323,18 +324,17 @@ export const UNCHECKED = { disableChecks: true };
|
|
|
323
324
|
/** @internal */
|
|
324
325
|
export const treeNode = (node) => {
|
|
325
326
|
const metadata = storedMetadata(node.metadata);
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
: TreeNode.cases.symlink.make({ ino: node.ino, links: named, target: CanonicalBase64.encode(node.target), metadata }, UNCHECKED);
|
|
327
|
+
return Match.value(node).pipe(Match.discriminator("kind")("directory", (directory) => TreeNode.cases.directory.make({ ino: directory.ino, parent: directory.parent, name: encodeName(directory.name), metadata }, UNCHECKED)), Match.discriminator("kind")("file", (file) => TreeNode.cases.file.make({
|
|
328
|
+
ino: file.ino,
|
|
329
|
+
links: file.links.map((link) => ({ parent: link.parent, name: encodeName(link.name) })),
|
|
330
|
+
content: TreeContent.cases.Inline.make({ bytes: CanonicalBase64.encode(file.data) }, UNCHECKED),
|
|
331
|
+
metadata
|
|
332
|
+
}, UNCHECKED)), Match.discriminator("kind")("symlink", (symlink) => TreeNode.cases.symlink.make({
|
|
333
|
+
ino: symlink.ino,
|
|
334
|
+
links: symlink.links.map((link) => ({ parent: link.parent, name: encodeName(link.name) })),
|
|
335
|
+
target: CanonicalBase64.encode(symlink.target),
|
|
336
|
+
metadata
|
|
337
|
+
}, UNCHECKED)), Match.exhaustive);
|
|
338
338
|
};
|
|
339
339
|
/** @internal */
|
|
340
340
|
export const treeNodes = Effect.fnUntraced(function* (value, extra = []) {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as ByteSize from "effect/ByteSize";
|
|
2
|
+
import * as Crypto from "effect/Crypto";
|
|
2
3
|
import * as Data from "effect/Data";
|
|
3
4
|
import * as Effect from "effect/Effect";
|
|
4
5
|
import type { Snapshot } from "../Snapshot.js";
|
|
@@ -6,8 +7,13 @@ import { type FsFailure, type ImageFailure } from "../VfsError.js";
|
|
|
6
7
|
import type { OverlayVolume, Volume, VolumeLimits } from "../VirtualFileSystem.js";
|
|
7
8
|
import * as LiveImage from "./liveImage.js";
|
|
8
9
|
import { type ObservationEntry } from "./overlayDiff.js";
|
|
9
|
-
import { type CommitProvider } from "./stagedState.js";
|
|
10
10
|
import { type VolumeState } from "./volumeState.js";
|
|
11
|
+
type CommitOutcome = "committed" | "rejected" | "unknown";
|
|
12
|
+
interface CommitProvider<State> {
|
|
13
|
+
/** Preparation failures leave the volume available. */
|
|
14
|
+
readonly prepare?: (candidate: State) => Effect.Effect<void, FsFailure>;
|
|
15
|
+
readonly commit: (candidate: State) => Effect.Effect<CommitOutcome>;
|
|
16
|
+
}
|
|
11
17
|
type VolumeSource = {
|
|
12
18
|
readonly _tag: "Empty";
|
|
13
19
|
} | {
|
|
@@ -74,7 +80,7 @@ export declare const makeVolume: (source: VolumeSource, options?: {
|
|
|
74
80
|
observation: ObservationEntry[];
|
|
75
81
|
}, FsFailure, never>;
|
|
76
82
|
}>;
|
|
77
|
-
}>, import("../VfsError.js").ArgumentFailure | ImageFailure,
|
|
83
|
+
}>, import("../VfsError.js").ArgumentFailure | ImageFailure, Crypto.Crypto>;
|
|
78
84
|
/** @internal */
|
|
79
85
|
export declare const make: (options?: {
|
|
80
86
|
readonly identity?: string & import("effect/Brand").Brand<"@effect-vfs/core/VolumeIdentity">;
|
|
@@ -84,7 +90,7 @@ export declare const make: (options?: {
|
|
|
84
90
|
readonly maxBytes?: ByteSize.ByteSize;
|
|
85
91
|
readonly maxFileBytes?: ByteSize.ByteSize;
|
|
86
92
|
readonly maxPathBytes?: ByteSize.ByteSize;
|
|
87
|
-
} | undefined) => Effect.Effect<Volume, import("../VfsError.js").ArgumentFailure,
|
|
93
|
+
} | undefined) => Effect.Effect<Volume, import("../VfsError.js").ArgumentFailure, Crypto.Crypto>;
|
|
88
94
|
/** @internal */
|
|
89
95
|
export declare const prepareEmptyLiveImage: (options?: {
|
|
90
96
|
readonly identity?: string & import("effect/Brand").Brand<"@effect-vfs/core/VolumeIdentity">;
|
|
@@ -94,12 +100,12 @@ export declare const prepareEmptyLiveImage: (options?: {
|
|
|
94
100
|
readonly maxBytes?: ByteSize.ByteSize;
|
|
95
101
|
readonly maxFileBytes?: ByteSize.ByteSize;
|
|
96
102
|
readonly maxPathBytes?: ByteSize.ByteSize;
|
|
97
|
-
} | undefined) => Effect.Effect<Uint8Array<ArrayBufferLike>, import("../VfsError.js").ArgumentFailure | ImageFailure,
|
|
103
|
+
} | undefined) => Effect.Effect<Uint8Array<ArrayBufferLike>, import("../VfsError.js").ArgumentFailure | ImageFailure, Crypto.Crypto>;
|
|
98
104
|
/** @internal */
|
|
99
105
|
export declare const openImageVolume: (image: Uint8Array<ArrayBufferLike>, maxImageBytes: ByteSize.ByteSize, commit: (image: Uint8Array) => Effect.Effect<"committed" | "rejected" | "unknown">, durability?: "memory-only" | "survives-operating-system-crash" | "survives-power-loss" | "survives-process-crash" | undefined) => Effect.Effect<Readonly<{
|
|
100
106
|
volume: Volume;
|
|
101
107
|
shutdown: Effect.Effect<void, never, never>;
|
|
102
|
-
}>, import("../VfsError.js").ArgumentFailure | ImageFailure,
|
|
108
|
+
}>, import("../VfsError.js").ArgumentFailure | ImageFailure, Crypto.Crypto>;
|
|
103
109
|
/** @internal */
|
|
104
110
|
export declare const fromSnapshot: (snapshot: Snapshot, options?: {
|
|
105
111
|
readonly identity?: string & import("effect/Brand").Brand<"@effect-vfs/core/VolumeIdentity">;
|
|
@@ -109,7 +115,7 @@ export declare const fromSnapshot: (snapshot: Snapshot, options?: {
|
|
|
109
115
|
readonly maxBytes?: ByteSize.ByteSize;
|
|
110
116
|
readonly maxFileBytes?: ByteSize.ByteSize;
|
|
111
117
|
readonly maxPathBytes?: ByteSize.ByteSize;
|
|
112
|
-
} | undefined) => Effect.Effect<Volume, import("../VfsError.js").ArgumentFailure | ImageFailure,
|
|
118
|
+
} | undefined) => Effect.Effect<Volume, import("../VfsError.js").ArgumentFailure | ImageFailure, Crypto.Crypto>;
|
|
113
119
|
/** @internal */
|
|
114
120
|
export declare const makeOverlay: (base: Snapshot, options?: {
|
|
115
121
|
readonly identity?: string & import("effect/Brand").Brand<"@effect-vfs/core/VolumeIdentity">;
|
|
@@ -119,6 +125,6 @@ export declare const makeOverlay: (base: Snapshot, options?: {
|
|
|
119
125
|
readonly maxBytes?: ByteSize.ByteSize;
|
|
120
126
|
readonly maxFileBytes?: ByteSize.ByteSize;
|
|
121
127
|
readonly maxPathBytes?: ByteSize.ByteSize;
|
|
122
|
-
} | undefined) => Effect.Effect<OverlayVolume, import("../VfsError.js").ArgumentFailure | ImageFailure,
|
|
128
|
+
} | undefined) => Effect.Effect<OverlayVolume, import("../VfsError.js").ArgumentFailure | ImageFailure, Crypto.Crypto>;
|
|
123
129
|
export {};
|
|
124
130
|
//# sourceMappingURL=virtualFileSystem.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"virtualFileSystem.d.ts","sourceRoot":"","sources":["../../src/internal/virtualFileSystem.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"virtualFileSystem.d.ts","sourceRoot":"","sources":["../../src/internal/virtualFileSystem.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,QAAQ,MAAM,iBAAiB,CAAA;AAE3C,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AACvC,OAAO,KAAK,IAAI,MAAM,aAAa,CAAA;AACnC,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AA4BvC,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AAE9C,OAAO,EAAE,KAAK,SAAS,EAAE,KAAK,YAAY,EAAqB,MAAM,gBAAgB,CAAA;AACrF,OAAO,KAAK,EAMV,aAAa,EAEb,MAAM,EACN,YAAY,EAIb,MAAM,yBAAyB,CAAA;AA2BhC,OAAO,KAAK,SAAS,MAAM,gBAAgB,CAAA;AAE3C,OAAO,EAAkB,KAAK,gBAAgB,EAAyB,MAAM,kBAAkB,CAAA;AAqB/F,OAAO,EAgBL,KAAK,WAAW,EAEjB,MAAM,kBAAkB,CAAA;AAWzB,KAAK,aAAa,GAAG,WAAW,GAAG,UAAU,GAAG,SAAS,CAAA;AAEzD,UAAU,cAAc,CAAC,KAAK;IAC5B,uDAAuD;IACvD,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,KAAK,KAAK,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,SAAS,CAAC,CAAA;IACvE,QAAQ,CAAC,MAAM,EAAE,CAAC,SAAS,EAAE,KAAK,KAAK,MAAM,CAAC,MAAM,CAAC,aAAa,CAAC,CAAA;CACpE;AAwTD,KAAK,YAAY,GACb;IAAE,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAA;CAAE,GAC1B;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,QAAQ,EAAE,SAAS,CAAC,QAAQ,CAAA;CAAE,GAChE;IAAE,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,WAAW,CAAA;CAAE,CAAA;AAE9D,gBAAgB;AAChB,eAAO,MAAM,YAAY;;uBALJ,OAAO;;;;uBACP,MAAM;2BAAqB,SAAS,CAAC,QAAQ;;;;uBAC7C,UAAU;wBAAkB,WAAW;;;;;;;;;;;;;uBAFvC,OAAO;;;uBACP,MAAM;2BAAqB,SAAS,CAAC,QAAQ;;;uBAC7C,UAAU;wBAAkB,WAAW;;CAGD,CAAA;AAsD3D,gBAAgB;AAChB,eAAO,MAAM,UAAU;;;;;;;;mGAMV,WAAW,UACV,SAAS,CAAC,MAAM,UAChB,YAAY,KACjB,MAAM,CAAC,MAAM,CAAC,UAAU,EAAE,YAAY,CAAC;;;;;;;;;;;2EAmmG/C,CAAA;AAED,gBAAgB;AAChB,eAAO,MAAM,IAAI;;;;;;;;gGAIf,CAAA;AAEF,gBAAgB;AAChB,eAAO,MAAM,qBAAqB;;;;;;;;oIAShC,CAAA;AAEF,gBAAgB;AAChB,eAAO,MAAM,eAAe,yFAGV,UAAU,KAAK,MAAM,CAAC,MAAM,CAAC,WAAW,GAAG,UAAU,GAAG,SAAS,CAAC;;;2EA+ClF,CAAA;AAeF,gBAAgB;AAChB,eAAO,MAAM,YAAY;;;;;;;;+GAKxB,CAAA;AAED,gBAAgB;AAChB,eAAO,MAAM,WAAW;;;;;;;;sHAkCvB,CAAA"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import * as Arr from "effect/Array";
|
|
1
2
|
import * as ByteSize from "effect/ByteSize";
|
|
2
3
|
import * as Clock from "effect/Clock";
|
|
3
4
|
import * as Crypto from "effect/Crypto";
|
|
@@ -5,9 +6,9 @@ import * as Data from "effect/Data";
|
|
|
5
6
|
import * as Effect from "effect/Effect";
|
|
6
7
|
import * as Encoding from "effect/Encoding";
|
|
7
8
|
import * as Exit from "effect/Exit";
|
|
9
|
+
import * as Match from "effect/Match";
|
|
8
10
|
import * as Option from "effect/Option";
|
|
9
11
|
import * as Predicate from "effect/Predicate";
|
|
10
|
-
import * as Random from "effect/Random";
|
|
11
12
|
import * as Result from "effect/Result";
|
|
12
13
|
import * as Schema from "effect/Schema";
|
|
13
14
|
import * as Scope from "effect/Scope";
|
|
@@ -31,10 +32,9 @@ import * as LiveImage from "./liveImage.js";
|
|
|
31
32
|
import * as MetadataDomain from "./metadata.js";
|
|
32
33
|
import { compareOverlay } from "./overlayDiff.js";
|
|
33
34
|
import { DOT_DOT_HEX, DOT_HEX, inputBytes, isAttachedBytes, isDotComponent, isWellFormed, joinPath, MAX_NAME_BYTES, MAX_SYMLINK_TRAVERSALS, nameBytes, ownedPath, preparePath, ROOT_PATH, SLASH_BYTE, SLASH_HEX } from "./path.js";
|
|
34
|
-
import { offerCommit } from "./stagedState.js";
|
|
35
35
|
import { VolumeTestSeams } from "./testSeams.js";
|
|
36
36
|
import { makeTurnstile } from "./turnstile.js";
|
|
37
|
-
import { directoryMetadata, emptyRoot, getNode, Ino, MAX_FILE_BYTES, reachableValue, ROOT_INO, storedMetadata, WALK_YIELD_INTERVAL } from "./volumeState.js";
|
|
37
|
+
import { byEntryName, directoryMetadata, emptyRoot, getNode, Ino, MAX_FILE_BYTES, reachableValue, ROOT_INO, storedMetadata, WALK_YIELD_INTERVAL } from "./volumeState.js";
|
|
38
38
|
import * as WatchHub from "./watchHub.js";
|
|
39
39
|
const EXECUTE = 0o1;
|
|
40
40
|
const WRITE = 0o2;
|
|
@@ -43,6 +43,20 @@ const ANY_EXECUTE = 0o111;
|
|
|
43
43
|
const SET_ID_BITS = 0o6000;
|
|
44
44
|
const STICKY_BIT = 0o1000;
|
|
45
45
|
const RELATIME_INTERVAL_NS = 86400000000000n; // relatime, as Linux mounts by default: a read refreshes an access time at least this old, 24 hours.
|
|
46
|
+
// An unknown commit outcome stops the volume; definite rejection stops it only during cleanup.
|
|
47
|
+
const offerCommit = (provider, operation, candidate, cleanup) => Effect.map(Effect.exit(Effect.suspend(() => provider.commit(candidate))), (committed) => {
|
|
48
|
+
// TODO(#186): Preserve the cause of a provider defect or interruption.
|
|
49
|
+
if (Exit.isFailure(committed)) {
|
|
50
|
+
return { available: false, failure: fsFailure("OutcomeUnknown", operation) };
|
|
51
|
+
}
|
|
52
|
+
if (committed.value === "rejected") {
|
|
53
|
+
return { available: !cleanup, failure: fsFailure("StorageRejected", operation) };
|
|
54
|
+
}
|
|
55
|
+
if (committed.value === "unknown") {
|
|
56
|
+
return { available: false, failure: fsFailure("OutcomeUnknown", operation) };
|
|
57
|
+
}
|
|
58
|
+
return { available: true, failure: undefined };
|
|
59
|
+
});
|
|
46
60
|
const WALK_CHUNK_ENTRIES = 128;
|
|
47
61
|
const MAX_FILE_OFFSET = 0x7fffffffffffffffn;
|
|
48
62
|
const isMode = Schema.is(Mode);
|
|
@@ -55,7 +69,7 @@ const decodeOwnerUpdate = Schema.decodeEffect(OwnerUpdate, { onExcessProperty: "
|
|
|
55
69
|
const decodeTimes = Schema.decodeEffect(Times, { onExcessProperty: "error" });
|
|
56
70
|
const decodeExpected = Schema.decodeEffect(SetattrOptions.fields.expected.schema, { onExcessProperty: "error" });
|
|
57
71
|
const SETATTR_FIELDS = Object.keys(SetattrOptions.fields);
|
|
58
|
-
const timeAt = (update, value, now) => update === undefined
|
|
72
|
+
const timeAt = (update, value, now) => update === undefined ? value : Match.value(update).pipe(Match.discriminator("kind")("omit", () => value), Match.discriminator("kind")("now", () => now), Match.discriminator("kind")("value", ({ nanoseconds }) => nanoseconds), Match.exhaustive);
|
|
59
73
|
const decodeWriteFileOptions = Schema.decodeEffect(WriteFileOptions, { onExcessProperty: "error" });
|
|
60
74
|
const decodeOpenOptions = Schema.decodeEffect(OpenOptions, { onExcessProperty: "error" });
|
|
61
75
|
const decodeMkdirOptions = Schema.decodeEffect(MkdirOptions, { onExcessProperty: "error" });
|
|
@@ -212,28 +226,14 @@ const observeChanges = Effect.fnUntraced(function* (captured) {
|
|
|
212
226
|
return observation;
|
|
213
227
|
});
|
|
214
228
|
const randomHex128 = Effect.gen(function* () {
|
|
215
|
-
const crypto = yield*
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
let hex = "";
|
|
219
|
-
for (let draw = 0; draw < 4; draw++) {
|
|
220
|
-
hex += (yield* Random.nextIntBetween(0, 0x100000000, { halfOpen: true })).toString(16).padStart(8, "0");
|
|
221
|
-
}
|
|
222
|
-
return hex;
|
|
223
|
-
});
|
|
224
|
-
// The 128-bit secret behind every key's tag, always from the platform's cryptographically secure generator. Unlike an
|
|
225
|
-
// identity or an epoch it must not follow a seedable Random: every key hands out the identity and epoch, so a
|
|
226
|
-
// reproducible secret would let one key forge the tag of any other inode. Web Crypto is global in every supported
|
|
227
|
-
// runtime, so reading it adds no service requirement; a runtime without it is a defect, not a recoverable failure.
|
|
228
|
-
const randomKeySecret = Effect.sync(() => {
|
|
229
|
-
const webCrypto = globalThis.crypto;
|
|
230
|
-
if (webCrypto === undefined) {
|
|
231
|
-
throw new Error("globalThis.crypto is unavailable, so a volume cannot draw its reference-key secret");
|
|
232
|
-
}
|
|
233
|
-
return Encoding.encodeHex(webCrypto.getRandomValues(new Uint8Array(16)));
|
|
234
|
-
});
|
|
229
|
+
const crypto = yield* Crypto.Crypto;
|
|
230
|
+
return Encoding.encodeHex(yield* crypto.randomBytes(16));
|
|
231
|
+
}).pipe(Effect.orDie);
|
|
235
232
|
/** @internal */
|
|
236
233
|
export const makeVolume = Effect.fnUntraced(function* (source, options, commitProvider, captureInitial, durability = "memory-only") {
|
|
234
|
+
const services = yield* Effect.context();
|
|
235
|
+
// These accessors run inside synchronous transition callbacks. The runner is reached only when an invariant fails.
|
|
236
|
+
const dieInvariant = (message) => Effect.runSyncWith(services)(Effect.die(message));
|
|
237
237
|
const live = Predicate.isTagged("Live")(source) ? source.restored : undefined;
|
|
238
238
|
let restoredOptions = options;
|
|
239
239
|
if (live !== undefined) {
|
|
@@ -255,7 +255,7 @@ export const makeVolume = Effect.fnUntraced(function* (source, options, commitPr
|
|
|
255
255
|
: VolumeIdentity.make(settings.identity);
|
|
256
256
|
const incarnation = VolumeIncarnation.make(yield* randomHex128);
|
|
257
257
|
const epoch = live === undefined ? VolumeEpoch.make(yield* randomHex128) : live.epoch;
|
|
258
|
-
const keySecret = live === undefined ? KeySecret.make(yield*
|
|
258
|
+
const keySecret = live === undefined ? KeySecret.make(yield* randomHex128) : live.keySecret;
|
|
259
259
|
const identityBytes = Result.getOrThrow(Encoding.decodeHex(identity));
|
|
260
260
|
const epochBytes = Result.getOrThrow(Encoding.decodeHex(epoch));
|
|
261
261
|
const keySecretBytes = Result.getOrThrow(Encoding.decodeHex(keySecret));
|
|
@@ -421,7 +421,7 @@ export const makeVolume = Effect.fnUntraced(function* (source, options, commitPr
|
|
|
421
421
|
}));
|
|
422
422
|
const current = () => {
|
|
423
423
|
if (draft === undefined)
|
|
424
|
-
|
|
424
|
+
return dieInvariant("Volume mutation outside a transition");
|
|
425
425
|
return draft;
|
|
426
426
|
};
|
|
427
427
|
const pathOf = (get, ino) => {
|
|
@@ -913,14 +913,15 @@ export const makeVolume = Effect.fnUntraced(function* (source, options, commitPr
|
|
|
913
913
|
};
|
|
914
914
|
const directoryNow = (ino) => {
|
|
915
915
|
const node = view(ino);
|
|
916
|
-
if (node?.kind !== "directory")
|
|
917
|
-
|
|
916
|
+
if (node?.kind !== "directory") {
|
|
917
|
+
return dieInvariant("Directory left the inode table during a transition");
|
|
918
|
+
}
|
|
918
919
|
return node;
|
|
919
920
|
};
|
|
920
921
|
const nodeNow = (ino) => {
|
|
921
922
|
const node = view(ino);
|
|
922
923
|
if (node === undefined)
|
|
923
|
-
|
|
924
|
+
return dieInvariant("Inode left the table during a transition");
|
|
924
925
|
return node;
|
|
925
926
|
};
|
|
926
927
|
const creationTimes = (times, now) => ({
|
|
@@ -1740,29 +1741,26 @@ export const makeVolume = Effect.fnUntraced(function* (source, options, commitPr
|
|
|
1740
1741
|
});
|
|
1741
1742
|
const walkChildren = (directory, path, depth, up) => {
|
|
1742
1743
|
const listed = referenceFor(directory.ino);
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
const childIno = directory.entries.get(name);
|
|
1746
|
-
const child = childIno === undefined ? undefined : view(childIno);
|
|
1744
|
+
return Arr.flatMap(Arr.sort(directory.entries, byEntryName), ([name, childIno]) => {
|
|
1745
|
+
const child = view(childIno);
|
|
1747
1746
|
if (child === undefined)
|
|
1748
|
-
|
|
1747
|
+
return [];
|
|
1749
1748
|
const bytes = nameBytes(name);
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
}
|
|
1765
|
-
return frames;
|
|
1749
|
+
return [{
|
|
1750
|
+
ino: child.ino,
|
|
1751
|
+
kind: child.kind,
|
|
1752
|
+
name: bytes,
|
|
1753
|
+
key: name,
|
|
1754
|
+
path: joinPath(path, bytes),
|
|
1755
|
+
parent: directory.ino,
|
|
1756
|
+
up,
|
|
1757
|
+
depth: depth + 1,
|
|
1758
|
+
bytes: child.kind === "directory" ? 0n : child.metadata.size,
|
|
1759
|
+
reference: referenceFor(child.ino),
|
|
1760
|
+
directory: listed,
|
|
1761
|
+
listed: false
|
|
1762
|
+
}];
|
|
1763
|
+
});
|
|
1766
1764
|
};
|
|
1767
1765
|
const anchorFrame = (parent, key, root) => ({
|
|
1768
1766
|
ino: root.ino,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as Brand from "effect/Brand";
|
|
2
2
|
import * as Effect from "effect/Effect";
|
|
3
|
+
import * as Order from "effect/Order";
|
|
3
4
|
import type { Metadata } from "../Metadata.js";
|
|
4
5
|
import * as InodeTable from "./inodeTable.js";
|
|
5
6
|
import type { StoredMetadata } from "./metadata.js";
|
|
@@ -64,9 +65,9 @@ export interface VolumeState {
|
|
|
64
65
|
/** @internal */
|
|
65
66
|
export declare const getNode: (state: VolumeState, ino: Ino) => Node | undefined;
|
|
66
67
|
/** @internal */
|
|
67
|
-
export declare const byIno:
|
|
68
|
+
export declare const byIno: Order.Order<Node>;
|
|
68
69
|
/** @internal */
|
|
69
|
-
export declare const byEntryName:
|
|
70
|
+
export declare const byEntryName: Order.Order<readonly [string, Ino]>;
|
|
70
71
|
/** @internal */
|
|
71
72
|
export declare const payloadOf: (node: Node) => Uint8Array | undefined;
|
|
72
73
|
/** @internal */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"volumeState.d.ts","sourceRoot":"","sources":["../../src/internal/volumeState.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,cAAc,CAAA;AACrC,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;
|
|
1
|
+
{"version":3,"file":"volumeState.d.ts","sourceRoot":"","sources":["../../src/internal/volumeState.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import * as Brand from "effect/Brand";
|
|
2
2
|
import * as Effect from "effect/Effect";
|
|
3
|
+
import * as Match from "effect/Match";
|
|
4
|
+
import * as Order from "effect/Order";
|
|
3
5
|
import * as InodeTable from "./inodeTable.js";
|
|
4
6
|
/** @internal */
|
|
5
7
|
export const Ino = Brand.nominal();
|
|
@@ -19,9 +21,9 @@ export const WALK_YIELD_INTERVAL = 128;
|
|
|
19
21
|
/** @internal */
|
|
20
22
|
export const getNode = (state, ino) => InodeTable.get(state.inodes, ino);
|
|
21
23
|
/** @internal */
|
|
22
|
-
export const byIno = (
|
|
24
|
+
export const byIno = Order.mapInput(Order.Number, (node) => node.ino);
|
|
23
25
|
/** @internal */
|
|
24
|
-
export const byEntryName = (
|
|
26
|
+
export const byEntryName = Order.mapInput(Order.String, ([name]) => name);
|
|
25
27
|
const inNameOrder = (entries) => {
|
|
26
28
|
let previous;
|
|
27
29
|
for (const name of entries.keys()) {
|
|
@@ -170,34 +172,30 @@ export const assemble = (specs) => {
|
|
|
170
172
|
const owner = Symbol();
|
|
171
173
|
let inodes = InodeTable.empty();
|
|
172
174
|
for (const spec of specs) {
|
|
173
|
-
const base = { ...spec.metadata,
|
|
174
|
-
const node = spec.kind
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
:
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
links: spec.links,
|
|
198
|
-
metadata: { ...base, nlink: spec.links.length, size: BigInt(spec.target.length) },
|
|
199
|
-
revision: spec.revision
|
|
200
|
-
};
|
|
175
|
+
const base = { ...spec.metadata, ino: BigInt(spec.ino) };
|
|
176
|
+
const node = Match.value(spec).pipe(Match.discriminator("kind")("directory", (directory) => ({
|
|
177
|
+
kind: "directory",
|
|
178
|
+
ino: directory.ino,
|
|
179
|
+
parent: directory.parent,
|
|
180
|
+
name: directory.name,
|
|
181
|
+
entries: new Map((edges.get(directory.ino) ?? []).sort(byEntryName)),
|
|
182
|
+
metadata: { ...base, kind: "directory", nlink: 2 + (subdirectories.get(directory.ino) ?? 0), size: 0n },
|
|
183
|
+
revision: directory.revision
|
|
184
|
+
})), Match.discriminator("kind")("file", (file) => ({
|
|
185
|
+
kind: "file",
|
|
186
|
+
ino: file.ino,
|
|
187
|
+
data: file.data,
|
|
188
|
+
links: file.links,
|
|
189
|
+
metadata: { ...base, kind: "file", nlink: file.links.length, size: BigInt(file.data.length) },
|
|
190
|
+
revision: file.revision
|
|
191
|
+
})), Match.discriminator("kind")("symlink", (symlink) => ({
|
|
192
|
+
kind: "symlink",
|
|
193
|
+
ino: symlink.ino,
|
|
194
|
+
target: symlink.target,
|
|
195
|
+
links: symlink.links,
|
|
196
|
+
metadata: { ...base, kind: "symlink", nlink: symlink.links.length, size: BigInt(symlink.target.length) },
|
|
197
|
+
revision: symlink.revision
|
|
198
|
+
})), Match.exhaustive);
|
|
201
199
|
inodes = InodeTable.set(inodes, spec.ino, node, owner);
|
|
202
200
|
}
|
|
203
201
|
return { inodes, open: new Map(), nextInode, revision, entries, usedBytes };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@effect-vfs/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "A runtime-neutral virtual filesystem core for Effect.",
|
|
5
5
|
"homepage": "https://github.com/lloydrichards/effect-virtual-fs#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -21,18 +21,18 @@
|
|
|
21
21
|
"scripts": {
|
|
22
22
|
"build": "tsc --project tsconfig.build.json && tsc --project compatibility/node-next/tsconfig.json",
|
|
23
23
|
"clean": "git clean -xdf .cache .turbo dist docs node_modules tsconfig.tsbuildinfo",
|
|
24
|
-
"test": "vitest run",
|
|
24
|
+
"test": "bun --bun run vitest run --config ../../vitest.config.ts --project core",
|
|
25
25
|
"type-check": "tsc --noEmit && tsc --project contracts/tsconfig.json"
|
|
26
26
|
},
|
|
27
27
|
"peerDependencies": {
|
|
28
|
-
"effect": "4.0.0-rc.
|
|
28
|
+
"effect": "4.0.0-rc.117"
|
|
29
29
|
},
|
|
30
30
|
"devDependencies": {
|
|
31
|
-
"@effect/platform-bun": "4.0.0-rc.
|
|
32
|
-
"@effect/platform-node-shared": "4.0.0-rc.
|
|
33
|
-
"@effect/vitest": "4.0.0-rc.
|
|
31
|
+
"@effect/platform-bun": "4.0.0-rc.117",
|
|
32
|
+
"@effect/platform-node-shared": "4.0.0-rc.117",
|
|
33
|
+
"@effect/vitest": "4.0.0-rc.117",
|
|
34
34
|
"@repo/config-typescript": "workspace:*",
|
|
35
|
-
"effect": "4.0.0-rc.
|
|
35
|
+
"effect": "4.0.0-rc.117"
|
|
36
36
|
},
|
|
37
37
|
"exports": {
|
|
38
38
|
".": {
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import * as Effect from "effect/Effect";
|
|
2
|
-
import type { FsFailure } from "../VfsError.js";
|
|
3
|
-
type CommitOutcome = "committed" | "rejected" | "unknown";
|
|
4
|
-
/** @internal */
|
|
5
|
-
export interface CommitProvider<State> {
|
|
6
|
-
/** Preparation failures leave the volume available. */
|
|
7
|
-
readonly prepare?: (candidate: State) => Effect.Effect<void, FsFailure>;
|
|
8
|
-
readonly commit: (candidate: State) => Effect.Effect<CommitOutcome>;
|
|
9
|
-
}
|
|
10
|
-
/** @internal */
|
|
11
|
-
export interface Classified {
|
|
12
|
-
readonly available: boolean;
|
|
13
|
-
readonly failure: FsFailure | undefined;
|
|
14
|
-
}
|
|
15
|
-
/** @internal */
|
|
16
|
-
export declare const offerCommit: <State>(provider: CommitProvider<State>, operation: string, candidate: State, cleanup: boolean) => Effect.Effect<Classified>;
|
|
17
|
-
export {};
|
|
18
|
-
//# sourceMappingURL=stagedState.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"stagedState.d.ts","sourceRoot":"","sources":["../../src/internal/stagedState.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AAEvC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAG/C,KAAK,aAAa,GAAG,WAAW,GAAG,UAAU,GAAG,SAAS,CAAA;AAEzD,gBAAgB;AAChB,MAAM,WAAW,cAAc,CAAC,KAAK;IACnC,uDAAuD;IACvD,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,KAAK,KAAK,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,SAAS,CAAC,CAAA;IACvE,QAAQ,CAAC,MAAM,EAAE,CAAC,SAAS,EAAE,KAAK,KAAK,MAAM,CAAC,MAAM,CAAC,aAAa,CAAC,CAAA;CACpE;AAED,gBAAgB;AAChB,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAA;IAC3B,QAAQ,CAAC,OAAO,EAAE,SAAS,GAAG,SAAS,CAAA;CACxC;AAGD,gBAAgB;AAChB,eAAO,MAAM,WAAW,GAAI,KAAK,YACrB,cAAc,CAAC,KAAK,CAAC,aACpB,MAAM,aACN,KAAK,WACP,OAAO,KACf,MAAM,CAAC,MAAM,CAAC,UAAU,CAgBvB,CAAA"}
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import * as Effect from "effect/Effect";
|
|
2
|
-
import * as Exit from "effect/Exit";
|
|
3
|
-
import { fsFailure } from "./errors.js";
|
|
4
|
-
// An unknown commit outcome stops the volume; definite rejection stops it only during cleanup.
|
|
5
|
-
/** @internal */
|
|
6
|
-
export const offerCommit = (provider, operation, candidate, cleanup) => Effect.map(Effect.exit(Effect.suspend(() => provider.commit(candidate))), (committed) => {
|
|
7
|
-
// TODO(#186): Preserve the cause of a provider defect or interruption.
|
|
8
|
-
if (Exit.isFailure(committed)) {
|
|
9
|
-
return { available: false, failure: fsFailure("OutcomeUnknown", operation) };
|
|
10
|
-
}
|
|
11
|
-
if (committed.value === "rejected") {
|
|
12
|
-
return { available: !cleanup, failure: fsFailure("StorageRejected", operation) };
|
|
13
|
-
}
|
|
14
|
-
if (committed.value === "unknown") {
|
|
15
|
-
return { available: false, failure: fsFailure("OutcomeUnknown", operation) };
|
|
16
|
-
}
|
|
17
|
-
return { available: true, failure: undefined };
|
|
18
|
-
});
|