@effect-vfs/core 0.7.1 → 0.9.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 +21 -235
- package/dist/Caller.d.ts +2 -0
- package/dist/Caller.d.ts.map +1 -1
- package/dist/Caller.js +3 -1
- package/dist/LiveVolume.d.ts +69 -1
- package/dist/LiveVolume.d.ts.map +1 -1
- package/dist/LiveVolume.js +89 -12
- package/dist/Search.d.ts +730 -0
- package/dist/Search.d.ts.map +1 -0
- package/dist/Search.js +576 -0
- package/dist/SnapshotDelta.d.ts +234 -0
- package/dist/SnapshotDelta.d.ts.map +1 -1
- package/dist/SnapshotDelta.js +107 -0
- package/dist/VfsError.d.ts +3 -1
- package/dist/VfsError.d.ts.map +1 -1
- package/dist/VfsError.js +3 -1
- package/dist/VirtualFileSystem.d.ts +78 -2
- package/dist/VirtualFileSystem.d.ts.map +1 -1
- package/dist/VirtualFileSystem.js +68 -4
- package/dist/Watch.d.ts +18 -0
- package/dist/Watch.d.ts.map +1 -1
- package/dist/Watch.js +12 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -0
- package/dist/internal/callerWatch.d.ts +32 -0
- package/dist/internal/callerWatch.d.ts.map +1 -0
- package/dist/internal/callerWatch.js +184 -0
- package/dist/internal/confinement.d.ts +16 -0
- package/dist/internal/confinement.d.ts.map +1 -0
- package/dist/internal/confinement.js +79 -0
- package/dist/internal/directoryEntries.d.ts +6 -0
- package/dist/internal/directoryEntries.d.ts.map +1 -0
- package/dist/internal/directoryEntries.js +79 -0
- package/dist/internal/fixture.js +4 -4
- package/dist/internal/glob.d.ts +66 -0
- package/dist/internal/glob.d.ts.map +1 -0
- package/dist/internal/glob.js +248 -0
- package/dist/internal/image.d.ts +7 -1
- package/dist/internal/image.d.ts.map +1 -1
- package/dist/internal/image.js +9 -7
- package/dist/internal/liveImage.d.ts +1 -1
- package/dist/internal/liveImage.d.ts.map +1 -1
- package/dist/internal/liveImage.js +2 -10
- package/dist/internal/path.js +2 -2
- package/dist/internal/resolution.d.ts +139 -0
- package/dist/internal/resolution.d.ts.map +1 -0
- package/dist/internal/resolution.js +332 -0
- package/dist/internal/search.d.ts +24 -0
- package/dist/internal/search.d.ts.map +1 -0
- package/dist/internal/search.js +78 -0
- package/dist/internal/searchContent.d.ts +77 -0
- package/dist/internal/searchContent.d.ts.map +1 -0
- package/dist/internal/searchContent.js +223 -0
- package/dist/internal/searchModel.d.ts +83 -0
- package/dist/internal/searchModel.d.ts.map +1 -0
- package/dist/internal/searchModel.js +23 -0
- package/dist/internal/snapshotDelta.d.ts +470 -1
- package/dist/internal/snapshotDelta.d.ts.map +1 -1
- package/dist/internal/snapshotDelta.js +59 -25
- package/dist/internal/snapshotMerge.d.ts +54 -0
- package/dist/internal/snapshotMerge.d.ts.map +1 -0
- package/dist/internal/snapshotMerge.js +959 -0
- package/dist/internal/tokenRegistry.d.ts +83 -0
- package/dist/internal/tokenRegistry.d.ts.map +1 -0
- package/dist/internal/tokenRegistry.js +146 -0
- package/dist/internal/virtualFileSystem.d.ts +32 -17
- package/dist/internal/virtualFileSystem.d.ts.map +1 -1
- package/dist/internal/virtualFileSystem.js +503 -736
- package/dist/internal/volumeLimits.d.ts +53 -0
- package/dist/internal/volumeLimits.d.ts.map +1 -0
- package/dist/internal/volumeLimits.js +63 -0
- package/dist/internal/volumeState.d.ts +2 -0
- package/dist/internal/volumeState.d.ts.map +1 -1
- package/dist/internal/volumeState.js +9 -0
- package/dist/internal/watchHub.d.ts +6 -2
- package/dist/internal/watchHub.d.ts.map +1 -1
- package/dist/internal/watchHub.js +31 -6
- package/package.json +9 -3
|
@@ -23,10 +23,11 @@ import * as FixtureInternal from "./internal/fixture.js";
|
|
|
23
23
|
import * as Image from "./internal/image.js";
|
|
24
24
|
import * as Path from "./internal/path.js";
|
|
25
25
|
import * as SnapshotDeltaInternal from "./internal/snapshotDelta.js";
|
|
26
|
+
import * as SnapshotMergeInternal from "./internal/snapshotMerge.js";
|
|
26
27
|
import * as VfsModel from "./internal/virtualFileSystem.js";
|
|
27
28
|
import { open as openLiveVolume } from "./LiveVolume.js";
|
|
28
29
|
import * as SnapshotDeltaModel from "./SnapshotDelta.js";
|
|
29
|
-
export { SnapshotChange, SnapshotChangesOptions, SnapshotDeltaLimits, SnapshotDeltaTypeId, SnapshotDifference, SnapshotNodeKind } from "./SnapshotDelta.js";
|
|
30
|
+
export { MergeConflict, MergeConflictReason, MergeOptions, MergeResolution, MergeResult, MergeSideChange, MergeTake, SnapshotChange, SnapshotChangesOptions, SnapshotDeltaLimits, SnapshotDeltaTypeId, SnapshotDifference, SnapshotNodeKind } from "./SnapshotDelta.js";
|
|
30
31
|
/**
|
|
31
32
|
* Schema for portable virtual filesystem error codes.
|
|
32
33
|
*
|
|
@@ -572,6 +573,12 @@ export const Change = WatchModule.Change;
|
|
|
572
573
|
* @since 0.6.0
|
|
573
574
|
*/
|
|
574
575
|
export const WatchOptions = WatchModule.WatchOptions;
|
|
576
|
+
/**
|
|
577
|
+
* Caller watch publication and alias options.
|
|
578
|
+
* @category schemas
|
|
579
|
+
* @since 0.9.0
|
|
580
|
+
*/
|
|
581
|
+
export const CallerWatchOptions = WatchModule.CallerWatchOptions;
|
|
575
582
|
/**
|
|
576
583
|
* Schema for the filesystem entry kinds reported by overlay summaries.
|
|
577
584
|
*
|
|
@@ -895,7 +902,7 @@ const deltaLimits = (operation, limits) => Effect.fromResult(decodeConfiguration
|
|
|
895
902
|
* @category snapshots
|
|
896
903
|
* @since 0.1.0
|
|
897
904
|
*/
|
|
898
|
-
export const diffSnapshots = Effect.
|
|
905
|
+
export const diffSnapshots = Effect.fnUntraced(function* (base, target, limits) {
|
|
899
906
|
// The delta codec fails under its own name; the public entry point names itself.
|
|
900
907
|
return yield* SnapshotDeltaInternal.diffSnapshots(base, target, yield* deltaLimits("diffSnapshots", limits)).pipe(Effect.mapError((error) => retargetFailure("diffSnapshots", error)));
|
|
901
908
|
});
|
|
@@ -934,7 +941,7 @@ export const diffSnapshots = Effect.fn("VirtualFileSystem.diffSnapshots")(functi
|
|
|
934
941
|
* @category snapshots
|
|
935
942
|
* @since 0.1.0
|
|
936
943
|
*/
|
|
937
|
-
export const inspectSnapshotDelta = Effect.
|
|
944
|
+
export const inspectSnapshotDelta = Effect.fnUntraced(function* (base, delta, options, limits) {
|
|
938
945
|
const decoded = yield* Effect.fromResult(decodeConfiguration(SnapshotDeltaModel.SnapshotChangesOptions, options ?? {}, "inspectSnapshotDelta"));
|
|
939
946
|
return yield* SnapshotDeltaInternal.inspectSnapshotDelta(base, delta, decoded, yield* deltaLimits("inspectSnapshotDelta", limits)).pipe(Effect.mapError((error) => retargetFailure("inspectSnapshotDelta", error)));
|
|
940
947
|
});
|
|
@@ -978,9 +985,66 @@ export const inspectSnapshotDelta = Effect.fn("VirtualFileSystem.inspectSnapshot
|
|
|
978
985
|
* @category snapshots
|
|
979
986
|
* @since 0.1.0
|
|
980
987
|
*/
|
|
981
|
-
export const applySnapshotDelta = Effect.
|
|
988
|
+
export const applySnapshotDelta = Effect.fnUntraced(function* (base, delta, limits) {
|
|
982
989
|
return yield* SnapshotDeltaInternal.applySnapshotDelta(base, delta, yield* deltaLimits("applySnapshotDelta", limits)).pipe(Effect.mapError((error) => retargetFailure("applySnapshotDelta", error)));
|
|
983
990
|
});
|
|
991
|
+
/**
|
|
992
|
+
* Merges two snapshot deltas that share one base into a single delta, reporting path-level conflicts as data.
|
|
993
|
+
*
|
|
994
|
+
* Changes only one side made are taken as they are. Where both sides changed one path the merge keeps the change
|
|
995
|
+
* when both agree, combines metadata fields that only one side changed, and otherwise reports a conflict and leaves
|
|
996
|
+
* the base node in place. Timestamps never conflict. The merged delta is bound to the same base as its inputs and
|
|
997
|
+
* applies with {@link applySnapshotDelta}. Pass `resolutions` to take one side at paths an earlier merge reported.
|
|
998
|
+
* Requires the platform-neutral `Crypto.Crypto` service for base identity. Provide
|
|
999
|
+
* `NodeCrypto.layer`, `BunCrypto.layer`, or your own via `Crypto.make`.
|
|
1000
|
+
*
|
|
1001
|
+
* @example
|
|
1002
|
+
* ```ts
|
|
1003
|
+
* import * as BunCrypto from "@effect/platform-bun/BunCrypto"
|
|
1004
|
+
* import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
|
|
1005
|
+
* import { Effect } from "effect"
|
|
1006
|
+
*
|
|
1007
|
+
* const encoder = new TextEncoder()
|
|
1008
|
+
*
|
|
1009
|
+
* const program = Effect.gen(function*() {
|
|
1010
|
+
* const source = yield* Vfs.fromFixture({
|
|
1011
|
+
* entries: [{ kind: "file", path: "/shared.txt", bytes: encoder.encode("v0") }]
|
|
1012
|
+
* })
|
|
1013
|
+
* const base = yield* source.snapshot
|
|
1014
|
+
*
|
|
1015
|
+
* // Two agents work on separate overlays of one base.
|
|
1016
|
+
* const ours = yield* Vfs.makeOverlay(base)
|
|
1017
|
+
* const theirs = yield* Vfs.makeOverlay(base)
|
|
1018
|
+
* yield* (yield* ours.caller()).writeFile("/ours.txt", encoder.encode("a"), { access: "write", create: "exclusive" })
|
|
1019
|
+
* yield* (yield* theirs.caller()).writeFile("/shared.txt", encoder.encode("v1"), { access: "write", truncate: true })
|
|
1020
|
+
*
|
|
1021
|
+
* const result = yield* Vfs.mergeSnapshotDeltas(
|
|
1022
|
+
* base,
|
|
1023
|
+
* yield* Vfs.diffSnapshots(base, (yield* ours.capture()).snapshot),
|
|
1024
|
+
* yield* Vfs.diffSnapshots(base, (yield* theirs.capture()).snapshot)
|
|
1025
|
+
* )
|
|
1026
|
+
*
|
|
1027
|
+
* // Disjoint changes merge without conflicts; the delta applies to the shared base.
|
|
1028
|
+
* const merged = yield* Vfs.fromSnapshot(yield* Vfs.applySnapshotDelta(base, result.delta))
|
|
1029
|
+
* const reader = yield* merged.caller()
|
|
1030
|
+
*
|
|
1031
|
+
* return [result.conflicts.length, new TextDecoder().decode(yield* reader.readFile("/shared.txt"))]
|
|
1032
|
+
* })
|
|
1033
|
+
*
|
|
1034
|
+
* Effect.runPromise(program.pipe(Effect.provide(BunCrypto.layer))).then(console.log)
|
|
1035
|
+
* // [ 0, 'v1' ]
|
|
1036
|
+
* ```
|
|
1037
|
+
*
|
|
1038
|
+
* @see {@link diffSnapshots} to build each side's delta, {@link applySnapshotDelta} to apply the merged one.
|
|
1039
|
+
* @category snapshots
|
|
1040
|
+
* @since 0.8.0
|
|
1041
|
+
*/
|
|
1042
|
+
export const mergeSnapshotDeltas = Effect.fnUntraced(function* (base, ours, theirs, options) {
|
|
1043
|
+
// Limits decode first so a bad limit names its own field; the rest of the options decode as one.
|
|
1044
|
+
const limits = yield* deltaLimits("mergeSnapshotDeltas", options?.limits);
|
|
1045
|
+
const decoded = yield* Effect.fromResult(decodeConfiguration(SnapshotDeltaModel.MergeOptions, options ?? {}, "mergeSnapshotDeltas"));
|
|
1046
|
+
return yield* SnapshotMergeInternal.mergeSnapshotDeltas(base, ours, theirs, decoded.resolutions ?? [], limits).pipe(Effect.mapError((error) => retargetFailure("mergeSnapshotDeltas", error)));
|
|
1047
|
+
});
|
|
984
1048
|
const deltaSchemaIssue = (cause, input, options) => new SchemaIssue.InvalidValue({ message: `Snapshot delta ${cause.code}${cause.field === undefined ? "" : ` at ${cause.field}`}` }, input, options);
|
|
985
1049
|
/**
|
|
986
1050
|
* Creates an Effect Schema codec between owned bytes and opaque snapshot deltas.
|
package/dist/Watch.d.ts
CHANGED
|
@@ -65,4 +65,22 @@ export declare const WatchOptions: Schema.Struct<{
|
|
|
65
65
|
* @since 0.6.0
|
|
66
66
|
*/
|
|
67
67
|
export type WatchOptions = typeof WatchOptions.Type;
|
|
68
|
+
/**
|
|
69
|
+
* Options for a caller watch. `resolved` follows the selected path alias
|
|
70
|
+
* through rename and ends when that name is unlinked. `all` reports every
|
|
71
|
+
* currently authorized alias of the object.
|
|
72
|
+
*
|
|
73
|
+
* @category schemas
|
|
74
|
+
* @since 0.9.0
|
|
75
|
+
*/
|
|
76
|
+
export declare const CallerWatchOptions: Schema.Struct<{
|
|
77
|
+
readonly recursive: Schema.optionalKey<Schema.Boolean>;
|
|
78
|
+
readonly alias: Schema.optionalKey<Schema.Literals<readonly ["all", "resolved"]>>;
|
|
79
|
+
}>;
|
|
80
|
+
/**
|
|
81
|
+
* Options for a caller watch.
|
|
82
|
+
* @category models
|
|
83
|
+
* @since 0.9.0
|
|
84
|
+
*/
|
|
85
|
+
export type CallerWatchOptions = typeof CallerWatchOptions.Type;
|
|
68
86
|
//# sourceMappingURL=Watch.d.ts.map
|
package/dist/Watch.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Watch.d.ts","sourceRoot":"","sources":["../src/Watch.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AACvC,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AAExC,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAA;AAE7D;;;;;;;GAOG;AACH,eAAO,MAAM,MAAM;;;;;;;;;;;;;EAKjB,CAAA;AAEF;;;;;GAKG;AACH,MAAM,MAAM,MAAM,GAAG,OAAO,MAAM,CAAC,IAAI,CAAA;AAEvC;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,YAAY;;;EAIvB,CAAA;AAEF;;;;;GAKG;AACH,MAAM,MAAM,YAAY,GAAG,OAAO,YAAY,CAAC,IAAI,CAAA"}
|
|
1
|
+
{"version":3,"file":"Watch.d.ts","sourceRoot":"","sources":["../src/Watch.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AACvC,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AAExC,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAA;AAE7D;;;;;;;GAOG;AACH,eAAO,MAAM,MAAM;;;;;;;;;;;;;EAKjB,CAAA;AAEF;;;;;GAKG;AACH,MAAM,MAAM,MAAM,GAAG,OAAO,MAAM,CAAC,IAAI,CAAA;AAEvC;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,YAAY;;;EAIvB,CAAA;AAEF;;;;;GAKG;AACH,MAAM,MAAM,YAAY,GAAG,OAAO,YAAY,CAAC,IAAI,CAAA;AAEnD;;;;;;;GAOG;AACH,eAAO,MAAM,kBAAkB;;;EAG7B,CAAA;AAEF;;;;GAIG;AACH,MAAM,MAAM,kBAAkB,GAAG,OAAO,kBAAkB,CAAC,IAAI,CAAA"}
|
package/dist/Watch.js
CHANGED
|
@@ -44,3 +44,15 @@ export const WatchOptions = Schema.Struct({
|
|
|
44
44
|
scope: Schema.optionalKey(ObjectReferenceSchema),
|
|
45
45
|
recursive: Schema.optionalKey(Schema.Boolean)
|
|
46
46
|
});
|
|
47
|
+
/**
|
|
48
|
+
* Options for a caller watch. `resolved` follows the selected path alias
|
|
49
|
+
* through rename and ends when that name is unlinked. `all` reports every
|
|
50
|
+
* currently authorized alias of the object.
|
|
51
|
+
*
|
|
52
|
+
* @category schemas
|
|
53
|
+
* @since 0.9.0
|
|
54
|
+
*/
|
|
55
|
+
export const CallerWatchOptions = Schema.Struct({
|
|
56
|
+
recursive: Schema.optionalKey(Schema.Boolean),
|
|
57
|
+
alias: Schema.optionalKey(Schema.Literals(["all", "resolved"]))
|
|
58
|
+
});
|
package/dist/index.d.ts
CHANGED
|
@@ -79,4 +79,11 @@ export * as Volume from "./Volume.js";
|
|
|
79
79
|
* @category re-exports
|
|
80
80
|
*/
|
|
81
81
|
export * as Watch from "./Watch.js";
|
|
82
|
+
/**
|
|
83
|
+
* Bounded filename search over immutable snapshots.
|
|
84
|
+
*
|
|
85
|
+
* @category re-exports
|
|
86
|
+
* @since 0.8.0
|
|
87
|
+
*/
|
|
88
|
+
export * as Search from "./Search.js";
|
|
82
89
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH;;;;;GAKG;AACH,OAAO,KAAK,iBAAiB,MAAM,wBAAwB,CAAA;AAE3D;;;;;GAKG;AACH,OAAO,KAAK,QAAQ,MAAM,eAAe,CAAA;AAEzC;;;;;GAKG;AACH,OAAO,KAAK,UAAU,MAAM,iBAAiB,CAAA;AAE7C;;;GAGG;AACH,OAAO,KAAK,QAAQ,MAAM,eAAe,CAAA;AAEzC;;;GAGG;AACH,OAAO,KAAK,MAAM,MAAM,aAAa,CAAA;AAErC;;;GAGG;AACH,OAAO,KAAK,UAAU,MAAM,iBAAiB,CAAA;AAE7C;;;GAGG;AACH,OAAO,KAAK,OAAO,MAAM,cAAc,CAAA;AAEvC;;;GAGG;AACH,OAAO,KAAK,QAAQ,MAAM,eAAe,CAAA;AAEzC;;;GAGG;AACH,OAAO,KAAK,QAAQ,MAAM,eAAe,CAAA;AAEzC;;;GAGG;AACH,OAAO,KAAK,aAAa,MAAM,oBAAoB,CAAA;AAEnD;;;GAGG;AACH,OAAO,KAAK,MAAM,MAAM,aAAa,CAAA;AAErC;;;GAGG;AACH,OAAO,KAAK,OAAO,MAAM,cAAc,CAAA;AAEvC;;;GAGG;AACH,OAAO,KAAK,MAAM,MAAM,aAAa,CAAA;AAErC;;;GAGG;AACH,OAAO,KAAK,KAAK,MAAM,YAAY,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH;;;;;GAKG;AACH,OAAO,KAAK,iBAAiB,MAAM,wBAAwB,CAAA;AAE3D;;;;;GAKG;AACH,OAAO,KAAK,QAAQ,MAAM,eAAe,CAAA;AAEzC;;;;;GAKG;AACH,OAAO,KAAK,UAAU,MAAM,iBAAiB,CAAA;AAE7C;;;GAGG;AACH,OAAO,KAAK,QAAQ,MAAM,eAAe,CAAA;AAEzC;;;GAGG;AACH,OAAO,KAAK,MAAM,MAAM,aAAa,CAAA;AAErC;;;GAGG;AACH,OAAO,KAAK,UAAU,MAAM,iBAAiB,CAAA;AAE7C;;;GAGG;AACH,OAAO,KAAK,OAAO,MAAM,cAAc,CAAA;AAEvC;;;GAGG;AACH,OAAO,KAAK,QAAQ,MAAM,eAAe,CAAA;AAEzC;;;GAGG;AACH,OAAO,KAAK,QAAQ,MAAM,eAAe,CAAA;AAEzC;;;GAGG;AACH,OAAO,KAAK,aAAa,MAAM,oBAAoB,CAAA;AAEnD;;;GAGG;AACH,OAAO,KAAK,MAAM,MAAM,aAAa,CAAA;AAErC;;;GAGG;AACH,OAAO,KAAK,OAAO,MAAM,cAAc,CAAA;AAEvC;;;GAGG;AACH,OAAO,KAAK,MAAM,MAAM,aAAa,CAAA;AAErC;;;GAGG;AACH,OAAO,KAAK,KAAK,MAAM,YAAY,CAAA;AAEnC;;;;;GAKG;AACH,OAAO,KAAK,MAAM,MAAM,aAAa,CAAA"}
|
package/dist/index.js
CHANGED
|
@@ -79,3 +79,10 @@ export * as Volume from "./Volume.js";
|
|
|
79
79
|
* @category re-exports
|
|
80
80
|
*/
|
|
81
81
|
export * as Watch from "./Watch.js";
|
|
82
|
+
/**
|
|
83
|
+
* Bounded filename search over immutable snapshots.
|
|
84
|
+
*
|
|
85
|
+
* @category re-exports
|
|
86
|
+
* @since 0.8.0
|
|
87
|
+
*/
|
|
88
|
+
export * as Search from "./Search.js";
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { FsFailure } from "../VfsError.js";
|
|
2
|
+
import { Change } from "../Watch.js";
|
|
3
|
+
import { type Ino, type Link, type VolumeState } from "./volumeState.js";
|
|
4
|
+
import type { Selection } from "./watchHub.js";
|
|
5
|
+
/** @internal */
|
|
6
|
+
export interface WatchEvent {
|
|
7
|
+
readonly change: Change;
|
|
8
|
+
readonly parent: Ino;
|
|
9
|
+
readonly ino: Ino;
|
|
10
|
+
readonly name?: string;
|
|
11
|
+
}
|
|
12
|
+
/** @internal */
|
|
13
|
+
export interface Rename {
|
|
14
|
+
readonly ino: Ino;
|
|
15
|
+
readonly from: Link;
|
|
16
|
+
readonly to: Link;
|
|
17
|
+
}
|
|
18
|
+
/** @internal */
|
|
19
|
+
export interface Installation {
|
|
20
|
+
readonly before: VolumeState;
|
|
21
|
+
readonly after: VolumeState;
|
|
22
|
+
readonly renames?: ReadonlyArray<Rename>;
|
|
23
|
+
}
|
|
24
|
+
/** Select and rebase names while publication holds both installed namespace states. @internal */
|
|
25
|
+
export declare const make: (options: {
|
|
26
|
+
readonly roots: ReadonlyArray<Ino>;
|
|
27
|
+
readonly additionalRoots?: ReadonlyArray<ReadonlyArray<Ino>>;
|
|
28
|
+
readonly target: Ino;
|
|
29
|
+
readonly recursive: boolean;
|
|
30
|
+
readonly selected?: Link;
|
|
31
|
+
}) => Selection<WatchEvent, Installation, FsFailure>;
|
|
32
|
+
//# sourceMappingURL=callerWatch.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"callerWatch.d.ts","sourceRoot":"","sources":["../../src/internal/callerWatch.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAC/C,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AAIpC,OAAO,EAAW,KAAK,GAAG,EAAE,KAAK,IAAI,EAAY,KAAK,WAAW,EAAE,MAAM,kBAAkB,CAAA;AAC3F,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,eAAe,CAAA;AAE9C,gBAAgB;AAChB,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,MAAM,EAAE,GAAG,CAAA;IACpB,QAAQ,CAAC,GAAG,EAAE,GAAG,CAAA;IACjB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CACvB;AAED,gBAAgB;AAChB,MAAM,WAAW,MAAM;IACrB,QAAQ,CAAC,GAAG,EAAE,GAAG,CAAA;IACjB,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAA;IACnB,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAA;CAClB;AAED,gBAAgB;AAChB,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAA;IAC5B,QAAQ,CAAC,KAAK,EAAE,WAAW,CAAA;IAC3B,QAAQ,CAAC,OAAO,CAAC,EAAE,aAAa,CAAC,MAAM,CAAC,CAAA;CACzC;AA6ED,iGAAiG;AACjG,eAAO,MAAM,IAAI,YAAa;IAC5B,QAAQ,CAAC,KAAK,EAAE,aAAa,CAAC,GAAG,CAAC,CAAA;IAClC,QAAQ,CAAC,eAAe,CAAC,EAAE,aAAa,CAAC,aAAa,CAAC,GAAG,CAAC,CAAC,CAAA;IAC5D,QAAQ,CAAC,MAAM,EAAE,GAAG,CAAA;IACpB,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAA;IAC3B,QAAQ,CAAC,QAAQ,CAAC,EAAE,IAAI,CAAA;CACzB,KAAG,SAAS,CAAC,UAAU,EAAE,YAAY,EAAE,SAAS,CA6IhD,CAAA"}
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import * as Predicate from "effect/Predicate";
|
|
2
|
+
import { Change } from "../Watch.js";
|
|
3
|
+
import { getBytes } from "./bytePath.js";
|
|
4
|
+
import { fsFailure } from "./errors.js";
|
|
5
|
+
import { nameBytes, ownedPath, SLASH_HEX } from "./path.js";
|
|
6
|
+
import { getNode, ROOT_INO } from "./volumeState.js";
|
|
7
|
+
const directoryPath = (state, ino) => {
|
|
8
|
+
const names = [];
|
|
9
|
+
const visited = new Set();
|
|
10
|
+
let at = ino;
|
|
11
|
+
for (;;) {
|
|
12
|
+
if (visited.has(at))
|
|
13
|
+
return undefined;
|
|
14
|
+
visited.add(at);
|
|
15
|
+
const directory = getNode(state, at);
|
|
16
|
+
if (directory?.kind !== "directory" || directory.metadata.nlink === 0)
|
|
17
|
+
return undefined;
|
|
18
|
+
if (at === ROOT_INO)
|
|
19
|
+
return SLASH_HEX + names.reverse().join(SLASH_HEX);
|
|
20
|
+
names.push(directory.name);
|
|
21
|
+
at = directory.parent;
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
const below = (path, root) => root === SLASH_HEX || path === root || path.startsWith(root + SLASH_HEX);
|
|
25
|
+
const rebase = (path, root) => root === SLASH_HEX ? path : path === root ? SLASH_HEX : path.slice(root.length);
|
|
26
|
+
const entryPath = (state, link) => {
|
|
27
|
+
const parent = directoryPath(state, link.parent);
|
|
28
|
+
return parent === undefined ? undefined : parent + (parent === SLASH_HEX ? "" : SLASH_HEX) + link.name;
|
|
29
|
+
};
|
|
30
|
+
const paths = (state, target) => {
|
|
31
|
+
const node = getNode(state, target);
|
|
32
|
+
if (node === undefined || node.metadata.nlink === 0)
|
|
33
|
+
return [];
|
|
34
|
+
if (node.kind === "directory") {
|
|
35
|
+
const path = directoryPath(state, target);
|
|
36
|
+
return path === undefined ? [] : [path];
|
|
37
|
+
}
|
|
38
|
+
return node.links.flatMap((link) => {
|
|
39
|
+
const directory = getNode(state, link.parent);
|
|
40
|
+
const path = directory?.kind === "directory" && directory.entries.get(link.name) === target
|
|
41
|
+
? entryPath(state, link)
|
|
42
|
+
: undefined;
|
|
43
|
+
return path === undefined ? [] : [path];
|
|
44
|
+
});
|
|
45
|
+
};
|
|
46
|
+
const rootPath = (state, roots) => {
|
|
47
|
+
let previous = SLASH_HEX;
|
|
48
|
+
for (const root of roots) {
|
|
49
|
+
const path = directoryPath(state, root);
|
|
50
|
+
if (path === undefined)
|
|
51
|
+
return fsFailure("ClosedCaller", "watch");
|
|
52
|
+
if (!below(path, previous))
|
|
53
|
+
return fsFailure("AccessDenied", "watch");
|
|
54
|
+
previous = path;
|
|
55
|
+
}
|
|
56
|
+
return previous;
|
|
57
|
+
};
|
|
58
|
+
const eventName = (event) => {
|
|
59
|
+
if (event.name !== undefined)
|
|
60
|
+
return event.name;
|
|
61
|
+
const bytes = getBytes(event.change.path);
|
|
62
|
+
const last = bytes.lastIndexOf(47);
|
|
63
|
+
return Array.from(bytes.subarray(last + 1), (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
64
|
+
};
|
|
65
|
+
/** Select and rebase names while publication holds both installed namespace states. @internal */
|
|
66
|
+
export const make = (options) => {
|
|
67
|
+
let selected = options.selected;
|
|
68
|
+
let oldSelected = selected;
|
|
69
|
+
let failure;
|
|
70
|
+
let previousRoot = SLASH_HEX;
|
|
71
|
+
let currentRoot = SLASH_HEX;
|
|
72
|
+
let terminal;
|
|
73
|
+
let rescanPath = SLASH_HEX;
|
|
74
|
+
const visible = (state, root, selection) => {
|
|
75
|
+
const aliases = paths(state, options.target);
|
|
76
|
+
for (const chain of options.additionalRoots ?? []) {
|
|
77
|
+
const boundary = rootPath(state, chain);
|
|
78
|
+
if (!Predicate.isString(boundary) || !aliases.some((path) => below(path, boundary)))
|
|
79
|
+
return [];
|
|
80
|
+
}
|
|
81
|
+
return aliases.filter((path) => below(path, root) && (selection === undefined || path === entryPath(state, selection)));
|
|
82
|
+
};
|
|
83
|
+
return {
|
|
84
|
+
boundedTerminal: true,
|
|
85
|
+
prepare: ({ before, after, renames }) => {
|
|
86
|
+
terminal = undefined;
|
|
87
|
+
oldSelected = selected;
|
|
88
|
+
const beforeRoot = rootPath(before, options.roots);
|
|
89
|
+
const afterRoot = rootPath(after, options.roots);
|
|
90
|
+
for (const chain of options.additionalRoots ?? []) {
|
|
91
|
+
const boundary = rootPath(after, chain);
|
|
92
|
+
if (!Predicate.isString(boundary)) {
|
|
93
|
+
failure = boundary;
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
if (!Predicate.isString(afterRoot)) {
|
|
98
|
+
failure = afterRoot;
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
if (!Predicate.isString(beforeRoot))
|
|
102
|
+
return;
|
|
103
|
+
previousRoot = beforeRoot;
|
|
104
|
+
currentRoot = afterRoot;
|
|
105
|
+
for (const rename of renames ?? []) {
|
|
106
|
+
if (rename.ino === options.target && selected !== undefined && selected.parent === rename.from.parent &&
|
|
107
|
+
selected.name === rename.from.name)
|
|
108
|
+
selected = rename.to;
|
|
109
|
+
}
|
|
110
|
+
const oldPaths = visible(before, previousRoot, oldSelected);
|
|
111
|
+
const nextPaths = visible(after, currentRoot, selected);
|
|
112
|
+
rescanPath = rebase(nextPaths[0] ?? oldPaths[0] ?? currentRoot, nextPaths.length > 0 ? currentRoot : previousRoot);
|
|
113
|
+
if (nextPaths.length === 0)
|
|
114
|
+
terminal = oldPaths.map((path) => rebase(path, previousRoot));
|
|
115
|
+
},
|
|
116
|
+
includes: () => failure === undefined,
|
|
117
|
+
project: (event, { before, after }) => {
|
|
118
|
+
if (failure !== undefined)
|
|
119
|
+
return undefined;
|
|
120
|
+
if (terminal === undefined && event.ino !== options.target && Predicate.isTagged(event.change, "Create")) {
|
|
121
|
+
const name = eventName(event);
|
|
122
|
+
const parent = getNode(before, event.parent);
|
|
123
|
+
const replacedPath = entryPath(before, { parent: event.parent, name });
|
|
124
|
+
// Rename publishes the incoming object; its replaced alias needs the old target's removal.
|
|
125
|
+
if (parent?.kind === "directory" && parent.entries.get(name) === options.target &&
|
|
126
|
+
replacedPath !== undefined && visible(before, previousRoot, oldSelected).includes(replacedPath)) {
|
|
127
|
+
return {
|
|
128
|
+
...event,
|
|
129
|
+
ino: options.target,
|
|
130
|
+
change: Change.cases.Remove.make({ path: ownedPath(nameBytes(rebase(replacedPath, previousRoot))) })
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
const removed = Predicate.isTagged(event.change, "Remove");
|
|
135
|
+
const installed = removed ? before : after;
|
|
136
|
+
const root = removed ? previousRoot : currentRoot;
|
|
137
|
+
const selection = removed ? oldSelected : selected;
|
|
138
|
+
let path;
|
|
139
|
+
const node = getNode(installed, event.ino);
|
|
140
|
+
if (event.ino === options.target) {
|
|
141
|
+
// Settlement emits the terminal removals once, behind already queued changes.
|
|
142
|
+
if (removed && terminal !== undefined)
|
|
143
|
+
return undefined;
|
|
144
|
+
path = node?.kind === "directory"
|
|
145
|
+
? directoryPath(installed, event.ino)
|
|
146
|
+
: entryPath(installed, { parent: event.parent, name: eventName(event) });
|
|
147
|
+
if (selection !== undefined && path !== entryPath(installed, selection))
|
|
148
|
+
return undefined;
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
const scopes = visible(installed, root, selection);
|
|
152
|
+
path = entryPath(installed, { parent: event.parent, name: eventName(event) });
|
|
153
|
+
if (path === undefined || !scopes.some((scope) => {
|
|
154
|
+
if (!below(path, scope))
|
|
155
|
+
return false;
|
|
156
|
+
if (options.recursive)
|
|
157
|
+
return true;
|
|
158
|
+
return directoryPath(installed, event.parent) === scope;
|
|
159
|
+
}))
|
|
160
|
+
return undefined;
|
|
161
|
+
}
|
|
162
|
+
if (path === undefined || !below(path, root))
|
|
163
|
+
return undefined;
|
|
164
|
+
// A confinement root's outside parent is never reported as a rename.
|
|
165
|
+
if (event.ino === options.roots.at(-1) && !Predicate.isTagged(event.change, "Update"))
|
|
166
|
+
return undefined;
|
|
167
|
+
return {
|
|
168
|
+
...event,
|
|
169
|
+
change: Change.cases[event.change._tag].make({ path: ownedPath(nameBytes(rebase(path, root))) })
|
|
170
|
+
};
|
|
171
|
+
},
|
|
172
|
+
rescan: () => ({
|
|
173
|
+
change: Change.cases.Rescan.make({ path: ownedPath(nameBytes(rescanPath)) }),
|
|
174
|
+
parent: options.target,
|
|
175
|
+
ino: options.target
|
|
176
|
+
}),
|
|
177
|
+
failure: () => failure,
|
|
178
|
+
settle: () => terminal?.map((path) => ({
|
|
179
|
+
change: Change.cases.Remove.make({ path: ownedPath(nameBytes(path)) }),
|
|
180
|
+
parent: options.target,
|
|
181
|
+
ino: options.target
|
|
182
|
+
}))
|
|
183
|
+
};
|
|
184
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import * as Effect from "effect/Effect";
|
|
2
|
+
import type { FsFailure } from "../VfsError.js";
|
|
3
|
+
import type { OpContext } from "./errors.js";
|
|
4
|
+
import { type Ino, type Node } from "./volumeState.js";
|
|
5
|
+
/** @internal */
|
|
6
|
+
export declare const make: (view: Effect.Effect<(ino: Ino) => Node | undefined>, roots: ReadonlyArray<Ino>) => {
|
|
7
|
+
roots: readonly Ino[];
|
|
8
|
+
root: Ino;
|
|
9
|
+
alive: (op: OpContext, handle?: boolean) => Effect.Effect<undefined, FsFailure, never>;
|
|
10
|
+
contains: (node: Node) => Effect.Effect<boolean, never, never>;
|
|
11
|
+
check: (node: Node, op: OpContext, handle?: boolean | undefined) => Effect.Effect<void, FsFailure, never>;
|
|
12
|
+
path: (ino: Ino) => Effect.Effect<string | undefined, never, never>;
|
|
13
|
+
};
|
|
14
|
+
/** @internal */
|
|
15
|
+
export type Confinement = ReturnType<typeof make>;
|
|
16
|
+
//# sourceMappingURL=confinement.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"confinement.d.ts","sourceRoot":"","sources":["../../src/internal/confinement.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AACvC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAC/C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AAC5C,OAAO,EAAE,KAAK,GAAG,EAAE,KAAK,IAAI,EAAiC,MAAM,kBAAkB,CAAA;AAErF,gBAAgB;AAChB,eAAO,MAAM,IAAI,SAAU,MAAM,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,GAAG,KAAK,IAAI,GAAG,SAAS,CAAC,SAAS,aAAa,CAAC,GAAG,CAAC;;;gBAoD9E,SAAS,WAAU,OAAO;qBAErB,IAAI;;;CAuC7B,CAAA;AAED,gBAAgB;AAChB,MAAM,MAAM,WAAW,GAAG,UAAU,CAAC,OAAO,IAAI,CAAC,CAAA"}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import * as Effect from "effect/Effect";
|
|
2
|
+
import { ROOT_INO, WALK_YIELD_INTERVAL } from "./volumeState.js";
|
|
3
|
+
/** @internal */
|
|
4
|
+
export const make = (view, roots) => {
|
|
5
|
+
const descends = Effect.fnUntraced(function* (get, ino, root) {
|
|
6
|
+
let node = get(ino);
|
|
7
|
+
let steps = 0;
|
|
8
|
+
while (node?.kind === "directory" && node.metadata.nlink > 0) {
|
|
9
|
+
if (node.ino === root)
|
|
10
|
+
return true;
|
|
11
|
+
if (node.ino === ROOT_INO)
|
|
12
|
+
return false;
|
|
13
|
+
const parent = get(node.parent);
|
|
14
|
+
if (parent?.kind !== "directory" || parent.entries.get(node.name) !== node.ino)
|
|
15
|
+
return false;
|
|
16
|
+
node = parent;
|
|
17
|
+
if (++steps % WALK_YIELD_INTERVAL === 0)
|
|
18
|
+
yield* Effect.yieldNow;
|
|
19
|
+
}
|
|
20
|
+
return false;
|
|
21
|
+
});
|
|
22
|
+
const aliveIn = Effect.fnUntraced(function* (get, op, handle) {
|
|
23
|
+
for (let index = 0; index < roots.length; index++) {
|
|
24
|
+
const root = roots[index];
|
|
25
|
+
const node = get(root);
|
|
26
|
+
if (node?.kind !== "directory" || node.metadata.nlink === 0) {
|
|
27
|
+
return yield* op.fail(handle ? "InvalidHandle" : "ClosedCaller");
|
|
28
|
+
}
|
|
29
|
+
if (index > 0 && !(yield* descends(get, root, roots[index - 1])))
|
|
30
|
+
return yield* op.fail("AccessDenied");
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
const containsIn = Effect.fnUntraced(function* (get, node) {
|
|
34
|
+
const root = roots.at(-1);
|
|
35
|
+
if (root === undefined)
|
|
36
|
+
return true;
|
|
37
|
+
if (node.kind === "directory")
|
|
38
|
+
return yield* descends(get, node.ino, root);
|
|
39
|
+
for (const link of node.links) {
|
|
40
|
+
const parent = get(link.parent);
|
|
41
|
+
if (parent?.kind === "directory" && parent.entries.get(link.name) === node.ino &&
|
|
42
|
+
(yield* descends(get, parent.ino, root)))
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
45
|
+
return false;
|
|
46
|
+
});
|
|
47
|
+
const alive = (op, handle = false) => Effect.flatMap(view, (get) => aliveIn(get, op, handle));
|
|
48
|
+
const contains = (node) => Effect.flatMap(view, (get) => containsIn(get, node));
|
|
49
|
+
const check = Effect.fnUntraced(function* (node, op, handle = false) {
|
|
50
|
+
// Share the current lookup within authorization, never its result between operations.
|
|
51
|
+
const get = yield* view;
|
|
52
|
+
yield* aliveIn(get, op, handle);
|
|
53
|
+
if (!(yield* containsIn(get, node)))
|
|
54
|
+
return yield* op.fail("AccessDenied");
|
|
55
|
+
});
|
|
56
|
+
const path = Effect.fnUntraced(function* (ino) {
|
|
57
|
+
const names = [];
|
|
58
|
+
const get = yield* view;
|
|
59
|
+
let node = get(ino);
|
|
60
|
+
let steps = 0;
|
|
61
|
+
if (node?.kind !== "directory" || node.metadata.nlink === 0)
|
|
62
|
+
return undefined;
|
|
63
|
+
const root = roots.at(-1) ?? ROOT_INO;
|
|
64
|
+
while (node.ino !== root) {
|
|
65
|
+
if (node.ino === ROOT_INO)
|
|
66
|
+
return undefined;
|
|
67
|
+
names.push(node.name);
|
|
68
|
+
const parent = get(node.parent);
|
|
69
|
+
if (parent?.kind !== "directory" || parent.metadata.nlink === 0 || parent.entries.get(node.name) !== node.ino) {
|
|
70
|
+
return undefined;
|
|
71
|
+
}
|
|
72
|
+
node = parent;
|
|
73
|
+
if (++steps % WALK_YIELD_INTERVAL === 0)
|
|
74
|
+
yield* Effect.yieldNow;
|
|
75
|
+
}
|
|
76
|
+
return "2f" + names.reverse().join("2f");
|
|
77
|
+
});
|
|
78
|
+
return { roots, root: roots.at(-1) ?? ROOT_INO, alive, contains, check, path };
|
|
79
|
+
};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { Ino } from "./volumeState.js";
|
|
2
|
+
/** @internal */
|
|
3
|
+
export declare const set: (entries: ReadonlyMap<string, Ino>, name: string, ino: Ino) => ReadonlyMap<string, Ino>;
|
|
4
|
+
/** @internal */
|
|
5
|
+
export declare const remove: (entries: ReadonlyMap<string, Ino>, name: string) => ReadonlyMap<string, Ino>;
|
|
6
|
+
//# sourceMappingURL=directoryEntries.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"directoryEntries.d.ts","sourceRoot":"","sources":["../../src/internal/directoryEntries.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,kBAAkB,CAAA;AA2E3C,gBAAgB;AAChB,eAAO,MAAM,GAAG,YAAa,WAAW,CAAC,MAAM,EAAE,GAAG,CAAC,QAAQ,MAAM,OAAO,GAAG,KAAG,WAAW,CAAC,MAAM,EAAE,GAAG,CAYtG,CAAA;AAED,gBAAgB;AAChB,eAAO,MAAM,MAAM,YAAa,WAAW,CAAC,MAAM,EAAE,GAAG,CAAC,QAAQ,MAAM,KAAG,WAAW,CAAC,MAAM,EAAE,GAAG,CAa/F,CAAA"}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import * as HashMap from "effect/HashMap";
|
|
2
|
+
import * as Option from "effect/Option";
|
|
3
|
+
import * as InodeTable from "./inodeTable.js";
|
|
4
|
+
// Name lookup and insertion order share immutable trees. Deleted slots are compacted once they outnumber live entries.
|
|
5
|
+
class DirectoryEntries {
|
|
6
|
+
names;
|
|
7
|
+
slots;
|
|
8
|
+
length;
|
|
9
|
+
[Symbol.toStringTag] = "Map";
|
|
10
|
+
constructor(names, slots, length) {
|
|
11
|
+
this.names = names;
|
|
12
|
+
this.slots = slots;
|
|
13
|
+
this.length = length;
|
|
14
|
+
}
|
|
15
|
+
get size() {
|
|
16
|
+
return HashMap.size(this.names);
|
|
17
|
+
}
|
|
18
|
+
get(name) {
|
|
19
|
+
const slot = HashMap.get(this.names, name);
|
|
20
|
+
return Option.isSome(slot) ? slot.value.ino : undefined;
|
|
21
|
+
}
|
|
22
|
+
has(name) {
|
|
23
|
+
return HashMap.has(this.names, name);
|
|
24
|
+
}
|
|
25
|
+
*entries() {
|
|
26
|
+
for (let index = 0; index < this.length; index++) {
|
|
27
|
+
const entry = InodeTable.get(this.slots, index);
|
|
28
|
+
if (entry !== undefined)
|
|
29
|
+
yield [entry.name, entry.ino];
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
*keys() {
|
|
33
|
+
for (const [name] of this)
|
|
34
|
+
yield name;
|
|
35
|
+
}
|
|
36
|
+
*values() {
|
|
37
|
+
for (const [, ino] of this)
|
|
38
|
+
yield ino;
|
|
39
|
+
}
|
|
40
|
+
[Symbol.iterator]() {
|
|
41
|
+
return this.entries();
|
|
42
|
+
}
|
|
43
|
+
// oxlint-disable-next-line anti-slop/no-unknown-parameters -- ReadonlyMap forwards the opaque callback receiver without inspecting it.
|
|
44
|
+
forEach(callback, thisArg) {
|
|
45
|
+
for (const [name, ino] of this)
|
|
46
|
+
callback.call(thisArg, ino, name, this);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
const fromEntries = (entries) => {
|
|
50
|
+
let slots = InodeTable.empty();
|
|
51
|
+
let length = 0;
|
|
52
|
+
const owner = Symbol();
|
|
53
|
+
const names = HashMap.mutate(HashMap.empty(), (mutable) => {
|
|
54
|
+
for (const [name, ino] of entries) {
|
|
55
|
+
const entry = { name, ino, index: length };
|
|
56
|
+
HashMap.set(mutable, name, entry);
|
|
57
|
+
slots = InodeTable.set(slots, length, entry, owner);
|
|
58
|
+
length++;
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
return new DirectoryEntries(names, slots, length);
|
|
62
|
+
};
|
|
63
|
+
/** @internal */
|
|
64
|
+
export const set = (entries, name, ino) => {
|
|
65
|
+
const current = entries instanceof DirectoryEntries ? entries : fromEntries(entries);
|
|
66
|
+
const existing = HashMap.get(current.names, name);
|
|
67
|
+
const index = Option.isSome(existing) ? existing.value.index : current.length;
|
|
68
|
+
const entry = { name, ino, index };
|
|
69
|
+
return new DirectoryEntries(HashMap.set(current.names, name, entry), InodeTable.set(current.slots, index, entry), Option.isSome(existing) ? current.length : current.length + 1);
|
|
70
|
+
};
|
|
71
|
+
/** @internal */
|
|
72
|
+
export const remove = (entries, name) => {
|
|
73
|
+
const current = entries instanceof DirectoryEntries ? entries : fromEntries(entries);
|
|
74
|
+
const existing = HashMap.get(current.names, name);
|
|
75
|
+
if (Option.isNone(existing))
|
|
76
|
+
return current;
|
|
77
|
+
const removed = new DirectoryEntries(HashMap.remove(current.names, name), InodeTable.set(current.slots, existing.value.index, undefined), current.length);
|
|
78
|
+
return removed.length > removed.size * 2 ? fromEntries(removed) : removed;
|
|
79
|
+
};
|
package/dist/internal/fixture.js
CHANGED
|
@@ -5,12 +5,12 @@ import { Fixture as FixtureSchema, FixtureEntry } from "../Fixture.js";
|
|
|
5
5
|
import { VolumeIdentity, VolumeOptions as VolumeOptionsSchema } from "../Volume.js";
|
|
6
6
|
import { decodeConfiguration, imageFailure, OpContext, VfsError } from "./errors.js";
|
|
7
7
|
import { inputBytes, isAttachedBytes, isDotComponent, preparePath } from "./path.js";
|
|
8
|
-
import { makeVolume, VolumeSource } from "./virtualFileSystem.js";
|
|
8
|
+
import { makeVolume, memoryCommitProvider, VolumeSource } from "./virtualFileSystem.js";
|
|
9
9
|
import { assemble, Ino, ROOT_INO } from "./volumeState.js";
|
|
10
10
|
const DEFAULT_MODE = { directory: 0o755, file: 0o644, symlink: 0o777 };
|
|
11
11
|
const EPOCH_NS = 0n;
|
|
12
12
|
/** @internal */
|
|
13
|
-
export const fromFixture = Effect.
|
|
13
|
+
export const fromFixture = Effect.fnUntraced(function* (fixture, options) {
|
|
14
14
|
const config = yield* Effect.fromResult(decodeConfiguration(VolumeOptionsSchema, options ?? {}, "fromFixture"));
|
|
15
15
|
const source = yield* Schema.decodeEffect(FixtureSchema, { onExcessProperty: "error" })(fixture).pipe(Effect.mapError((cause) => imageFailure("fromFixture", "InvalidStructure", { field: "fixture", cause })));
|
|
16
16
|
if (source.entries.some((entry) => FixtureEntry.match(entry, {
|
|
@@ -124,8 +124,8 @@ export const fromFixture = Effect.fn("VirtualFileSystem.fromFixture")(function*
|
|
|
124
124
|
const restored = VolumeSource.Restored({ value: assemble(specs) });
|
|
125
125
|
const { identity, ...volumeOptions } = config;
|
|
126
126
|
if (identity === undefined)
|
|
127
|
-
return (yield* makeVolume(restored, volumeOptions)).volume;
|
|
128
|
-
const { volume } = yield* makeVolume(restored, {
|
|
127
|
+
return (yield* makeVolume(restored, memoryCommitProvider, volumeOptions)).volume;
|
|
128
|
+
const { volume } = yield* makeVolume(restored, memoryCommitProvider, {
|
|
129
129
|
...volumeOptions,
|
|
130
130
|
identity: VolumeIdentity.make(identity)
|
|
131
131
|
});
|