@effect-vfs/core 0.7.0 → 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.
- package/README.md +15 -238
- package/dist/LiveVolume.d.ts.map +1 -1
- package/dist/LiveVolume.js +9 -9
- package/dist/Search.d.ts +730 -0
- package/dist/Search.d.ts.map +1 -0
- package/dist/Search.js +568 -0
- package/dist/SnapshotDelta.d.ts +234 -0
- package/dist/SnapshotDelta.d.ts.map +1 -1
- package/dist/SnapshotDelta.js +107 -0
- package/dist/VirtualFileSystem.d.ts +53 -1
- package/dist/VirtualFileSystem.d.ts.map +1 -1
- package/dist/VirtualFileSystem.js +59 -1
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -0
- package/dist/internal/canonicalBase64.js +3 -3
- package/dist/internal/fixture.js +3 -3
- 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/overlayDiff.js +2 -2
- package/dist/internal/path.d.ts.map +1 -1
- package/dist/internal/path.js +3 -3
- package/dist/internal/resolution.d.ts +135 -0
- package/dist/internal/resolution.d.ts.map +1 -0
- package/dist/internal/resolution.js +277 -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 +60 -26
- 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 +77 -0
- package/dist/internal/tokenRegistry.d.ts.map +1 -0
- package/dist/internal/tokenRegistry.js +128 -0
- package/dist/internal/tree.js +2 -2
- package/dist/internal/virtualFileSystem.d.ts +32 -17
- package/dist/internal/virtualFileSystem.d.ts.map +1 -1
- package/dist/internal/virtualFileSystem.js +640 -993
- 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/package.json +10 -6
|
@@ -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,568 @@
|
|
|
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.fn("Search.glob")(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.fn("Search.lines")((snapshot, query) => collectContent(snapshot, query, "lines"));
|
|
529
|
+
/**
|
|
530
|
+
* Stream one root-relative filename per matching regular-file path. Classify
|
|
531
|
+
* the complete file before testing lines and stop matching after the first hit.
|
|
532
|
+
* Build no line-result ranges or excerpts. Budget exhaustion fails the Stream;
|
|
533
|
+
* use `files` for coverage and skip counters.
|
|
534
|
+
*
|
|
535
|
+
* @category operations
|
|
536
|
+
* @since 0.8.0
|
|
537
|
+
*/
|
|
538
|
+
export const scanFiles = (snapshot, query) => scanContent(snapshot, query, "files");
|
|
539
|
+
/**
|
|
540
|
+
* Collect matching filenames with preceding rows, work, skips and completion.
|
|
541
|
+
* Shares text classification and matching with `lines`, but evaluates only as
|
|
542
|
+
* far as the first hit in each classified file. Raw result bytes count paths
|
|
543
|
+
* only; scanned bytes still charge the full selected file before classification.
|
|
544
|
+
*
|
|
545
|
+
* @category operations
|
|
546
|
+
* @since 0.8.0
|
|
547
|
+
*/
|
|
548
|
+
export const files = Effect.fn("Search.files")((snapshot, query) => collectContent(snapshot, query, "files"));
|
|
549
|
+
/**
|
|
550
|
+
* Stream exact matching-line counts for completely scanned matching files.
|
|
551
|
+
* Multiple occurrences on one line count once. A stopped file emits no partial
|
|
552
|
+
* count; earlier completed rows can precede a budget failure. Builds no line
|
|
553
|
+
* ranges or excerpts. Use `countLines` when coverage information matters.
|
|
554
|
+
*
|
|
555
|
+
* @category operations
|
|
556
|
+
* @since 0.8.0
|
|
557
|
+
*/
|
|
558
|
+
export const scanCountLines = (snapshot, query) => scanContent(snapshot, query, "countLines");
|
|
559
|
+
/**
|
|
560
|
+
* Collect exact per-file matching-line counts with work, skips and completion.
|
|
561
|
+
* A stopped or skipped query's summed counts are lower bounds for its original
|
|
562
|
+
* scope. Raw result accounting is UTF-8 path bytes plus 8 bytes per count;
|
|
563
|
+
* scalar metadata is excluded, as in the other content modes.
|
|
564
|
+
*
|
|
565
|
+
* @category operations
|
|
566
|
+
* @since 0.8.0
|
|
567
|
+
*/
|
|
568
|
+
export const countLines = Effect.fn("Search.countLines")((snapshot, query) => collectContent(snapshot, query, "countLines"));
|