@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.
Files changed (79) hide show
  1. package/README.md +21 -235
  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 +89 -12
  8. package/dist/Search.d.ts +730 -0
  9. package/dist/Search.d.ts.map +1 -0
  10. package/dist/Search.js +576 -0
  11. package/dist/SnapshotDelta.d.ts +234 -0
  12. package/dist/SnapshotDelta.d.ts.map +1 -1
  13. package/dist/SnapshotDelta.js +107 -0
  14. package/dist/VfsError.d.ts +3 -1
  15. package/dist/VfsError.d.ts.map +1 -1
  16. package/dist/VfsError.js +3 -1
  17. package/dist/VirtualFileSystem.d.ts +78 -2
  18. package/dist/VirtualFileSystem.d.ts.map +1 -1
  19. package/dist/VirtualFileSystem.js +68 -4
  20. package/dist/Watch.d.ts +18 -0
  21. package/dist/Watch.d.ts.map +1 -1
  22. package/dist/Watch.js +12 -0
  23. package/dist/index.d.ts +7 -0
  24. package/dist/index.d.ts.map +1 -1
  25. package/dist/index.js +7 -0
  26. package/dist/internal/callerWatch.d.ts +32 -0
  27. package/dist/internal/callerWatch.d.ts.map +1 -0
  28. package/dist/internal/callerWatch.js +184 -0
  29. package/dist/internal/confinement.d.ts +16 -0
  30. package/dist/internal/confinement.d.ts.map +1 -0
  31. package/dist/internal/confinement.js +79 -0
  32. package/dist/internal/directoryEntries.d.ts +6 -0
  33. package/dist/internal/directoryEntries.d.ts.map +1 -0
  34. package/dist/internal/directoryEntries.js +79 -0
  35. package/dist/internal/fixture.js +4 -4
  36. package/dist/internal/glob.d.ts +66 -0
  37. package/dist/internal/glob.d.ts.map +1 -0
  38. package/dist/internal/glob.js +248 -0
  39. package/dist/internal/image.d.ts +7 -1
  40. package/dist/internal/image.d.ts.map +1 -1
  41. package/dist/internal/image.js +9 -7
  42. package/dist/internal/liveImage.d.ts +1 -1
  43. package/dist/internal/liveImage.d.ts.map +1 -1
  44. package/dist/internal/liveImage.js +2 -10
  45. package/dist/internal/path.js +2 -2
  46. package/dist/internal/resolution.d.ts +139 -0
  47. package/dist/internal/resolution.d.ts.map +1 -0
  48. package/dist/internal/resolution.js +332 -0
  49. package/dist/internal/search.d.ts +24 -0
  50. package/dist/internal/search.d.ts.map +1 -0
  51. package/dist/internal/search.js +78 -0
  52. package/dist/internal/searchContent.d.ts +77 -0
  53. package/dist/internal/searchContent.d.ts.map +1 -0
  54. package/dist/internal/searchContent.js +223 -0
  55. package/dist/internal/searchModel.d.ts +83 -0
  56. package/dist/internal/searchModel.d.ts.map +1 -0
  57. package/dist/internal/searchModel.js +23 -0
  58. package/dist/internal/snapshotDelta.d.ts +470 -1
  59. package/dist/internal/snapshotDelta.d.ts.map +1 -1
  60. package/dist/internal/snapshotDelta.js +59 -25
  61. package/dist/internal/snapshotMerge.d.ts +54 -0
  62. package/dist/internal/snapshotMerge.d.ts.map +1 -0
  63. package/dist/internal/snapshotMerge.js +959 -0
  64. package/dist/internal/tokenRegistry.d.ts +83 -0
  65. package/dist/internal/tokenRegistry.d.ts.map +1 -0
  66. package/dist/internal/tokenRegistry.js +146 -0
  67. package/dist/internal/virtualFileSystem.d.ts +32 -17
  68. package/dist/internal/virtualFileSystem.d.ts.map +1 -1
  69. package/dist/internal/virtualFileSystem.js +503 -736
  70. package/dist/internal/volumeLimits.d.ts +53 -0
  71. package/dist/internal/volumeLimits.d.ts.map +1 -0
  72. package/dist/internal/volumeLimits.js +63 -0
  73. package/dist/internal/volumeState.d.ts +2 -0
  74. package/dist/internal/volumeState.d.ts.map +1 -1
  75. package/dist/internal/volumeState.js +9 -0
  76. package/dist/internal/watchHub.d.ts +6 -2
  77. package/dist/internal/watchHub.d.ts.map +1 -1
  78. package/dist/internal/watchHub.js +31 -6
  79. package/package.json +9 -3
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Search.d.ts","sourceRoot":"","sources":["../src/Search.ts"],"names":[],"mappings":"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"}
package/dist/Search.js ADDED
@@ -0,0 +1,576 @@
1
+ /**
2
+ * Bounded filename and content search over an explicit immutable snapshot.
3
+ *
4
+ * Search grants the snapshot's authority, without caller permission filtering.
5
+ * Paths are exact UTF-8 strings relative to the selected directory root. Hidden
6
+ * names are included, discovered symlinks are not followed, and hard-link names
7
+ * are searched independently. Glob never reads or copies file contents.
8
+ *
9
+ * @since 0.8.0
10
+ */
11
+ import * as ByteSize from "effect/ByteSize";
12
+ import * as Effect from "effect/Effect";
13
+ import * as Schema from "effect/Schema";
14
+ import * as Stream from "effect/Stream";
15
+ import { BytePath } from "./BytePath.js";
16
+ import * as Glob from "./internal/glob.js";
17
+ import * as Visitor from "./internal/search.js";
18
+ import * as Content from "./internal/searchContent.js";
19
+ import * as Internal from "./internal/searchModel.js";
20
+ /**
21
+ * Entry kinds selected by filename queries.
22
+ *
23
+ * @category schemas
24
+ * @since 0.8.0
25
+ */
26
+ export const Kind = Schema.Literals(["file", "directory", "symlink"]);
27
+ const limitFields = {
28
+ maxEntries: Schema.Natural,
29
+ maxDepth: Schema.Natural,
30
+ maxPathBytes: Schema.ByteSize,
31
+ maxResults: Schema.Natural,
32
+ maxResultBytes: Schema.ByteSize,
33
+ maxPatternBytes: Schema.ByteSize,
34
+ maxPatterns: Schema.Natural,
35
+ maxPatternListBytes: Schema.ByteSize,
36
+ maxExpansions: Schema.Natural,
37
+ maxTokens: Schema.Natural,
38
+ maxMatchingWork: Schema.Natural,
39
+ maxScannedBytes: Schema.ByteSize,
40
+ maxFileBytes: Schema.ByteSize,
41
+ maxLineEvaluations: Schema.Natural,
42
+ maxExcerptBytes: Schema.ByteSize
43
+ };
44
+ /**
45
+ * Complete finite namespace, matching, content and output limits.
46
+ *
47
+ * @category schemas
48
+ * @since 0.8.0
49
+ */
50
+ export const Limits = Schema.Struct(limitFields);
51
+ /**
52
+ * Optional overrides applied to default limits.
53
+ *
54
+ * @category schemas
55
+ * @since 0.8.0
56
+ */
57
+ export const LimitOverrides = Schema.Struct({
58
+ maxEntries: Schema.optionalKey(limitFields.maxEntries),
59
+ maxDepth: Schema.optionalKey(limitFields.maxDepth),
60
+ maxPathBytes: Schema.optionalKey(limitFields.maxPathBytes),
61
+ maxResults: Schema.optionalKey(limitFields.maxResults),
62
+ maxResultBytes: Schema.optionalKey(limitFields.maxResultBytes),
63
+ maxPatternBytes: Schema.optionalKey(limitFields.maxPatternBytes),
64
+ maxPatterns: Schema.optionalKey(limitFields.maxPatterns),
65
+ maxPatternListBytes: Schema.optionalKey(limitFields.maxPatternListBytes),
66
+ maxExpansions: Schema.optionalKey(limitFields.maxExpansions),
67
+ maxTokens: Schema.optionalKey(limitFields.maxTokens),
68
+ maxMatchingWork: Schema.optionalKey(limitFields.maxMatchingWork),
69
+ maxScannedBytes: Schema.optionalKey(limitFields.maxScannedBytes),
70
+ maxFileBytes: Schema.optionalKey(limitFields.maxFileBytes),
71
+ maxLineEvaluations: Schema.optionalKey(limitFields.maxLineEvaluations),
72
+ maxExcerptBytes: Schema.optionalKey(limitFields.maxExcerptBytes)
73
+ });
74
+ /**
75
+ * Names of compiler, traversal, output and content bounds.
76
+ *
77
+ * @category schemas
78
+ * @since 0.8.0
79
+ */
80
+ export const Limit = Schema.Literals([
81
+ "maxEntries",
82
+ "maxDepth",
83
+ "maxPathBytes",
84
+ "maxResults",
85
+ "maxResultBytes",
86
+ "maxPatternBytes",
87
+ "maxPatterns",
88
+ "maxPatternListBytes",
89
+ "maxExpansions",
90
+ "maxTokens",
91
+ "maxMatchingWork",
92
+ "maxScannedBytes",
93
+ "maxFileBytes",
94
+ "maxLineEvaluations",
95
+ "maxExcerptBytes"
96
+ ]);
97
+ /**
98
+ * Finite default work policy. Byte limits use exact `ByteSize` values.
99
+ *
100
+ * Defaults are 100,000 entries, depth 256, 4 KiB paths, 100 rows, 64 KiB
101
+ * result payload, 4 KiB per pattern, 128 pattern sources, 64 KiB aggregate
102
+ * sources, 1,024 expanded alternatives, 65,536 compiled tokens, and 10 million
103
+ * matching operations. Content defaults are 64 MiB scanned bytes, 1 MiB per
104
+ * file, 100,000 line evaluations and 1 KiB excerpts. Glob performs no content work. These bounds do not promise a deadline or
105
+ * measure JavaScript heap or serialized JSON size.
106
+ *
107
+ * @category defaults
108
+ * @since 0.8.0
109
+ */
110
+ export const defaultLimits = Object.freeze({
111
+ maxEntries: 100_000,
112
+ maxDepth: 256,
113
+ maxPathBytes: ByteSize.bytes(4_096),
114
+ maxResults: 100,
115
+ maxResultBytes: ByteSize.bytes(65_536),
116
+ maxPatternBytes: ByteSize.bytes(4_096),
117
+ maxPatterns: 128,
118
+ maxPatternListBytes: ByteSize.bytes(65_536),
119
+ maxExpansions: 1_024,
120
+ maxTokens: 65_536,
121
+ maxMatchingWork: 10_000_000,
122
+ maxScannedBytes: ByteSize.bytes(67_108_864),
123
+ maxFileBytes: ByteSize.bytes(1_048_576),
124
+ maxLineEvaluations: 100_000,
125
+ maxExcerptBytes: ByteSize.bytes(1_024)
126
+ });
127
+ /**
128
+ * An explicit directory root, nonempty include list and optional exclusions,
129
+ * kinds and limits. All patterns are root-relative and case-sensitive.
130
+ *
131
+ * Wildcards and classes match Unicode code points without normalization.
132
+ * Supports `*`, `?`, classes, comma brace alternatives, whole-segment `**`,
133
+ * backslash escapes, and a directory-only trailing slash. Excluded directories
134
+ * prune traversal. Include selectors and kind filters do not prune directories.
135
+ *
136
+ * @category schemas
137
+ * @since 0.8.0
138
+ */
139
+ export const GlobQuery = Schema.Struct({
140
+ root: Schema.Union([Schema.String, BytePath]),
141
+ include: Schema.NonEmptyArray(Schema.String),
142
+ exclude: Schema.optionalKey(Schema.Array(Schema.String)),
143
+ kinds: Schema.optionalKey(Schema.Array(Kind)),
144
+ limits: Schema.optionalKey(LimitOverrides)
145
+ });
146
+ /**
147
+ * Work counts, with exact raw UTF-8 result bytes.
148
+ *
149
+ * @category schemas
150
+ * @since 0.8.0
151
+ */
152
+ export const Work = Schema.Struct({
153
+ entries: Schema.Natural,
154
+ matchingWork: Schema.Natural,
155
+ resultBytes: Schema.ByteSize,
156
+ scannedBytes: Schema.ByteSize,
157
+ lineEvaluations: Schema.Natural
158
+ });
159
+ /**
160
+ * Counted policy skips. Descendants of skipped directories are not estimated.
161
+ *
162
+ * @category schemas
163
+ * @since 0.8.0
164
+ */
165
+ export const Skips = Schema.Struct({
166
+ invalidNames: Schema.Natural,
167
+ invalidNameSubtrees: Schema.Natural,
168
+ oversizedFiles: Schema.Natural,
169
+ invalidUtf8Files: Schema.Natural,
170
+ binaryFiles: Schema.Natural
171
+ });
172
+ /**
173
+ * `Complete` exhausts the eligible namespace, possibly with skips. `Stopped`
174
+ * names the exhausted bound and a root-relative path when representable.
175
+ * The empty string identifies the selected root. Stopping does not assert
176
+ * that another matching entry exists.
177
+ *
178
+ * @category schemas
179
+ * @since 0.8.0
180
+ */
181
+ export const Completion = Schema.Union([
182
+ Schema.TaggedStruct("Complete", {}),
183
+ Schema.TaggedStruct("Stopped", { limit: Limit, path: Schema.optionalKey(Schema.String) })
184
+ ]);
185
+ /**
186
+ * A selected root, preceding results, counters and completion.
187
+ *
188
+ * @category schemas
189
+ * @since 0.8.0
190
+ */
191
+ export const GlobReport = Schema.Struct({
192
+ root: GlobQuery.fields.root,
193
+ results: Schema.Array(Schema.String),
194
+ work: Work,
195
+ skips: Skips,
196
+ completion: Completion
197
+ });
198
+ /**
199
+ * Invalid query, pattern syntax or aggregate compilation limit.
200
+ *
201
+ * @category errors
202
+ * @since 0.8.0
203
+ */
204
+ export class QueryFailure extends Schema.TaggedError()("SearchQueryFailure", {
205
+ field: Schema.String,
206
+ message: Schema.String,
207
+ pattern: Schema.optionalKey(Schema.String),
208
+ limit: Schema.optionalKey(Limit)
209
+ }) {
210
+ }
211
+ /**
212
+ * Typed runtime budget exhaustion.
213
+ *
214
+ * @category errors
215
+ * @since 0.8.0
216
+ */
217
+ export class BudgetExceeded extends Schema.TaggedError()("SearchBudgetExceeded", {
218
+ limit: Limit,
219
+ path: Schema.optionalKey(Schema.String)
220
+ }) {
221
+ }
222
+ /**
223
+ * Explicit content interpretation. Literal strings never become regex implicitly.
224
+ *
225
+ * @category schemas
226
+ * @since 0.8.0
227
+ */
228
+ export const Pattern = Schema.TaggedUnion({
229
+ Literal: {
230
+ pattern: Schema.NonEmptyString.check(Schema.makeFilter((value) => !value.includes("\0") && !value.includes("\r") && !value.includes("\n") && !/[\ud800-\udfff]/u.test(value), {
231
+ expected: "literal line text without NUL, CR, LF or lone surrogates"
232
+ }))
233
+ },
234
+ Regex: {
235
+ pattern: Schema.String
236
+ }
237
+ });
238
+ /**
239
+ * Regular-file content selection using the same case-sensitive filename globs as
240
+ * `GlobQuery`. Content `ignoreCase` defaults to false and never affects filenames.
241
+ *
242
+ * @category schemas
243
+ * @since 0.8.0
244
+ */
245
+ export const ContentQuery = Schema.Struct({
246
+ root: GlobQuery.fields.root,
247
+ include: GlobQuery.fields.include,
248
+ exclude: GlobQuery.fields.exclude,
249
+ pattern: Pattern,
250
+ ignoreCase: Schema.optionalKey(Schema.Boolean),
251
+ limits: GlobQuery.fields.limits
252
+ });
253
+ /**
254
+ * Half-open, file-absolute UTF-8 byte offsets. These are safe integers, not UTF-16
255
+ * indices or character columns.
256
+ *
257
+ * @category schemas
258
+ * @since 0.8.0
259
+ */
260
+ export const MatchRange = Schema.Struct({ start: Schema.Natural, end: Schema.Natural });
261
+ /**
262
+ * UTF-8-bounded text beginning at the match, with its original file-byte start.
263
+ * `truncated` is true when any line prefix or suffix is omitted. The full match
264
+ * range can extend beyond this text. A zero-width end match can have empty text.
265
+ *
266
+ * @category schemas
267
+ * @since 0.8.0
268
+ */
269
+ export const Excerpt = Schema.Struct({ text: Schema.String, start: Schema.Natural, truncated: Schema.Boolean });
270
+ /**
271
+ * One matching line, its first whole match and bounded excerpt. Line numbers are
272
+ * 1-based; paths are root-relative. LF separates lines, CR is stripped only before
273
+ * LF, BOM and lone CR are preserved, and a final LF adds no synthetic empty line.
274
+ *
275
+ * @category schemas
276
+ * @since 0.8.0
277
+ */
278
+ export const LineResult = Schema.Struct({
279
+ path: Schema.String,
280
+ lineNumber: Schema.Int.check(Schema.isGreaterThanOrEqualTo(1)),
281
+ range: MatchRange,
282
+ excerpt: Excerpt
283
+ });
284
+ /**
285
+ * Exact matching-line count for one completely scanned matching file. This counts
286
+ * lines, not occurrences. Incomplete files never produce a count row.
287
+ *
288
+ * @category schemas
289
+ * @since 0.8.0
290
+ */
291
+ export const CountResult = Schema.Struct({
292
+ path: Schema.String,
293
+ count: Schema.Int.check(Schema.isGreaterThanOrEqualTo(1))
294
+ });
295
+ /**
296
+ * Preceding line matches with coverage, work and skips.
297
+ * `Complete` covers eligible content and can still contain skips. Stopped or skipped
298
+ * query totals are lower bounds for the original scope.
299
+ *
300
+ * @category schemas
301
+ * @since 0.8.0
302
+ */
303
+ export const LinesReport = Schema.Struct({
304
+ root: ContentQuery.fields.root,
305
+ results: Schema.Array(LineResult),
306
+ work: Work,
307
+ skips: Skips,
308
+ completion: Completion
309
+ });
310
+ /**
311
+ * Preceding matching filenames with coverage, work and skips.
312
+ * `Complete` covers eligible content and can still contain skips. Stopped or skipped
313
+ * query totals are lower bounds for the original scope.
314
+ *
315
+ * @category schemas
316
+ * @since 0.8.0
317
+ */
318
+ export const FilesReport = Schema.Struct({
319
+ root: ContentQuery.fields.root,
320
+ results: Schema.Array(Schema.String),
321
+ work: Work,
322
+ skips: Skips,
323
+ completion: Completion
324
+ });
325
+ /**
326
+ * Preceding completed-file counts with coverage, work and skips.
327
+ * `Complete` covers eligible content and can still contain skips. Stopped or skipped
328
+ * query totals are lower bounds for the original scope.
329
+ *
330
+ * @category schemas
331
+ * @since 0.8.0
332
+ */
333
+ export const CountLinesReport = Schema.Struct({
334
+ root: ContentQuery.fields.root,
335
+ results: Schema.Array(CountResult),
336
+ work: Work,
337
+ skips: Skips,
338
+ completion: Completion
339
+ });
340
+ const evaluate = Effect.fnUntraced(function* (snapshot, input) {
341
+ const { query, limits } = yield* Internal.query(input);
342
+ const patterns = yield* Glob.compile(query, limits);
343
+ const meter = Internal.makeMeter(limits);
344
+ const visitor = yield* Visitor.makeVisitor(snapshot, query.root, meter);
345
+ let rows = 0;
346
+ let stopped = limits.maxResults === 0
347
+ ? new BudgetExceeded({ limit: "maxResults", path: "" })
348
+ : undefined;
349
+ const select = Effect.fnUntraced(function* (entry) {
350
+ const match = yield* Glob.matches(patterns, entry.parts, entry.kind, meter);
351
+ return {
352
+ selected: !match.excluded && match.included && (query.kinds === undefined || query.kinds.includes(entry.kind)),
353
+ prune: match.excluded
354
+ };
355
+ });
356
+ const next = Effect.fnUntraced(function* () {
357
+ if (stopped !== undefined)
358
+ return yield* stopped;
359
+ const entry = yield* visitor.next(select);
360
+ if (entry === undefined)
361
+ return undefined;
362
+ const size = BigInt(entry.pathBytes);
363
+ if (size > limits.maxResultBytes - meter.work.resultBytes) {
364
+ return yield* new BudgetExceeded({ limit: "maxResultBytes", path: entry.path });
365
+ }
366
+ meter.work.resultBytes = ByteSize.bytes(meter.work.resultBytes + size);
367
+ rows += 1;
368
+ if (rows === limits.maxResults)
369
+ stopped = new BudgetExceeded({ limit: "maxResults", path: entry.path });
370
+ return entry.path;
371
+ });
372
+ return { query, meter, next };
373
+ });
374
+ /**
375
+ * Stream ordinary root-relative strings in depth-first preorder, with each
376
+ * directory sorted by raw name bytes. Resolve the root through directory
377
+ * symlinks; never follow discovered symlinks or emit the root itself.
378
+ *
379
+ * A budget failure may follow earlier rows. Taking a prefix establishes no
380
+ * completion. Use `glob` when skip counts or coverage matter. Each evaluation
381
+ * owns fresh traversal and counters, including repeated or concurrent runs.
382
+ *
383
+ * @example
384
+ * ```ts
385
+ * import * as BunCrypto from "@effect/platform-bun/BunCrypto"
386
+ * import { Search, VirtualFileSystem as Vfs } from "@effect-vfs/core"
387
+ * import { Effect, Stream } from "effect"
388
+ *
389
+ * const program = Effect.gen(function*() {
390
+ * const volume = yield* Vfs.fromFixture({ entries: [
391
+ * { kind: "file", path: "/😀.ts", bytes: new Uint8Array() }
392
+ * ] })
393
+ * const snapshot = yield* volume.snapshot
394
+ * return yield* Stream.runCollect(Search.scanGlob(snapshot, { root: "/", include: ["?.ts"] }))
395
+ * })
396
+ * Effect.runPromise(program.pipe(Effect.provide(BunCrypto.layer))).then(console.log)
397
+ * // [ '😀.ts' ]
398
+ * ```
399
+ *
400
+ * @category operations
401
+ * @since 0.8.0
402
+ */
403
+ export const scanGlob = (snapshot, query) => Stream.unwrap(Effect.map(evaluate(snapshot, query), ({ next }) => Stream.unfold(undefined, Effect.fnUntraced(function* () {
404
+ const row = yield* next();
405
+ return row === undefined ? undefined : [row, undefined];
406
+ }))));
407
+ /**
408
+ * Collect bounded matches and preserve preceding rows when a work or output
409
+ * budget stops the query. Invalid queries, malformed patterns and snapshot
410
+ * failures remain typed failures; interruption remains interruption.
411
+ *
412
+ * Reaching the row cap always reports `Stopped`, even for the last possible
413
+ * result. Invalid UTF-8 names are counted skips, and invalid-name directories
414
+ * count one skipped subtree without examining its descendants.
415
+ *
416
+ * @example
417
+ * ```ts
418
+ * import * as BunCrypto from "@effect/platform-bun/BunCrypto"
419
+ * import { Search, VirtualFileSystem as Vfs } from "@effect-vfs/core"
420
+ * import { Effect } from "effect"
421
+ *
422
+ * const program = Effect.gen(function*() {
423
+ * const volume = yield* Vfs.fromFixture({ entries: [
424
+ * { kind: "file", path: "/a.ts", bytes: new Uint8Array() },
425
+ * { kind: "file", path: "/b.ts", bytes: new Uint8Array() }
426
+ * ] })
427
+ * return yield* Search.glob(yield* volume.snapshot, {
428
+ * root: "/", include: ["*.ts"], limits: { maxResults: 1 }
429
+ * })
430
+ * })
431
+ * Effect.runPromise(program.pipe(Effect.provide(BunCrypto.layer))).then((report) => {
432
+ * console.log(report.results, report.completion._tag)
433
+ * })
434
+ * // [ "a.ts" ] Stopped
435
+ * ```
436
+ *
437
+ * @category operations
438
+ * @since 0.8.0
439
+ */
440
+ export const glob = Effect.fnUntraced(function* (snapshot, query) {
441
+ const evaluation = yield* evaluate(snapshot, query);
442
+ const results = [];
443
+ const completion = yield* Effect.gen(function* () {
444
+ for (let row = yield* evaluation.next(); row !== undefined; row = yield* evaluation.next())
445
+ results.push(row);
446
+ return Completion.members[0].make({});
447
+ }).pipe(Effect.catchTag("SearchBudgetExceeded", (error) => Effect.succeed(error.path === undefined
448
+ ? Completion.members[1].make({ limit: error.limit })
449
+ : Completion.members[1].make({ limit: error.limit, path: error.path }))));
450
+ return {
451
+ root: evaluation.query.root,
452
+ results,
453
+ work: { ...evaluation.meter.work },
454
+ skips: { ...evaluation.meter.skips },
455
+ completion
456
+ };
457
+ });
458
+ const scanContent = (snapshot, query, mode) => Stream.unwrap(Effect.map(Content.evaluate(snapshot, query, mode), ({ next }) => Stream.unfold(undefined, Effect.fnUntraced(function* () {
459
+ const row = yield* next();
460
+ return row === undefined ? undefined : [row, undefined];
461
+ }))));
462
+ const collectContent = Effect.fnUntraced(function* (snapshot, query, mode) {
463
+ const evaluation = yield* Content.evaluate(snapshot, query, mode);
464
+ const results = [];
465
+ const completion = yield* Effect.gen(function* () {
466
+ for (let row = yield* evaluation.next(); row !== undefined; row = yield* evaluation.next())
467
+ results.push(row);
468
+ return Completion.members[0].make({});
469
+ }).pipe(Effect.catchTag("SearchBudgetExceeded", (error) => Effect.succeed(error.path === undefined
470
+ ? Completion.members[1].make({ limit: error.limit })
471
+ : Completion.members[1].make({ limit: error.limit, path: error.path }))));
472
+ return {
473
+ root: evaluation.query.root,
474
+ results,
475
+ work: { ...evaluation.meter.work },
476
+ skips: { ...evaluation.meter.skips },
477
+ completion
478
+ };
479
+ });
480
+ /**
481
+ * Stream one result per matching line after classifying the complete file for
482
+ * strict UTF-8 and NUL. Each row contains the first whole-match byte range and
483
+ * a UTF-8-bounded excerpt beginning at the match. Files and hard-link names are
484
+ * selected independently; invalid, binary and oversized files are skipped.
485
+ *
486
+ * A budget failure can follow earlier rows. A prefix establishes no completion;
487
+ * use `lines` for coverage and skip counters. Each evaluation owns fresh state.
488
+ * Native regex can block within one call; Effect timeouts cannot preempt it.
489
+ *
490
+ * @example
491
+ * ```ts
492
+ * import * as BunCrypto from "@effect/platform-bun/BunCrypto"
493
+ * import { Search, VirtualFileSystem as Vfs } from "@effect-vfs/core"
494
+ * import { Effect, Stream } from "effect"
495
+ *
496
+ * const program = Effect.gen(function*() {
497
+ * const volume = yield* Vfs.fromFixture({ entries: [
498
+ * { kind: "file", path: "/note", bytes: new TextEncoder().encode("😀TODO\n") }
499
+ * ] })
500
+ * return yield* Stream.runCollect(Search.scanLines(yield* volume.snapshot, {
501
+ * root: "/", include: ["**"], pattern: Search.Pattern.cases.Literal.make({ pattern: "TODO" })
502
+ * }))
503
+ * })
504
+ * Effect.runPromise(program.pipe(Effect.provide(BunCrypto.layer))).then((rows) => {
505
+ * console.log(rows[0]?.range)
506
+ * })
507
+ * // { start: 4, end: 8 }
508
+ * ```
509
+ *
510
+ * @category operations
511
+ * @since 0.8.0
512
+ */
513
+ export const scanLines = (snapshot, query) => scanContent(snapshot, query, "lines");
514
+ /**
515
+ * Collect matching lines, preceding rows, skips, work and completion. Catch only
516
+ * budget exhaustion; query, pattern and snapshot failures remain typed failures,
517
+ * and interruption propagates. Reaching the result cap reports `Stopped` with
518
+ * no lookahead, even for the last possible result.
519
+ *
520
+ * Raw row bytes are UTF-8 path and excerpt bytes plus 16 bytes for the range.
521
+ * Scalar metadata is excluded; this is not heap or serialized JSON accounting.
522
+ * Complete classification charges full eligible file bytes, including skips and
523
+ * repeated hard-link scans. Empty files have no lines.
524
+ *
525
+ * @category operations
526
+ * @since 0.8.0
527
+ */
528
+ export const lines = Effect
529
+ .fnUntraced(function* (snapshot, query) {
530
+ return yield* collectContent(snapshot, query, "lines");
531
+ });
532
+ /**
533
+ * Stream one root-relative filename per matching regular-file path. Classify
534
+ * the complete file before testing lines and stop matching after the first hit.
535
+ * Build no line-result ranges or excerpts. Budget exhaustion fails the Stream;
536
+ * use `files` for coverage and skip counters.
537
+ *
538
+ * @category operations
539
+ * @since 0.8.0
540
+ */
541
+ export const scanFiles = (snapshot, query) => scanContent(snapshot, query, "files");
542
+ /**
543
+ * Collect matching filenames with preceding rows, work, skips and completion.
544
+ * Shares text classification and matching with `lines`, but evaluates only as
545
+ * far as the first hit in each classified file. Raw result bytes count paths
546
+ * only; scanned bytes still charge the full selected file before classification.
547
+ *
548
+ * @category operations
549
+ * @since 0.8.0
550
+ */
551
+ export const files = Effect
552
+ .fnUntraced(function* (snapshot, query) {
553
+ return yield* collectContent(snapshot, query, "files");
554
+ });
555
+ /**
556
+ * Stream exact matching-line counts for completely scanned matching files.
557
+ * Multiple occurrences on one line count once. A stopped file emits no partial
558
+ * count; earlier completed rows can precede a budget failure. Builds no line
559
+ * ranges or excerpts. Use `countLines` when coverage information matters.
560
+ *
561
+ * @category operations
562
+ * @since 0.8.0
563
+ */
564
+ export const scanCountLines = (snapshot, query) => scanContent(snapshot, query, "countLines");
565
+ /**
566
+ * Collect exact per-file matching-line counts with work, skips and completion.
567
+ * A stopped or skipped query's summed counts are lower bounds for its original
568
+ * scope. Raw result accounting is UTF-8 path bytes plus 8 bytes per count;
569
+ * scalar metadata is excluded, as in the other content modes.
570
+ *
571
+ * @category operations
572
+ * @since 0.8.0
573
+ */
574
+ export const countLines = Effect.fnUntraced(function* (snapshot, query) {
575
+ return yield* collectContent(snapshot, query, "countLines");
576
+ });