@effect-vfs/core 0.8.0 → 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.
Files changed (41) hide show
  1. package/README.md +9 -0
  2. package/dist/Caller.d.ts +2 -0
  3. package/dist/Caller.d.ts.map +1 -1
  4. package/dist/Caller.js +3 -1
  5. package/dist/LiveVolume.d.ts +69 -1
  6. package/dist/LiveVolume.d.ts.map +1 -1
  7. package/dist/LiveVolume.js +80 -3
  8. package/dist/Search.d.ts.map +1 -1
  9. package/dist/Search.js +12 -4
  10. package/dist/VfsError.d.ts +3 -1
  11. package/dist/VfsError.d.ts.map +1 -1
  12. package/dist/VfsError.js +3 -1
  13. package/dist/VirtualFileSystem.d.ts +25 -1
  14. package/dist/VirtualFileSystem.d.ts.map +1 -1
  15. package/dist/VirtualFileSystem.js +10 -4
  16. package/dist/Watch.d.ts +18 -0
  17. package/dist/Watch.d.ts.map +1 -1
  18. package/dist/Watch.js +12 -0
  19. package/dist/internal/callerWatch.d.ts +32 -0
  20. package/dist/internal/callerWatch.d.ts.map +1 -0
  21. package/dist/internal/callerWatch.js +184 -0
  22. package/dist/internal/confinement.d.ts +16 -0
  23. package/dist/internal/confinement.d.ts.map +1 -0
  24. package/dist/internal/confinement.js +79 -0
  25. package/dist/internal/directoryEntries.d.ts +6 -0
  26. package/dist/internal/directoryEntries.d.ts.map +1 -0
  27. package/dist/internal/directoryEntries.js +79 -0
  28. package/dist/internal/fixture.js +1 -1
  29. package/dist/internal/path.js +2 -2
  30. package/dist/internal/resolution.d.ts +8 -4
  31. package/dist/internal/resolution.d.ts.map +1 -1
  32. package/dist/internal/resolution.js +68 -13
  33. package/dist/internal/tokenRegistry.d.ts +6 -0
  34. package/dist/internal/tokenRegistry.d.ts.map +1 -1
  35. package/dist/internal/tokenRegistry.js +21 -3
  36. package/dist/internal/virtualFileSystem.d.ts.map +1 -1
  37. package/dist/internal/virtualFileSystem.js +245 -80
  38. package/dist/internal/watchHub.d.ts +6 -2
  39. package/dist/internal/watchHub.d.ts.map +1 -1
  40. package/dist/internal/watchHub.js +31 -6
  41. package/package.json +5 -3
@@ -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
+ };
@@ -10,7 +10,7 @@ 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.fn("VirtualFileSystem.fromFixture")(function* (fixture, options) {
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, {
@@ -63,7 +63,7 @@ const attachedBuffer = (bytes) => {
63
63
  /** @internal */
64
64
  export const isAttachedBytes = (bytes) => Predicate.isUint8Array(bytes) && bytes.buffer instanceof ArrayBuffer && attachedBuffer(bytes);
65
65
  /** @internal */
66
- export const pathFromBytes = Effect.fn("VirtualFileSystem.pathFromBytes")(function* (bytes) {
66
+ export const pathFromBytes = Effect.fnUntraced(function* (bytes) {
67
67
  if (!isAttachedBytes(bytes))
68
68
  return yield* fsFailure("InvalidArgument", "pathFromBytes");
69
69
  const owned = new Uint8Array(bytes);
@@ -73,7 +73,7 @@ export const pathFromBytes = Effect.fn("VirtualFileSystem.pathFromBytes")(functi
73
73
  return makeBytePath(owned);
74
74
  });
75
75
  /** @internal */
76
- export const pathToBytes = Effect.fn("VirtualFileSystem.pathToBytes")(function* (path) {
76
+ export const pathToBytes = Effect.fnUntraced(function* (path) {
77
77
  const bytes = getBytePathBytes(path);
78
78
  if (bytes === undefined)
79
79
  return yield* fsFailure("InvalidArgument", "pathToBytes");
@@ -4,7 +4,8 @@ import * as Result from "effect/Result";
4
4
  import type { Identity } from "../Caller.js";
5
5
  import { type EntryInput, type NameInput, type Target } from "../Target.js";
6
6
  import type { FsFailure } from "../VfsError.js";
7
- import type { DirectoryHandle, PathInput } from "../VirtualFileSystem.js";
7
+ import type { DirectoryHandle, FileHandle, PathInput } from "../VirtualFileSystem.js";
8
+ import { type Confinement } from "./confinement.js";
8
9
  import type { OpContext } from "./errors.js";
9
10
  import { type PreparedPath } from "./path.js";
10
11
  import * as TokenRegistry from "./tokenRegistry.js";
@@ -86,7 +87,10 @@ export declare const authorize: (node: Node, identity: Identity, bits: number, o
86
87
  /** @internal */
87
88
  export declare const entryName: (input: NameInput, op: OpContext) => Result.Result<string, FsFailure>;
88
89
  /** @internal */
89
- export declare const make: ({ caller, get, identity, maxPathBytes, registry }: {
90
+ export declare const make: ({ caller, get, identity, maxPathBytes, registry, confinement, additional, authorityView }: {
91
+ readonly additional?: ReadonlyArray<Confinement> | undefined;
92
+ readonly authorityView: Effect.Effect<(ino: Ino) => Node | undefined>;
93
+ readonly confinement?: Confinement | undefined;
90
94
  readonly caller: TokenRegistry.DirectoryReference;
91
95
  readonly get: (ino: Ino) => Effect.Effect<Node | undefined>;
92
96
  readonly identity: Identity;
@@ -102,7 +106,7 @@ export declare const make: ({ caller, get, identity, maxPathBytes, registry }: {
102
106
  };
103
107
  directory: (target: {
104
108
  readonly _tag: "Handle";
105
- readonly handle: import("../VirtualFileSystem.js").FileHandle | DirectoryHandle;
109
+ readonly handle: FileHandle | DirectoryHandle;
106
110
  } | {
107
111
  readonly _tag: "Path";
108
112
  readonly path: PathInput;
@@ -117,7 +121,7 @@ export declare const make: ({ caller, get, identity, maxPathBytes, registry }: {
117
121
  }, FsFailure, never>;
118
122
  entryDirectory: (target: {
119
123
  readonly _tag: "Handle";
120
- readonly handle: import("../VirtualFileSystem.js").FileHandle | DirectoryHandle;
124
+ readonly handle: FileHandle | DirectoryHandle;
121
125
  } | {
122
126
  readonly _tag: "Path";
123
127
  readonly path: PathInput;
@@ -1 +1 @@
1
- {"version":3,"file":"resolution.d.ts","sourceRoot":"","sources":["../../src/internal/resolution.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"resolution.d.ts","sourceRoot":"","sources":["../../src/internal/resolution.ts"],"names":[],"mappings":"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"}
@@ -4,10 +4,12 @@ import * as Hex from "effect/encoding/Hex";
4
4
  import * as Predicate from "effect/Predicate";
5
5
  import * as Result from "effect/Result";
6
6
  import { isEntry, isTarget } from "../Target.js";
7
+ import { make as makeConfinement } from "./confinement.js";
7
8
  import { DOT_DOT_HEX, DOT_HEX, isAttachedBytes, isDotComponent, isWellFormed, MAX_NAME_BYTES, MAX_SYMLINK_TRAVERSALS, nameBytes, ownedPath, preparePath, SLASH_BYTE } from "./path.js";
8
9
  import * as TokenRegistry from "./tokenRegistry.js";
9
10
  import { ROOT_INO } from "./volumeState.js";
10
11
  const encoder = new TextEncoder();
12
+ const EMPTY_AUTHORITIES = [];
11
13
  /** @internal */
12
14
  export const inGroup = (identity, gid) => identity.gid === gid || identity.groups.includes(gid);
13
15
  /** @internal */
@@ -30,28 +32,37 @@ export const entryName = (input, op) => {
30
32
  return Result.succeed(Hex.encode(new Uint8Array(bytes)));
31
33
  };
32
34
  /** @internal */
33
- export const make = ({ caller, get, identity, maxPathBytes, registry }) => {
35
+ export const make = ({ caller, get, identity, maxPathBytes, registry, confinement, additional, authorityView }) => {
36
+ const visibleRoot = confinement?.root ?? ROOT_INO;
34
37
  const prepare = (input, op) => preparePath(input, op.operation, maxPathBytes);
35
38
  const prepareEntry = (input, op) => isEntry(input)
36
39
  ? Result.map(entryName(input.name, op), (name) => ({ kind: "entry", directory: input.directory, name }))
37
40
  : isTarget(input)
38
41
  ? Result.map(prepare(input.path, op), (path) => ({ kind: "path", path, base: input.relativeTo }))
39
42
  : Result.map(prepare(input, op), (path) => ({ kind: "path", path, base: undefined }));
40
- const nodeAt = Effect.fnUntraced(function* (ino) {
41
- const node = yield* get(ino);
43
+ const nodeAt = Effect.fnUntraced(function* (lookup, ino) {
44
+ const node = lookup(ino);
42
45
  return node === undefined ? yield* Effect.die("Inode left the table during resolution") : node;
43
46
  });
44
- const directoryAt = Effect.fnUntraced(function* (ino) {
45
- const node = yield* nodeAt(ino);
47
+ const directoryAt = Effect.fnUntraced(function* (lookup, ino) {
48
+ const node = yield* nodeAt(lookup, ino);
46
49
  return node.kind !== "directory" ? yield* Effect.die("Directory left the table during resolution") : node;
47
50
  });
48
- const callerDirectory = Effect.fnUntraced(function* (op) {
51
+ const callerDirectory = Effect.fnUntraced(function* (lookup, op) {
49
52
  const ino = TokenRegistry.inode(caller);
50
- const node = ino === undefined ? undefined : yield* get(ino);
53
+ const node = ino === undefined ? undefined : lookup(ino);
51
54
  return node?.kind !== "directory" ? yield* op.fail("ClosedCaller") : node;
52
55
  });
53
56
  const walk = Effect.fnUntraced(function* (path, base, op, mode, referencedBase) {
54
57
  const pathOp = op.at(path.input);
58
+ const baseAuthorities = base === undefined
59
+ ? EMPTY_AUTHORITIES
60
+ : registry.handleBoundaries(base).map((roots) => makeConfinement(authorityView, roots));
61
+ const authorizeDirectory = baseAuthorities.length === 0 ? authorize : Effect.fnUntraced(function* (node, callerIdentity, bits, context) {
62
+ for (const authority of baseAuthorities)
63
+ yield* authority.check(node, context, true);
64
+ yield* authorize(node, callerIdentity, bits, context);
65
+ });
55
66
  const parentOnly = mode.kind === "Parent";
56
67
  const allowMissing = mode.kind === "OrCreate" && mode.create !== "never";
57
68
  const followFinalSymlink = mode.kind === "Node"
@@ -61,7 +72,26 @@ export const make = ({ caller, get, identity, maxPathBytes, registry }) => {
61
72
  if (!TokenRegistry.isOpen(caller)) {
62
73
  return yield* pathOp.fail("ClosedCaller");
63
74
  }
64
- let current = path.absolute ? (yield* nodeAt(ROOT_INO)) : yield* callerDirectory(pathOp);
75
+ if (additional !== undefined && additional.length > 0) {
76
+ for (const authority of additional)
77
+ yield* authority.alive(pathOp);
78
+ }
79
+ if (confinement !== undefined)
80
+ yield* confinement.alive(pathOp);
81
+ if (path.absolute && base !== undefined &&
82
+ (confinement !== undefined || (additional?.length ?? 0) > 0 || baseAuthorities.length > 0)) {
83
+ const node = yield* TokenRegistry.nodeOrFail(yield* registry.resolve(base, "directory"), "handle", pathOp);
84
+ yield* registry.authorizeHandle(base, node, pathOp);
85
+ if (confinement !== undefined)
86
+ yield* confinement.check(node, pathOp);
87
+ if (additional !== undefined && additional.length > 0) {
88
+ for (const authority of additional)
89
+ yield* authority.check(node, pathOp);
90
+ }
91
+ }
92
+ // Reads remain live so recursive creation can revisit nodes added earlier in this walk.
93
+ const lookup = yield* authorityView;
94
+ let current = path.absolute ? (yield* nodeAt(lookup, visibleRoot)) : yield* callerDirectory(lookup, pathOp);
65
95
  if (!path.absolute && referencedBase !== undefined) {
66
96
  current = referencedBase;
67
97
  yield* authorize(current, identity, 0o1, op);
@@ -70,9 +100,16 @@ export const make = ({ caller, get, identity, maxPathBytes, registry }) => {
70
100
  const directory = yield* TokenRegistry.nodeOrFail(yield* registry.resolve(base, "directory"), "handle", pathOp);
71
101
  if (directory.kind !== "directory")
72
102
  return yield* pathOp.fail("InvalidHandle");
103
+ yield* registry.authorizeHandle(base, directory, pathOp);
73
104
  current = directory;
74
105
  yield* authorize(current, identity, 0o1, pathOp);
75
106
  }
107
+ if (confinement !== undefined)
108
+ yield* confinement.check(current, pathOp);
109
+ if (additional !== undefined && additional.length > 0) {
110
+ for (const authority of additional)
111
+ yield* authority.check(current, pathOp);
112
+ }
76
113
  if (current.metadata.nlink === 0) {
77
114
  return yield* pathOp.fail("NotFound");
78
115
  }
@@ -87,14 +124,14 @@ export const make = ({ caller, get, identity, maxPathBytes, registry }) => {
87
124
  if (current.kind !== "directory") {
88
125
  return yield* pathOp.fail("NotDirectory");
89
126
  }
90
- yield* authorize(current, identity, 0o1, pathOp);
127
+ yield* authorizeDirectory(current, identity, 0o1, pathOp);
91
128
  const component = work.components[index];
92
129
  if (component === undefined)
93
130
  break;
94
131
  if (component === DOT_HEX)
95
132
  continue;
96
133
  if (component === DOT_DOT_HEX) {
97
- current = yield* directoryAt(current.parent);
134
+ current = yield* directoryAt(lookup, confinement !== undefined && current.ino === visibleRoot ? current.ino : current.parent);
98
135
  parent = undefined;
99
136
  name = undefined;
100
137
  continue;
@@ -102,7 +139,7 @@ export const make = ({ caller, get, identity, maxPathBytes, registry }) => {
102
139
  parent = current;
103
140
  name = component;
104
141
  const childIno = current.entries.get(component);
105
- const child = childIno === undefined ? undefined : (yield* get(childIno));
142
+ const child = childIno === undefined ? undefined : lookup(childIno);
106
143
  if (child === undefined) {
107
144
  if (allowMissing && index === work.components.length - 1) {
108
145
  return { kind: "missing", parent, name, op: pathOp };
@@ -139,12 +176,16 @@ export const make = ({ caller, get, identity, maxPathBytes, registry }) => {
139
176
  work = expanded.success;
140
177
  linked = work.components.length - remaining + Math.max(0, linked - index - 1);
141
178
  if (work.absolute)
142
- current = yield* nodeAt(ROOT_INO);
179
+ current = yield* nodeAt(lookup, visibleRoot);
143
180
  index = -1;
144
181
  }
145
182
  else
146
183
  current = child;
147
184
  }
185
+ if (baseAuthorities.length > 0) {
186
+ for (const authority of baseAuthorities)
187
+ yield* authority.check(current, pathOp, true);
188
+ }
148
189
  if (!parentOnly && work.trailingSlash && current.kind !== "directory") {
149
190
  return yield* pathOp.fail("NotDirectory");
150
191
  }
@@ -168,6 +209,14 @@ export const make = ({ caller, get, identity, maxPathBytes, registry }) => {
168
209
  const node = yield* TokenRegistry.nodeOrFail(yield* (Predicate.isTagged(target, "Reference")
169
210
  ? registry.resolve(target.reference, "reference")
170
211
  : registry.resolve(target.handle, "handle")), Predicate.isTagged(target, "Reference") ? "reference" : "handle", op);
212
+ if (!Predicate.isTagged(target, "Reference"))
213
+ yield* registry.authorizeHandle(target.handle, node, op);
214
+ if (confinement !== undefined)
215
+ yield* confinement.check(node, op);
216
+ if (additional !== undefined && additional.length > 0) {
217
+ for (const authority of additional)
218
+ yield* authority.check(node, op);
219
+ }
171
220
  return { kind: "node", node, op };
172
221
  });
173
222
  const directory = Effect.fnUntraced(function* (target, op) {
@@ -249,7 +298,13 @@ export const make = ({ caller, get, identity, maxPathBytes, registry }) => {
249
298
  : { kind: "entry", node: direct, parent, name: prepared.name, op };
250
299
  if (direct?.kind === "symlink" && mode.finalSymlink === "follow") {
251
300
  const path = yield* Effect.fromResult(preparePath(ownedPath(nameBytes(prepared.name)), op.operation, undefined));
252
- result = yield* walk(path, undefined, op, mode, parent);
301
+ // Following an entry link must retain the directory capability's boundaries.
302
+ const base = Predicate.isTagged(prepared.directory, "Handle")
303
+ ? prepared.directory.handle
304
+ : Predicate.isTagged(prepared.directory, "Path")
305
+ ? prepared.directory.relativeTo
306
+ : undefined;
307
+ result = yield* walk(path, base, op, mode, parent);
253
308
  // Entry writeFile rejects a followed directory before checking exclusive creation.
254
309
  if (mode.create === "exclusive" && mode.entryExclusive === "afterSymlink" &&
255
310
  (result.kind === "node" || (result.kind === "entry" && result.node.kind === "directory"))) {