@effect-vfs/core 0.7.1 → 0.8.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 (56) hide show
  1. package/README.md +15 -238
  2. package/dist/LiveVolume.d.ts.map +1 -1
  3. package/dist/LiveVolume.js +9 -9
  4. package/dist/Search.d.ts +730 -0
  5. package/dist/Search.d.ts.map +1 -0
  6. package/dist/Search.js +568 -0
  7. package/dist/SnapshotDelta.d.ts +234 -0
  8. package/dist/SnapshotDelta.d.ts.map +1 -1
  9. package/dist/SnapshotDelta.js +107 -0
  10. package/dist/VirtualFileSystem.d.ts +53 -1
  11. package/dist/VirtualFileSystem.d.ts.map +1 -1
  12. package/dist/VirtualFileSystem.js +59 -1
  13. package/dist/index.d.ts +7 -0
  14. package/dist/index.d.ts.map +1 -1
  15. package/dist/index.js +7 -0
  16. package/dist/internal/fixture.js +3 -3
  17. package/dist/internal/glob.d.ts +66 -0
  18. package/dist/internal/glob.d.ts.map +1 -0
  19. package/dist/internal/glob.js +248 -0
  20. package/dist/internal/image.d.ts +7 -1
  21. package/dist/internal/image.d.ts.map +1 -1
  22. package/dist/internal/image.js +9 -7
  23. package/dist/internal/liveImage.d.ts +1 -1
  24. package/dist/internal/liveImage.d.ts.map +1 -1
  25. package/dist/internal/liveImage.js +2 -10
  26. package/dist/internal/resolution.d.ts +135 -0
  27. package/dist/internal/resolution.d.ts.map +1 -0
  28. package/dist/internal/resolution.js +277 -0
  29. package/dist/internal/search.d.ts +24 -0
  30. package/dist/internal/search.d.ts.map +1 -0
  31. package/dist/internal/search.js +78 -0
  32. package/dist/internal/searchContent.d.ts +77 -0
  33. package/dist/internal/searchContent.d.ts.map +1 -0
  34. package/dist/internal/searchContent.js +223 -0
  35. package/dist/internal/searchModel.d.ts +83 -0
  36. package/dist/internal/searchModel.d.ts.map +1 -0
  37. package/dist/internal/searchModel.js +23 -0
  38. package/dist/internal/snapshotDelta.d.ts +470 -1
  39. package/dist/internal/snapshotDelta.d.ts.map +1 -1
  40. package/dist/internal/snapshotDelta.js +59 -25
  41. package/dist/internal/snapshotMerge.d.ts +54 -0
  42. package/dist/internal/snapshotMerge.d.ts.map +1 -0
  43. package/dist/internal/snapshotMerge.js +959 -0
  44. package/dist/internal/tokenRegistry.d.ts +77 -0
  45. package/dist/internal/tokenRegistry.d.ts.map +1 -0
  46. package/dist/internal/tokenRegistry.js +128 -0
  47. package/dist/internal/virtualFileSystem.d.ts +32 -17
  48. package/dist/internal/virtualFileSystem.d.ts.map +1 -1
  49. package/dist/internal/virtualFileSystem.js +263 -661
  50. package/dist/internal/volumeLimits.d.ts +53 -0
  51. package/dist/internal/volumeLimits.d.ts.map +1 -0
  52. package/dist/internal/volumeLimits.js +63 -0
  53. package/dist/internal/volumeState.d.ts +2 -0
  54. package/dist/internal/volumeState.d.ts.map +1 -1
  55. package/dist/internal/volumeState.js +9 -0
  56. package/package.json +5 -1
@@ -0,0 +1,277 @@
1
+ import * as ByteSize from "effect/ByteSize";
2
+ import * as Effect from "effect/Effect";
3
+ import * as Hex from "effect/encoding/Hex";
4
+ import * as Predicate from "effect/Predicate";
5
+ import * as Result from "effect/Result";
6
+ import { isEntry, isTarget } from "../Target.js";
7
+ import { DOT_DOT_HEX, DOT_HEX, isAttachedBytes, isDotComponent, isWellFormed, MAX_NAME_BYTES, MAX_SYMLINK_TRAVERSALS, nameBytes, ownedPath, preparePath, SLASH_BYTE } from "./path.js";
8
+ import * as TokenRegistry from "./tokenRegistry.js";
9
+ import { ROOT_INO } from "./volumeState.js";
10
+ const encoder = new TextEncoder();
11
+ /** @internal */
12
+ export const inGroup = (identity, gid) => identity.gid === gid || identity.groups.includes(gid);
13
+ /** @internal */
14
+ export const permitted = (node, identity) => {
15
+ const shift = identity.uid === node.metadata.uid ? 6 : inGroup(identity, node.metadata.gid) ? 3 : 0;
16
+ return (node.metadata.mode >> shift) & 0o7;
17
+ };
18
+ /** @internal */
19
+ export const authorize = (node, identity, bits, op) => identity.privileged || (permitted(node, identity) & bits) === bits
20
+ ? Effect.void
21
+ : Effect.fail(op.fail("AccessDenied"));
22
+ /** @internal */
23
+ export const entryName = (input, op) => {
24
+ if (Predicate.isString(input) && !isWellFormed(input))
25
+ return Result.fail(op.fail("InvalidPathEncoding"));
26
+ const bytes = Predicate.isString(input) ? encoder.encode(input) : input;
27
+ if (!isAttachedBytes(bytes) || bytes.length === 0 || bytes.length > MAX_NAME_BYTES || bytes.includes(0) ||
28
+ bytes.includes(SLASH_BYTE))
29
+ return Result.fail(op.fail("InvalidArgument"));
30
+ return Result.succeed(Hex.encode(new Uint8Array(bytes)));
31
+ };
32
+ /** @internal */
33
+ export const make = ({ caller, get, identity, maxPathBytes, registry }) => {
34
+ const prepare = (input, op) => preparePath(input, op.operation, maxPathBytes);
35
+ const prepareEntry = (input, op) => isEntry(input)
36
+ ? Result.map(entryName(input.name, op), (name) => ({ kind: "entry", directory: input.directory, name }))
37
+ : isTarget(input)
38
+ ? Result.map(prepare(input.path, op), (path) => ({ kind: "path", path, base: input.relativeTo }))
39
+ : Result.map(prepare(input, op), (path) => ({ kind: "path", path, base: undefined }));
40
+ const nodeAt = Effect.fnUntraced(function* (ino) {
41
+ const node = yield* get(ino);
42
+ return node === undefined ? yield* Effect.die("Inode left the table during resolution") : node;
43
+ });
44
+ const directoryAt = Effect.fnUntraced(function* (ino) {
45
+ const node = yield* nodeAt(ino);
46
+ return node.kind !== "directory" ? yield* Effect.die("Directory left the table during resolution") : node;
47
+ });
48
+ const callerDirectory = Effect.fnUntraced(function* (op) {
49
+ const ino = TokenRegistry.inode(caller);
50
+ const node = ino === undefined ? undefined : yield* get(ino);
51
+ return node?.kind !== "directory" ? yield* op.fail("ClosedCaller") : node;
52
+ });
53
+ const walk = Effect.fnUntraced(function* (path, base, op, mode, referencedBase) {
54
+ const pathOp = op.at(path.input);
55
+ const parentOnly = mode.kind === "Parent";
56
+ const allowMissing = mode.kind === "OrCreate" && mode.create !== "never";
57
+ const followFinalSymlink = mode.kind === "Node"
58
+ ? mode.followFinalSymlink !== false
59
+ : mode.kind === "Parent" || mode.finalSymlink === "follow";
60
+ const createMissing = mode.kind === "Node" ? mode.createMissing : undefined;
61
+ if (!TokenRegistry.isOpen(caller)) {
62
+ return yield* pathOp.fail("ClosedCaller");
63
+ }
64
+ let current = path.absolute ? (yield* nodeAt(ROOT_INO)) : yield* callerDirectory(pathOp);
65
+ if (!path.absolute && referencedBase !== undefined) {
66
+ current = referencedBase;
67
+ yield* authorize(current, identity, 0o1, op);
68
+ }
69
+ else if (!path.absolute && base !== undefined) {
70
+ const directory = yield* TokenRegistry.nodeOrFail(yield* registry.resolve(base, "directory"), "handle", pathOp);
71
+ if (directory.kind !== "directory")
72
+ return yield* pathOp.fail("InvalidHandle");
73
+ current = directory;
74
+ yield* authorize(current, identity, 0o1, pathOp);
75
+ }
76
+ if (current.metadata.nlink === 0) {
77
+ return yield* pathOp.fail("NotFound");
78
+ }
79
+ let work = path;
80
+ let parent;
81
+ let name;
82
+ let traversals = 0;
83
+ // Leading components that came from a symbolic link's target. Only the components the caller wrote are
84
+ // created, so a dangling link stays missing, as it does for mkdir -p.
85
+ let linked = 0;
86
+ for (let index = 0; index < work.components.length - (parentOnly ? 1 : 0); index++) {
87
+ if (current.kind !== "directory") {
88
+ return yield* pathOp.fail("NotDirectory");
89
+ }
90
+ yield* authorize(current, identity, 0o1, pathOp);
91
+ const component = work.components[index];
92
+ if (component === undefined)
93
+ break;
94
+ if (component === DOT_HEX)
95
+ continue;
96
+ if (component === DOT_DOT_HEX) {
97
+ current = yield* directoryAt(current.parent);
98
+ parent = undefined;
99
+ name = undefined;
100
+ continue;
101
+ }
102
+ parent = current;
103
+ name = component;
104
+ const childIno = current.entries.get(component);
105
+ const child = childIno === undefined ? undefined : (yield* get(childIno));
106
+ if (child === undefined) {
107
+ if (allowMissing && index === work.components.length - 1) {
108
+ return { kind: "missing", parent, name, op: pathOp };
109
+ }
110
+ if (createMissing !== undefined && index >= linked) {
111
+ current = yield* createMissing(current, component, index === work.components.length - 1);
112
+ continue;
113
+ }
114
+ return yield* pathOp.fail("NotFound");
115
+ }
116
+ if (child.kind === "symlink" && (followFinalSymlink || index < work.components.length - 1 || work.trailingSlash)) {
117
+ if (child.target.length === 0) {
118
+ return yield* pathOp.fail("NotFound");
119
+ }
120
+ if (++traversals > MAX_SYMLINK_TRAVERSALS) {
121
+ return yield* pathOp.fail("SymlinkLoop");
122
+ }
123
+ const suffix = work.suffixes[index] ?? new Uint8Array(0);
124
+ const remaining = work.components.length - index - 1;
125
+ if (maxPathBytes !== undefined &&
126
+ ByteSize.isGreaterThan(ByteSize.bytes(child.target.length + suffix.length), maxPathBytes)) {
127
+ return yield* pathOp.fail("PathTooLong");
128
+ }
129
+ const expansion = new Uint8Array(child.target.length + suffix.length);
130
+ expansion.set(child.target);
131
+ expansion.set(suffix, child.target.length);
132
+ const expanded = preparePath(ownedPath(expansion), op.operation, maxPathBytes);
133
+ // The expansion is synthetic: its per-component limits are the caller's to hear about,
134
+ // but the path in the error has to be the one the caller passed in, so the
135
+ // expansion's own failure is not kept as the cause.
136
+ if (Result.isFailure(expanded)) {
137
+ return yield* pathOp.fail(expanded.failure.code);
138
+ }
139
+ work = expanded.success;
140
+ linked = work.components.length - remaining + Math.max(0, linked - index - 1);
141
+ if (work.absolute)
142
+ current = yield* nodeAt(ROOT_INO);
143
+ index = -1;
144
+ }
145
+ else
146
+ current = child;
147
+ }
148
+ if (!parentOnly && work.trailingSlash && current.kind !== "directory") {
149
+ return yield* pathOp.fail("NotDirectory");
150
+ }
151
+ return parent === undefined || name === undefined
152
+ ? { kind: "node", node: current, op: pathOp }
153
+ : { kind: "entry", node: current, parent, name, op: pathOp };
154
+ });
155
+ const resolveNode = Effect.fnUntraced(function* (target, mode, op) {
156
+ if (Predicate.isTagged(target, "Path")) {
157
+ const path = yield* Effect.fromResult(prepare(target.path, op));
158
+ const result = yield* walk(path, target.relativeTo, op, {
159
+ ...mode,
160
+ followFinalSymlink: mode.final === true
161
+ ? mode.followFinalSymlink ?? true
162
+ : target.followFinalSymlink ?? mode.followFinalSymlink ?? true
163
+ });
164
+ return result.kind === "missing" ? yield* result.op.fail("NotFound") : result;
165
+ }
166
+ if (!TokenRegistry.isOpen(caller))
167
+ return yield* op.fail("ClosedCaller");
168
+ const node = yield* TokenRegistry.nodeOrFail(yield* (Predicate.isTagged(target, "Reference")
169
+ ? registry.resolve(target.reference, "reference")
170
+ : registry.resolve(target.handle, "handle")), Predicate.isTagged(target, "Reference") ? "reference" : "handle", op);
171
+ return { kind: "node", node, op };
172
+ });
173
+ const directory = Effect.fnUntraced(function* (target, op) {
174
+ const resolved = yield* resolveNode(target, { kind: "Node" }, op);
175
+ if (resolved.node.kind !== "directory")
176
+ return yield* resolved.op.fail("NotDirectory");
177
+ return { node: resolved.node, op: resolved.op };
178
+ });
179
+ const entryDirectory = Effect.fnUntraced(function* (target, op) {
180
+ const resolved = yield* directory(target, op);
181
+ if (resolved.node.metadata.nlink === 0)
182
+ return yield* resolved.op.fail("NotFound");
183
+ return resolved;
184
+ });
185
+ const parent = Effect.fnUntraced(function* (prepared, op) {
186
+ if (prepared.kind === "entry") {
187
+ const resolved = yield* entryDirectory(prepared.directory, op);
188
+ return {
189
+ kind: "parent",
190
+ parent: resolved.node.ino,
191
+ name: prepared.name,
192
+ trailingSlash: false,
193
+ addressing: "entry",
194
+ op
195
+ };
196
+ }
197
+ const resolved = yield* walk(prepared.path, prepared.base, op, { kind: "Parent" });
198
+ if (resolved.kind === "missing")
199
+ return yield* resolved.op.fail("NotFound");
200
+ if (resolved.node.kind !== "directory")
201
+ return yield* resolved.op.fail("NotDirectory");
202
+ return {
203
+ kind: "parent",
204
+ parent: resolved.node.ino,
205
+ name: prepared.path.components.at(-1),
206
+ trailingSlash: prepared.path.trailingSlash,
207
+ addressing: "path",
208
+ op: resolved.op
209
+ };
210
+ });
211
+ const creationResult = (result, trailingSlash, addressing, op, origin) => {
212
+ if (result.kind === "node")
213
+ return Effect.fail(op.fail("IsDirectory"));
214
+ const entry = {
215
+ parent: result.parent,
216
+ name: result.name,
217
+ trailingSlash,
218
+ addressing,
219
+ op,
220
+ origin: origin ?? { parent: result.parent, name: result.name }
221
+ };
222
+ return Effect.succeed(result.kind === "missing" ? { ...entry, kind: "missing" } : { ...entry, kind: "existing", node: result.node });
223
+ };
224
+ const orCreate = Effect.fnUntraced(function* (prepared, mode, op) {
225
+ if (prepared.kind === "path") {
226
+ const pathOp = op.at(prepared.path.input);
227
+ if (mode.create === "exclusive") {
228
+ const exists = yield* Effect.result(walk(prepared.path, prepared.base, op, { kind: "Node", followFinalSymlink: false }));
229
+ if (Result.isSuccess(exists))
230
+ return yield* pathOp.fail("AlreadyExists");
231
+ if (exists.failure.code !== "NotFound")
232
+ return yield* exists.failure;
233
+ }
234
+ return yield* creationResult(yield* walk(prepared.path, prepared.base, op, mode), prepared.path.trailingSlash, "path", pathOp);
235
+ }
236
+ const { node: parent } = yield* entryDirectory(prepared.directory, op);
237
+ if (isDotComponent(prepared.name))
238
+ return yield* op.fail("InvalidArgument");
239
+ yield* authorize(parent, identity, 0o1, op);
240
+ const directIno = parent.entries.get(prepared.name);
241
+ const direct = directIno === undefined ? undefined : yield* get(directIno);
242
+ if (mode.beforeFollow !== undefined)
243
+ yield* mode.beforeFollow(parent, direct);
244
+ if (mode.create === "exclusive" && mode.entryExclusive !== "afterSymlink" && direct !== undefined) {
245
+ return yield* op.fail("AlreadyExists");
246
+ }
247
+ let result = direct === undefined
248
+ ? { kind: "missing", parent, name: prepared.name, op }
249
+ : { kind: "entry", node: direct, parent, name: prepared.name, op };
250
+ if (direct?.kind === "symlink" && mode.finalSymlink === "follow") {
251
+ const path = yield* Effect.fromResult(preparePath(ownedPath(nameBytes(prepared.name)), op.operation, undefined));
252
+ result = yield* walk(path, undefined, op, mode, parent);
253
+ // Entry writeFile rejects a followed directory before checking exclusive creation.
254
+ if (mode.create === "exclusive" && mode.entryExclusive === "afterSymlink" &&
255
+ (result.kind === "node" || (result.kind === "entry" && result.node.kind === "directory"))) {
256
+ return yield* op.fail("IsDirectory");
257
+ }
258
+ }
259
+ if (result.kind === "missing" && mode.create === "never")
260
+ return yield* op.fail("NotFound");
261
+ if (mode.create === "exclusive" && mode.entryExclusive === "afterSymlink" && result.kind !== "missing") {
262
+ return yield* op.fail("AlreadyExists");
263
+ }
264
+ return yield* creationResult(result, false, "entry", op, { parent, name: prepared.name });
265
+ });
266
+ function resolve(input, mode, op) {
267
+ if (isTarget(input)) {
268
+ return mode.kind === "Node" ? resolveNode(input, mode, op) : Effect.die("A node target requires Node resolution");
269
+ }
270
+ return mode.kind === "Parent"
271
+ ? parent(input, op)
272
+ : mode.kind === "OrCreate"
273
+ ? orCreate(input, mode, op)
274
+ : Effect.die("An entry requires Parent or OrCreate resolution");
275
+ }
276
+ return { prepare, prepareEntry, resolve, directory, entryDirectory };
277
+ };
@@ -0,0 +1,24 @@
1
+ import * as Effect from "effect/Effect";
2
+ import type { Snapshot } from "../Snapshot.js";
3
+ import type { FsFailure, ImageFailure } from "../VfsError.js";
4
+ import type { PathInput } from "../VirtualFileSystem.js";
5
+ import { BudgetExceeded, type Kind, type Meter } from "./searchModel.js";
6
+ import { type Node } from "./volumeState.js";
7
+ /** @internal */
8
+ export interface Entry {
9
+ readonly path: string;
10
+ readonly pathBytes: number;
11
+ readonly parts: ReadonlyArray<string>;
12
+ readonly kind: Kind;
13
+ readonly node: Node;
14
+ }
15
+ /** @internal */
16
+ export interface Visitor {
17
+ readonly next: (select: (entry: Entry) => Effect.Effect<{
18
+ readonly selected: boolean;
19
+ readonly prune: boolean;
20
+ }, BudgetExceeded>) => Effect.Effect<Entry | undefined, BudgetExceeded>;
21
+ }
22
+ /** @internal */
23
+ export declare const makeVisitor: (snapshot: Snapshot, root: PathInput, meter: Meter) => Effect.Effect<Visitor, FsFailure | ImageFailure, never>;
24
+ //# sourceMappingURL=search.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../../src/internal/search.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AAGvC,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AAC9C,OAAO,KAAK,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAA;AAC7D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAA;AAIxD,OAAO,EAAE,cAAc,EAAE,KAAK,IAAI,EAAE,KAAK,KAAK,EAAE,MAAM,kBAAkB,CAAA;AACxE,OAAO,EAAkD,KAAK,IAAI,EAAE,MAAM,kBAAkB,CAAA;AAE5F,gBAAgB;AAChB,MAAM,WAAW,KAAK;IACpB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,QAAQ,CAAC,KAAK,EAAE,aAAa,CAAC,MAAM,CAAC,CAAA;IACrC,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAA;IACnB,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAA;CACpB;AAED,gBAAgB;AAChB,MAAM,WAAW,OAAO;IACtB,QAAQ,CAAC,IAAI,EAAE,CACb,MAAM,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,MAAM,CAAC,MAAM,CAAC;QAAE,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;QAAC,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAA;KAAE,EAAE,cAAc,CAAC,KAC7G,MAAM,CAAC,MAAM,CAAC,KAAK,GAAG,SAAS,EAAE,cAAc,CAAC,CAAA;CACtD;AAeD,gBAAgB;AAChB,eAAO,MAAM,WAAW,gHAyFtB,CAAA"}
@@ -0,0 +1,78 @@
1
+ import * as Effect from "effect/Effect";
2
+ import * as Option from "effect/Option";
3
+ import { decodeOption } from "../BytePath.js";
4
+ import { fsFailure } from "./errors.js";
5
+ import * as Image from "./image.js";
6
+ import { nameBytes } from "./path.js";
7
+ import { BudgetExceeded } from "./searchModel.js";
8
+ import { byEntryName, getNode } from "./volumeState.js";
9
+ /** @internal */
10
+ export const makeVisitor = Effect.fnUntraced(function* (snapshot, root, meter) {
11
+ const value = yield* Image.valueOf(snapshot);
12
+ const node = yield* Effect.fromResult(Image.resolve(value, root, { operation: "search", followFinalSymlink: true }));
13
+ if (node.kind !== "directory")
14
+ return yield* fsFailure("NotDirectory", "search", { path: root });
15
+ const frames = [];
16
+ let deferred = { node, path: "", pathBytes: 0, parts: [], depth: 0 };
17
+ let pending = 0;
18
+ const prepare = Effect.fnUntraced(function* (directory) {
19
+ const width = directory.node.entries.size;
20
+ // Reserve prepared siblings too, before allocating this directory's sorted list.
21
+ if (width > meter.limits.maxEntries - meter.work.entries - pending) {
22
+ return yield* new BudgetExceeded({ limit: "maxEntries", path: directory.path });
23
+ }
24
+ if (width === 0)
25
+ return;
26
+ const children = [...directory.node.entries].sort(byEntryName);
27
+ frames.push({ ...directory, children, index: 0 });
28
+ pending += width;
29
+ });
30
+ const next = Effect.fnUntraced(function* (select) {
31
+ while (true) {
32
+ if (deferred !== undefined) {
33
+ const directory = deferred;
34
+ deferred = undefined;
35
+ yield* prepare(directory);
36
+ }
37
+ let frame = frames[frames.length - 1];
38
+ while (frame !== undefined && frame.index === frame.children.length) {
39
+ frames.pop();
40
+ frame = frames[frames.length - 1];
41
+ }
42
+ if (frame === undefined)
43
+ return undefined;
44
+ const [hex, ino] = frame.children[frame.index++];
45
+ pending--;
46
+ meter.work.entries++;
47
+ if (meter.work.entries % 128 === 0)
48
+ yield* Effect.yieldNow;
49
+ const depth = frame.depth + 1;
50
+ if (depth > meter.limits.maxDepth) {
51
+ return yield* new BudgetExceeded({ limit: "maxDepth", path: frame.path });
52
+ }
53
+ const pathBytes = frame.pathBytes + (frame.depth === 0 ? 0 : 1) + hex.length / 2;
54
+ if (BigInt(pathBytes) > meter.limits.maxPathBytes) {
55
+ return yield* new BudgetExceeded({ limit: "maxPathBytes", path: frame.path });
56
+ }
57
+ const child = getNode(value, ino);
58
+ const name = decodeOption(nameBytes(hex));
59
+ if (Option.isNone(name)) {
60
+ meter.skips.invalidNames++;
61
+ if (child.kind === "directory")
62
+ meter.skips.invalidNameSubtrees++;
63
+ continue;
64
+ }
65
+ const path = frame.depth === 0 ? name.value : `${frame.path}/${name.value}`;
66
+ const parts = [...frame.parts, name.value];
67
+ const entry = { path, pathBytes, parts, kind: child.kind, node: child };
68
+ const selection = yield* select(entry);
69
+ // Delay children until the following pull, so a row cap needs no lookahead work.
70
+ if (child.kind === "directory" && !selection.prune) {
71
+ deferred = { node: child, path, pathBytes, parts, depth };
72
+ }
73
+ if (selection.selected)
74
+ return entry;
75
+ }
76
+ });
77
+ return { next };
78
+ });
@@ -0,0 +1,77 @@
1
+ import * as ByteSize from "effect/ByteSize";
2
+ import * as Effect from "effect/Effect";
3
+ import { BudgetExceeded, type CountResult, type LineResult, QueryFailure } from "../Search.js";
4
+ import type { Snapshot } from "../Snapshot.js";
5
+ /** @internal */
6
+ export interface ContentRows {
7
+ readonly lines: LineResult;
8
+ readonly files: string;
9
+ readonly countLines: CountResult;
10
+ }
11
+ /** @internal */
12
+ export type Mode = keyof ContentRows;
13
+ /** @internal */
14
+ export declare const evaluate: <M extends Mode>(snapshot: Snapshot, input: {
15
+ readonly root: string | import("../BytePath.js").BytePath;
16
+ readonly include: readonly [string, ...string[]];
17
+ readonly exclude?: readonly string[];
18
+ readonly pattern: {
19
+ readonly _tag: "Literal";
20
+ readonly pattern: string;
21
+ } | {
22
+ readonly _tag: "Regex";
23
+ readonly pattern: string;
24
+ };
25
+ readonly ignoreCase?: boolean;
26
+ readonly limits?: {
27
+ readonly maxEntries?: number;
28
+ readonly maxDepth?: number;
29
+ readonly maxPathBytes?: ByteSize.ByteSize;
30
+ readonly maxResults?: number;
31
+ readonly maxResultBytes?: ByteSize.ByteSize;
32
+ readonly maxPatternBytes?: ByteSize.ByteSize;
33
+ readonly maxPatterns?: number;
34
+ readonly maxPatternListBytes?: ByteSize.ByteSize;
35
+ readonly maxExpansions?: number;
36
+ readonly maxTokens?: number;
37
+ readonly maxMatchingWork?: number;
38
+ readonly maxScannedBytes?: ByteSize.ByteSize;
39
+ readonly maxFileBytes?: ByteSize.ByteSize;
40
+ readonly maxLineEvaluations?: number;
41
+ readonly maxExcerptBytes?: ByteSize.ByteSize;
42
+ };
43
+ }, mode: M) => Effect.Effect<{
44
+ query: {
45
+ readonly root: string | import("../BytePath.js").BytePath;
46
+ readonly include: readonly [string, ...string[]];
47
+ readonly exclude?: readonly string[];
48
+ readonly pattern: {
49
+ readonly _tag: "Literal";
50
+ readonly pattern: string;
51
+ } | {
52
+ readonly _tag: "Regex";
53
+ readonly pattern: string;
54
+ };
55
+ readonly ignoreCase?: boolean;
56
+ readonly limits?: {
57
+ readonly maxEntries?: number;
58
+ readonly maxDepth?: number;
59
+ readonly maxPathBytes?: ByteSize.ByteSize;
60
+ readonly maxResults?: number;
61
+ readonly maxResultBytes?: ByteSize.ByteSize;
62
+ readonly maxPatternBytes?: ByteSize.ByteSize;
63
+ readonly maxPatterns?: number;
64
+ readonly maxPatternListBytes?: ByteSize.ByteSize;
65
+ readonly maxExpansions?: number;
66
+ readonly maxTokens?: number;
67
+ readonly maxMatchingWork?: number;
68
+ readonly maxScannedBytes?: ByteSize.ByteSize;
69
+ readonly maxFileBytes?: ByteSize.ByteSize;
70
+ readonly maxLineEvaluations?: number;
71
+ readonly maxExcerptBytes?: ByteSize.ByteSize;
72
+ };
73
+ };
74
+ meter: import("./searchModel.js").Meter;
75
+ next: () => Effect.Effect<ContentRows[M] | undefined, BudgetExceeded, never>;
76
+ }, QueryFailure | import("../VfsError.js").FsFailure | import("../VfsError.js").ImageFailure, never>;
77
+ //# sourceMappingURL=searchContent.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"searchContent.d.ts","sourceRoot":"","sources":["../../src/internal/searchContent.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,QAAQ,MAAM,iBAAiB,CAAA;AAC3C,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AAGvC,OAAO,EACL,cAAc,EAEd,KAAK,WAAW,EAEhB,KAAK,UAAU,EAEf,YAAY,EACb,MAAM,cAAc,CAAA;AACrB,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AAK9C,gBAAgB;AAChB,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAA;IAC1B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,UAAU,EAAE,WAAW,CAAA;CACjC;AAED,gBAAgB;AAChB,MAAM,MAAM,IAAI,GAAG,MAAM,WAAW,CAAA;AAkFpC,gBAAgB;AAChB,eAAO,MAAM,QAAQ,GAA+B,CAAC,SAAS,IAAI;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;oGAsMhE,CAAA"}