@prisma/streams-server 0.1.2 → 0.1.3
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/CONTRIBUTING.md +8 -0
- package/package.json +2 -1
- package/src/app.ts +290 -17
- package/src/app_core.ts +1833 -698
- package/src/app_local.ts +144 -4
- package/src/auto_tune.ts +62 -0
- package/src/bootstrap.ts +159 -1
- package/src/concurrency_gate.ts +108 -0
- package/src/config.ts +116 -14
- package/src/db/db.ts +1201 -131
- package/src/db/schema.ts +308 -8
- package/src/foreground_activity.ts +55 -0
- package/src/index/indexer.ts +254 -124
- package/src/index/lexicon_file_cache.ts +261 -0
- package/src/index/lexicon_format.ts +93 -0
- package/src/index/lexicon_indexer.ts +789 -0
- package/src/index/secondary_indexer.ts +824 -0
- package/src/index/secondary_schema.ts +105 -0
- package/src/ingest.ts +10 -12
- package/src/manifest.ts +143 -8
- package/src/memory.ts +183 -8
- package/src/metrics.ts +15 -29
- package/src/metrics_emitter.ts +26 -3
- package/src/notifier.ts +121 -5
- package/src/objectstore/accounting.ts +92 -0
- package/src/objectstore/mock_r2.ts +1 -1
- package/src/objectstore/r2.ts +17 -1
- package/src/profiles/evlog/schema.ts +234 -0
- package/src/profiles/evlog.ts +299 -0
- package/src/profiles/generic.ts +47 -0
- package/src/profiles/index.ts +205 -0
- package/src/profiles/metrics/block_format.ts +109 -0
- package/src/profiles/metrics/normalize.ts +366 -0
- package/src/profiles/metrics/schema.ts +319 -0
- package/src/profiles/metrics.ts +85 -0
- package/src/profiles/profile.ts +225 -0
- package/src/{touch/engine.ts → profiles/stateProtocol/changes.ts} +3 -20
- package/src/profiles/stateProtocol/routes.ts +389 -0
- package/src/profiles/stateProtocol/types.ts +6 -0
- package/src/profiles/stateProtocol/validation.ts +51 -0
- package/src/profiles/stateProtocol.ts +100 -0
- package/src/read_filter.ts +468 -0
- package/src/reader.ts +2151 -164
- package/src/runtime_memory.ts +200 -0
- package/src/runtime_memory_sampler.ts +235 -0
- package/src/schema/read_json.ts +43 -0
- package/src/schema/registry.ts +563 -59
- package/src/search/agg_format.ts +638 -0
- package/src/search/aggregate.ts +389 -0
- package/src/search/binary/codec.ts +162 -0
- package/src/search/binary/docset.ts +67 -0
- package/src/search/binary/restart_strings.ts +181 -0
- package/src/search/binary/varint.ts +34 -0
- package/src/search/bitset.ts +19 -0
- package/src/search/col_format.ts +382 -0
- package/src/search/col_runtime.ts +59 -0
- package/src/search/column_encoding.ts +43 -0
- package/src/search/companion_file_cache.ts +319 -0
- package/src/search/companion_format.ts +313 -0
- package/src/search/companion_manager.ts +1086 -0
- package/src/search/companion_plan.ts +218 -0
- package/src/search/fts_format.ts +423 -0
- package/src/search/fts_runtime.ts +333 -0
- package/src/search/query.ts +875 -0
- package/src/search/schema.ts +245 -0
- package/src/segment/cache.ts +93 -2
- package/src/segment/cached_segment.ts +89 -0
- package/src/segment/format.ts +108 -36
- package/src/segment/segmenter.ts +79 -5
- package/src/segment/segmenter_worker.ts +31 -5
- package/src/segment/segmenter_workers.ts +40 -12
- package/src/server.ts +150 -36
- package/src/sqlite/adapter.ts +155 -8
- package/src/sqlite/runtime_stats.ts +163 -0
- package/src/stats.ts +3 -3
- package/src/stream_size_reconciler.ts +100 -0
- package/src/touch/canonical_change.ts +7 -0
- package/src/touch/live_metrics.ts +94 -64
- package/src/touch/live_templates.ts +15 -1
- package/src/touch/manager.ts +166 -88
- package/src/touch/{interpreter_worker.ts → processor_worker.ts} +13 -13
- package/src/touch/spec.ts +95 -92
- package/src/touch/touch_journal.ts +4 -0
- package/src/touch/worker_pool.ts +6 -13
- package/src/touch/worker_protocol.ts +3 -3
- package/src/uploader.ts +77 -6
- package/src/util/bloom256.ts +2 -2
- package/src/util/byte_lru.ts +73 -0
- package/src/util/lru.ts +8 -0
- package/src/util/stream_paths.ts +19 -0
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
import { Result } from "better-result";
|
|
2
|
+
import type { SearchFtsClause, SearchTextTarget } from "./query";
|
|
3
|
+
import { FtsFieldView, FtsSectionView, type FtsPostingBlock } from "./fts_format";
|
|
4
|
+
|
|
5
|
+
export const SEARCH_PREFIX_TERM_LIMIT = 1024;
|
|
6
|
+
|
|
7
|
+
type PostingDoc = {
|
|
8
|
+
docId: number;
|
|
9
|
+
positions?: number[];
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
type ResolvedTermGroup = {
|
|
13
|
+
ordinals: number[];
|
|
14
|
+
docFreq: number;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
type CandidateDocIds = ReadonlySet<number> | null;
|
|
18
|
+
|
|
19
|
+
function intersectInto(target: Set<number> | null, next: Set<number>): Set<number> {
|
|
20
|
+
if (target == null) return next;
|
|
21
|
+
for (const docId of Array.from(target)) {
|
|
22
|
+
if (!next.has(docId)) target.delete(docId);
|
|
23
|
+
}
|
|
24
|
+
return target;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function unionInto(target: Set<number>, next: Iterable<number>): void {
|
|
28
|
+
for (const docId of next) target.add(docId);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function unionDocIdsInto(target: Set<number>, docIds: Uint32Array): void {
|
|
32
|
+
for (let index = 0; index < docIds.length; index++) target.add(docIds[index]!);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function postingDocs(field: FtsFieldView, termOrdinal: number): Map<number, PostingDoc> {
|
|
36
|
+
const docs = new Map<number, PostingDoc>();
|
|
37
|
+
const iterator = field.postings(termOrdinal);
|
|
38
|
+
for (;;) {
|
|
39
|
+
const block = iterator.nextBlock();
|
|
40
|
+
if (!block) break;
|
|
41
|
+
for (let index = 0; index < block.docIds.length; index++) {
|
|
42
|
+
const docId = block.docIds[index]!;
|
|
43
|
+
if (!block.positions || !block.posOffsets) {
|
|
44
|
+
docs.set(docId, { docId });
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
const start = block.posOffsets[index]!;
|
|
48
|
+
const end = block.posOffsets[index + 1]!;
|
|
49
|
+
docs.set(docId, {
|
|
50
|
+
docId,
|
|
51
|
+
positions: Array.from(block.positions.subarray(start, end)),
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return docs;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function docsForTerm(field: FtsFieldView, termOrdinal: number, candidateDocIds: CandidateDocIds = null): Set<number> {
|
|
59
|
+
if (candidateDocIds && candidateDocIds.size === 0) return new Set();
|
|
60
|
+
const docs = new Set<number>();
|
|
61
|
+
const iterator = field.postings(termOrdinal);
|
|
62
|
+
for (;;) {
|
|
63
|
+
const block = iterator.nextBlock();
|
|
64
|
+
if (!block) break;
|
|
65
|
+
if (!candidateDocIds) {
|
|
66
|
+
unionDocIdsInto(docs, block.docIds);
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
for (let index = 0; index < block.docIds.length; index++) {
|
|
70
|
+
const docId = block.docIds[index]!;
|
|
71
|
+
if (candidateDocIds.has(docId)) docs.add(docId);
|
|
72
|
+
}
|
|
73
|
+
if (docs.size === candidateDocIds.size) break;
|
|
74
|
+
}
|
|
75
|
+
return docs;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function docsForOrdinals(field: FtsFieldView, ordinals: number[], candidateDocIds: CandidateDocIds = null): Set<number> {
|
|
79
|
+
if (candidateDocIds && candidateDocIds.size === 0) return new Set();
|
|
80
|
+
const docs = new Set<number>();
|
|
81
|
+
for (const ordinal of ordinals) {
|
|
82
|
+
unionInto(docs, docsForTerm(field, ordinal, candidateDocIds));
|
|
83
|
+
if (candidateDocIds && docs.size === candidateDocIds.size) break;
|
|
84
|
+
}
|
|
85
|
+
return docs;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function totalDocFreq(field: FtsFieldView, ordinals: number[]): number {
|
|
89
|
+
let total = 0;
|
|
90
|
+
for (const ordinal of ordinals) total += field.docFreq(ordinal);
|
|
91
|
+
return total;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function sortBySelectivity(groups: ResolvedTermGroup[]): ResolvedTermGroup[] {
|
|
95
|
+
return [...groups].sort((left, right) => left.docFreq - right.docFreq);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function phraseDocsForFieldResult(
|
|
99
|
+
field: FtsFieldView,
|
|
100
|
+
tokens: string[],
|
|
101
|
+
prefix: boolean,
|
|
102
|
+
candidateDocIds: CandidateDocIds = null
|
|
103
|
+
): Result<Set<number>, { message: string }> {
|
|
104
|
+
if (!field.positions) return Result.err({ message: "field does not support phrase queries" });
|
|
105
|
+
if (tokens.length === 0) return Result.ok(new Set());
|
|
106
|
+
|
|
107
|
+
const expandedTokens: ResolvedTermGroup[] = [];
|
|
108
|
+
for (let index = 0; index < tokens.length; index++) {
|
|
109
|
+
const token = tokens[index]!;
|
|
110
|
+
const isLast = index === tokens.length - 1;
|
|
111
|
+
if (prefix && isLast) {
|
|
112
|
+
const expansionRes = field.expandPrefixResult(token, SEARCH_PREFIX_TERM_LIMIT);
|
|
113
|
+
if (Result.isError(expansionRes)) return expansionRes;
|
|
114
|
+
expandedTokens.push({ ordinals: expansionRes.value, docFreq: totalDocFreq(field, expansionRes.value) });
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
const termOrdinal = field.lookupTerm(token);
|
|
118
|
+
const ordinals = termOrdinal == null ? [] : [termOrdinal];
|
|
119
|
+
expandedTokens.push({ ordinals, docFreq: totalDocFreq(field, ordinals) });
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
let candidateDocs: Set<number> | null = candidateDocIds ? new Set(candidateDocIds) : null;
|
|
123
|
+
for (const group of sortBySelectivity(expandedTokens)) {
|
|
124
|
+
const docs = docsForOrdinals(field, group.ordinals, candidateDocs);
|
|
125
|
+
candidateDocs = intersectInto(candidateDocs, docs);
|
|
126
|
+
if (candidateDocs.size === 0) return Result.ok(candidateDocs);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const positionsByToken = expandedTokens.map((group) => group.ordinals.map((ordinal) => postingDocs(field, ordinal)));
|
|
130
|
+
const matches = new Set<number>();
|
|
131
|
+
for (const docId of candidateDocs ?? []) {
|
|
132
|
+
const positionSets: Array<Set<number>[]> = [];
|
|
133
|
+
for (const tokenMaps of positionsByToken) {
|
|
134
|
+
const variants: Set<number>[] = [];
|
|
135
|
+
for (const tokenMap of tokenMaps) {
|
|
136
|
+
const posting = tokenMap.get(docId);
|
|
137
|
+
if (posting?.positions) variants.push(new Set(posting.positions));
|
|
138
|
+
}
|
|
139
|
+
positionSets.push(variants);
|
|
140
|
+
}
|
|
141
|
+
let found = false;
|
|
142
|
+
for (const startPositions of positionSets[0] ?? []) {
|
|
143
|
+
for (const start of startPositions) {
|
|
144
|
+
let ok = true;
|
|
145
|
+
for (let tokenIndex = 1; tokenIndex < positionSets.length; tokenIndex++) {
|
|
146
|
+
const needed = start + tokenIndex;
|
|
147
|
+
const any = positionSets[tokenIndex]!.some((positions) => positions.has(needed));
|
|
148
|
+
if (!any) {
|
|
149
|
+
ok = false;
|
|
150
|
+
break;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
if (ok) {
|
|
154
|
+
found = true;
|
|
155
|
+
break;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
if (found) break;
|
|
159
|
+
}
|
|
160
|
+
if (found) matches.add(docId);
|
|
161
|
+
}
|
|
162
|
+
return Result.ok(matches);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function docsForTextFieldResult(
|
|
166
|
+
field: FtsFieldView,
|
|
167
|
+
tokens: string[],
|
|
168
|
+
phrase: boolean,
|
|
169
|
+
prefix: boolean,
|
|
170
|
+
candidateDocIds: CandidateDocIds = null
|
|
171
|
+
): Result<Set<number>, { message: string }> {
|
|
172
|
+
if (phrase) return phraseDocsForFieldResult(field, tokens, prefix, candidateDocIds);
|
|
173
|
+
if (tokens.length === 0) return Result.ok(new Set());
|
|
174
|
+
|
|
175
|
+
const groups: ResolvedTermGroup[] = [];
|
|
176
|
+
for (let index = 0; index < tokens.length; index++) {
|
|
177
|
+
const token = tokens[index]!;
|
|
178
|
+
const isLast = index === tokens.length - 1;
|
|
179
|
+
if (prefix && isLast) {
|
|
180
|
+
const expansionRes = field.expandPrefixResult(token, SEARCH_PREFIX_TERM_LIMIT);
|
|
181
|
+
if (Result.isError(expansionRes)) return expansionRes;
|
|
182
|
+
groups.push({ ordinals: expansionRes.value, docFreq: totalDocFreq(field, expansionRes.value) });
|
|
183
|
+
} else {
|
|
184
|
+
const termOrdinal = field.lookupTerm(token);
|
|
185
|
+
const ordinals = termOrdinal == null ? [] : [termOrdinal];
|
|
186
|
+
groups.push({ ordinals, docFreq: totalDocFreq(field, ordinals) });
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
let intersection: Set<number> | null = candidateDocIds ? new Set(candidateDocIds) : null;
|
|
191
|
+
for (const group of sortBySelectivity(groups)) {
|
|
192
|
+
const docs = docsForOrdinals(field, group.ordinals, intersection);
|
|
193
|
+
intersection = intersectInto(intersection, docs);
|
|
194
|
+
if (intersection.size === 0) return Result.ok(intersection);
|
|
195
|
+
}
|
|
196
|
+
return Result.ok(intersection ?? new Set());
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function docsForTargetFieldResult(
|
|
200
|
+
field: FtsFieldView,
|
|
201
|
+
target: SearchTextTarget,
|
|
202
|
+
tokens: string[],
|
|
203
|
+
phrase: boolean,
|
|
204
|
+
prefix: boolean,
|
|
205
|
+
candidateDocIds: CandidateDocIds = null
|
|
206
|
+
): Result<Set<number>, { message: string }> {
|
|
207
|
+
if (target.config.kind === "keyword") {
|
|
208
|
+
const docs = new Set<number>();
|
|
209
|
+
if (tokens.length === 0) return Result.ok(docs);
|
|
210
|
+
if (prefix) {
|
|
211
|
+
const expansionRes = field.expandPrefixResult(tokens[0]!, SEARCH_PREFIX_TERM_LIMIT);
|
|
212
|
+
if (Result.isError(expansionRes)) return expansionRes;
|
|
213
|
+
for (const termOrdinal of expansionRes.value) {
|
|
214
|
+
unionInto(docs, docsForTerm(field, termOrdinal, candidateDocIds));
|
|
215
|
+
if (candidateDocIds && docs.size === candidateDocIds.size) break;
|
|
216
|
+
}
|
|
217
|
+
return Result.ok(docs);
|
|
218
|
+
}
|
|
219
|
+
const termOrdinal = field.lookupTerm(tokens[0]!);
|
|
220
|
+
if (termOrdinal != null) unionInto(docs, docsForTerm(field, termOrdinal, candidateDocIds));
|
|
221
|
+
return Result.ok(docs);
|
|
222
|
+
}
|
|
223
|
+
return docsForTextFieldResult(field, tokens, phrase, prefix, candidateDocIds);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
export function filterDocIdsByFtsClauseResult(args: {
|
|
227
|
+
companion: FtsSectionView;
|
|
228
|
+
clause: SearchFtsClause;
|
|
229
|
+
candidateDocIds?: CandidateDocIds;
|
|
230
|
+
}): Result<Set<number>, { message: string }> {
|
|
231
|
+
const candidateDocIds = args.candidateDocIds ?? null;
|
|
232
|
+
if (args.clause.kind === "has") {
|
|
233
|
+
const field = args.companion.getField(args.clause.field);
|
|
234
|
+
if (!field) return Result.err({ message: `missing .fts2 field ${args.clause.field}` });
|
|
235
|
+
const docs = new Set<number>();
|
|
236
|
+
for (const docId of field.existsDocIds()) {
|
|
237
|
+
if (!candidateDocIds || candidateDocIds.has(docId)) docs.add(docId);
|
|
238
|
+
}
|
|
239
|
+
return Result.ok(docs);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
if (args.clause.kind === "keyword") {
|
|
243
|
+
const field = args.companion.getField(args.clause.field);
|
|
244
|
+
if (!field) return Result.err({ message: `missing .fts2 field ${args.clause.field}` });
|
|
245
|
+
if (args.clause.prefix) {
|
|
246
|
+
const expansionRes = field.expandPrefixResult(args.clause.canonicalValue, SEARCH_PREFIX_TERM_LIMIT);
|
|
247
|
+
if (Result.isError(expansionRes)) return expansionRes;
|
|
248
|
+
const docs = new Set<number>();
|
|
249
|
+
for (const termOrdinal of expansionRes.value) {
|
|
250
|
+
unionInto(docs, docsForTerm(field, termOrdinal, candidateDocIds));
|
|
251
|
+
if (candidateDocIds && docs.size === candidateDocIds.size) break;
|
|
252
|
+
}
|
|
253
|
+
return Result.ok(docs);
|
|
254
|
+
}
|
|
255
|
+
const termOrdinal = field.lookupTerm(args.clause.canonicalValue);
|
|
256
|
+
return Result.ok(termOrdinal == null ? new Set() : docsForTerm(field, termOrdinal, candidateDocIds));
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const docs = new Set<number>();
|
|
260
|
+
for (const target of args.clause.fields) {
|
|
261
|
+
const field = args.companion.getField(target.field);
|
|
262
|
+
if (!field) return Result.err({ message: `missing .fts2 field ${target.field}` });
|
|
263
|
+
const fieldDocsRes = docsForTargetFieldResult(
|
|
264
|
+
field,
|
|
265
|
+
target,
|
|
266
|
+
args.clause.tokens,
|
|
267
|
+
args.clause.phrase,
|
|
268
|
+
args.clause.prefix,
|
|
269
|
+
candidateDocIds
|
|
270
|
+
);
|
|
271
|
+
if (Result.isError(fieldDocsRes)) return fieldDocsRes;
|
|
272
|
+
unionInto(docs, fieldDocsRes.value);
|
|
273
|
+
if (candidateDocIds && docs.size === candidateDocIds.size) break;
|
|
274
|
+
}
|
|
275
|
+
return Result.ok(docs);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function estimateDocFreqForFtsClauseResult(args: {
|
|
279
|
+
companion: FtsSectionView;
|
|
280
|
+
clause: SearchFtsClause;
|
|
281
|
+
}): Result<number, { message: string }> {
|
|
282
|
+
if (args.clause.kind === "has") {
|
|
283
|
+
const field = args.companion.getField(args.clause.field);
|
|
284
|
+
if (!field) return Result.err({ message: `missing .fts2 field ${args.clause.field}` });
|
|
285
|
+
return Result.ok(field.existsDocIds().length);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
if (args.clause.kind === "keyword") {
|
|
289
|
+
const field = args.companion.getField(args.clause.field);
|
|
290
|
+
if (!field) return Result.err({ message: `missing .fts2 field ${args.clause.field}` });
|
|
291
|
+
if (args.clause.prefix) {
|
|
292
|
+
const expansionRes = field.expandPrefixResult(args.clause.canonicalValue, SEARCH_PREFIX_TERM_LIMIT);
|
|
293
|
+
if (Result.isError(expansionRes)) return expansionRes;
|
|
294
|
+
return Result.ok(totalDocFreq(field, expansionRes.value));
|
|
295
|
+
}
|
|
296
|
+
const termOrdinal = field.lookupTerm(args.clause.canonicalValue);
|
|
297
|
+
return Result.ok(termOrdinal == null ? 0 : field.docFreq(termOrdinal));
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
return Result.ok(Number.MAX_SAFE_INTEGER);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
export function filterDocIdsByFtsClausesResult(args: {
|
|
304
|
+
companion: FtsSectionView;
|
|
305
|
+
clauses: SearchFtsClause[];
|
|
306
|
+
onEstimateMs?: (deltaMs: number) => void;
|
|
307
|
+
}): Result<Set<number>, { message: string }> {
|
|
308
|
+
if (args.clauses.length === 0) return Result.ok(new Set());
|
|
309
|
+
|
|
310
|
+
const planned: Array<{ clause: SearchFtsClause; docFreq: number }> = [];
|
|
311
|
+
const estimateStartedAt = Date.now();
|
|
312
|
+
for (const clause of args.clauses) {
|
|
313
|
+
const docFreqRes = estimateDocFreqForFtsClauseResult({ companion: args.companion, clause });
|
|
314
|
+
if (Result.isError(docFreqRes)) return docFreqRes;
|
|
315
|
+
planned.push({ clause, docFreq: docFreqRes.value });
|
|
316
|
+
}
|
|
317
|
+
args.onEstimateMs?.(Date.now() - estimateStartedAt);
|
|
318
|
+
|
|
319
|
+
planned.sort((left, right) => left.docFreq - right.docFreq);
|
|
320
|
+
let intersection: Set<number> | null = null;
|
|
321
|
+
for (const plan of planned) {
|
|
322
|
+
const clauseRes = filterDocIdsByFtsClauseResult({
|
|
323
|
+
companion: args.companion,
|
|
324
|
+
clause: plan.clause,
|
|
325
|
+
candidateDocIds: intersection,
|
|
326
|
+
});
|
|
327
|
+
if (Result.isError(clauseRes)) return clauseRes;
|
|
328
|
+
intersection = intersection == null ? clauseRes.value : intersectInto(intersection, clauseRes.value);
|
|
329
|
+
if (intersection.size === 0) break;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
return Result.ok(intersection ?? new Set());
|
|
333
|
+
}
|