@ifc-lite/rules 0.2.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/LICENSE +373 -0
- package/README.md +45 -0
- package/dist/engine/rule-engine-chunk.d.ts +25 -0
- package/dist/engine/rule-engine-chunk.d.ts.map +1 -0
- package/dist/engine/rule-engine-chunk.js +43 -0
- package/dist/engine/rule-engine-chunk.js.map +1 -0
- package/dist/engine/rule-engine-compare.d.ts +17 -0
- package/dist/engine/rule-engine-compare.d.ts.map +1 -0
- package/dist/engine/rule-engine-compare.js +69 -0
- package/dist/engine/rule-engine-compare.js.map +1 -0
- package/dist/engine/rule-engine-requirements.d.ts +37 -0
- package/dist/engine/rule-engine-requirements.d.ts.map +1 -0
- package/dist/engine/rule-engine-requirements.js +293 -0
- package/dist/engine/rule-engine-requirements.js.map +1 -0
- package/dist/engine/rule-engine-sets.d.ts +46 -0
- package/dist/engine/rule-engine-sets.d.ts.map +1 -0
- package/dist/engine/rule-engine-sets.js +298 -0
- package/dist/engine/rule-engine-sets.js.map +1 -0
- package/dist/engine/rule-engine-unit.d.ts +23 -0
- package/dist/engine/rule-engine-unit.d.ts.map +1 -0
- package/dist/engine/rule-engine-unit.js +64 -0
- package/dist/engine/rule-engine-unit.js.map +1 -0
- package/dist/engine/rule-engine.d.ts +29 -0
- package/dist/engine/rule-engine.d.ts.map +1 -0
- package/dist/engine/rule-engine.js +244 -0
- package/dist/engine/rule-engine.js.map +1 -0
- package/dist/filter/effective-filter-fields.d.ts +13 -0
- package/dist/filter/effective-filter-fields.d.ts.map +1 -0
- package/dist/filter/effective-filter-fields.js +44 -0
- package/dist/filter/effective-filter-fields.js.map +1 -0
- package/dist/filter/entity-predefined-type.d.ts +24 -0
- package/dist/filter/entity-predefined-type.d.ts.map +1 -0
- package/dist/filter/entity-predefined-type.js +34 -0
- package/dist/filter/entity-predefined-type.js.map +1 -0
- package/dist/filter/filter-evaluate-groups.d.ts +36 -0
- package/dist/filter/filter-evaluate-groups.d.ts.map +1 -0
- package/dist/filter/filter-evaluate-groups.js +111 -0
- package/dist/filter/filter-evaluate-groups.js.map +1 -0
- package/dist/filter/filter-evaluate-model-tag.d.ts +22 -0
- package/dist/filter/filter-evaluate-model-tag.d.ts.map +1 -0
- package/dist/filter/filter-evaluate-model-tag.js +35 -0
- package/dist/filter/filter-evaluate-model-tag.js.map +1 -0
- package/dist/filter/filter-evaluate-mutations.d.ts +51 -0
- package/dist/filter/filter-evaluate-mutations.d.ts.map +1 -0
- package/dist/filter/filter-evaluate-mutations.js +111 -0
- package/dist/filter/filter-evaluate-mutations.js.map +1 -0
- package/dist/filter/filter-evaluate-yield.d.ts +9 -0
- package/dist/filter/filter-evaluate-yield.d.ts.map +1 -0
- package/dist/filter/filter-evaluate-yield.js +32 -0
- package/dist/filter/filter-evaluate-yield.js.map +1 -0
- package/dist/filter/filter-evaluate.d.ts +120 -0
- package/dist/filter/filter-evaluate.d.ts.map +1 -0
- package/dist/filter/filter-evaluate.js +408 -0
- package/dist/filter/filter-evaluate.js.map +1 -0
- package/dist/filter/filter-group-rule.d.ts +23 -0
- package/dist/filter/filter-group-rule.d.ts.map +1 -0
- package/dist/filter/filter-group-rule.js +69 -0
- package/dist/filter/filter-group-rule.js.map +1 -0
- package/dist/filter/filter-groups.d.ts +65 -0
- package/dist/filter/filter-groups.d.ts.map +1 -0
- package/dist/filter/filter-groups.js +136 -0
- package/dist/filter/filter-groups.js.map +1 -0
- package/dist/filter/filter-iteration-source.d.ts +26 -0
- package/dist/filter/filter-iteration-source.d.ts.map +1 -0
- package/dist/filter/filter-iteration-source.js +154 -0
- package/dist/filter/filter-iteration-source.js.map +1 -0
- package/dist/filter/filter-iteration.d.ts +5 -0
- package/dist/filter/filter-iteration.d.ts.map +1 -0
- package/dist/filter/filter-iteration.js +25 -0
- package/dist/filter/filter-iteration.js.map +1 -0
- package/dist/filter/filter-match.d.ts +114 -0
- package/dist/filter/filter-match.d.ts.map +1 -0
- package/dist/filter/filter-match.js +302 -0
- package/dist/filter/filter-match.js.map +1 -0
- package/dist/filter/filter-ops.d.ts +90 -0
- package/dist/filter/filter-ops.d.ts.map +1 -0
- package/dist/filter/filter-ops.js +202 -0
- package/dist/filter/filter-ops.js.map +1 -0
- package/dist/filter/filter-rule-guards.d.ts +4 -0
- package/dist/filter/filter-rule-guards.d.ts.map +1 -0
- package/dist/filter/filter-rule-guards.js +74 -0
- package/dist/filter/filter-rule-guards.js.map +1 -0
- package/dist/filter/filter-rules.d.ts +275 -0
- package/dist/filter/filter-rules.d.ts.map +1 -0
- package/dist/filter/filter-rules.js +68 -0
- package/dist/filter/filter-rules.js.map +1 -0
- package/dist/filter/lens-material-names.d.ts +13 -0
- package/dist/filter/lens-material-names.d.ts.map +1 -0
- package/dist/filter/lens-material-names.js +36 -0
- package/dist/filter/lens-material-names.js.map +1 -0
- package/dist/filter/measure-units.d.ts +29 -0
- package/dist/filter/measure-units.d.ts.map +1 -0
- package/dist/filter/measure-units.js +72 -0
- package/dist/filter/measure-units.js.map +1 -0
- package/dist/filter/model-tag.d.ts +65 -0
- package/dist/filter/model-tag.d.ts.map +1 -0
- package/dist/filter/model-tag.js +57 -0
- package/dist/filter/model-tag.js.map +1 -0
- package/dist/filter/read-measure-subject.d.ts +28 -0
- package/dist/filter/read-measure-subject.d.ts.map +1 -0
- package/dist/filter/read-measure-subject.js +116 -0
- package/dist/filter/read-measure-subject.js.map +1 -0
- package/dist/filter/read-subject.d.ts +72 -0
- package/dist/filter/read-subject.d.ts.map +1 -0
- package/dist/filter/read-subject.js +128 -0
- package/dist/filter/read-subject.js.map +1 -0
- package/dist/filter/subject-match.d.ts +27 -0
- package/dist/filter/subject-match.d.ts.map +1 -0
- package/dist/filter/subject-match.js +52 -0
- package/dist/filter/subject-match.js.map +1 -0
- package/dist/filter/subject-read-options.d.ts +33 -0
- package/dist/filter/subject-read-options.d.ts.map +1 -0
- package/dist/filter/subject-read-options.js +8 -0
- package/dist/filter/subject-read-options.js.map +1 -0
- package/dist/ids/ids-facet-to-rules.d.ts +20 -0
- package/dist/ids/ids-facet-to-rules.d.ts.map +1 -0
- package/dist/ids/ids-facet-to-rules.js +230 -0
- package/dist/ids/ids-facet-to-rules.js.map +1 -0
- package/dist/ids/ids-regex.d.ts +44 -0
- package/dist/ids/ids-regex.d.ts.map +1 -0
- package/dist/ids/ids-regex.js +334 -0
- package/dist/ids/ids-regex.js.map +1 -0
- package/dist/ids/ids-to-rule-set.d.ts +39 -0
- package/dist/ids/ids-to-rule-set.d.ts.map +1 -0
- package/dist/ids/ids-to-rule-set.js +103 -0
- package/dist/ids/ids-to-rule-set.js.map +1 -0
- package/dist/ids/ids-xml-writer.d.ts +11 -0
- package/dist/ids/ids-xml-writer.d.ts.map +1 -0
- package/dist/ids/ids-xml-writer.js +172 -0
- package/dist/ids/ids-xml-writer.js.map +1 -0
- package/dist/ids/rule-set-to-ids.d.ts +45 -0
- package/dist/ids/rule-set-to-ids.d.ts.map +1 -0
- package/dist/ids/rule-set-to-ids.js +140 -0
- package/dist/ids/rule-set-to-ids.js.map +1 -0
- package/dist/ids/rule-to-ids-facets.d.ts +52 -0
- package/dist/ids/rule-to-ids-facets.d.ts.map +1 -0
- package/dist/ids/rule-to-ids-facets.js +291 -0
- package/dist/ids/rule-to-ids-facets.js.map +1 -0
- package/dist/ids/stored-unit-scale.d.ts +15 -0
- package/dist/ids/stored-unit-scale.d.ts.map +1 -0
- package/dist/ids/stored-unit-scale.js +64 -0
- package/dist/ids/stored-unit-scale.js.map +1 -0
- package/dist/index.d.ts +39 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +46 -0
- package/dist/index.js.map +1 -0
- package/dist/rule-set/between-chip.d.ts +54 -0
- package/dist/rule-set/between-chip.d.ts.map +1 -0
- package/dist/rule-set/between-chip.js +95 -0
- package/dist/rule-set/between-chip.js.map +1 -0
- package/dist/rule-set/requirement-invariants.d.ts +25 -0
- package/dist/rule-set/requirement-invariants.d.ts.map +1 -0
- package/dist/rule-set/requirement-invariants.js +29 -0
- package/dist/rule-set/requirement-invariants.js.map +1 -0
- package/dist/rule-set/requirement-text.d.ts +20 -0
- package/dist/rule-set/requirement-text.d.ts.map +1 -0
- package/dist/rule-set/requirement-text.js +179 -0
- package/dist/rule-set/requirement-text.js.map +1 -0
- package/dist/rule-set/rule-set-io-requirement.d.ts +22 -0
- package/dist/rule-set/rule-set-io-requirement.d.ts.map +1 -0
- package/dist/rule-set/rule-set-io-requirement.js +140 -0
- package/dist/rule-set/rule-set-io-requirement.js.map +1 -0
- package/dist/rule-set/rule-set-io-shared.d.ts +24 -0
- package/dist/rule-set/rule-set-io-shared.d.ts.map +1 -0
- package/dist/rule-set/rule-set-io-shared.js +48 -0
- package/dist/rule-set/rule-set-io-shared.js.map +1 -0
- package/dist/rule-set/rule-set-io-subject.d.ts +10 -0
- package/dist/rule-set/rule-set-io-subject.d.ts.map +1 -0
- package/dist/rule-set/rule-set-io-subject.js +67 -0
- package/dist/rule-set/rule-set-io-subject.js.map +1 -0
- package/dist/rule-set/rule-set-io.d.ts +37 -0
- package/dist/rule-set/rule-set-io.d.ts.map +1 -0
- package/dist/rule-set/rule-set-io.js +142 -0
- package/dist/rule-set/rule-set-io.js.map +1 -0
- package/dist/rule-set/rule-set.d.ts +109 -0
- package/dist/rule-set/rule-set.d.ts.map +1 -0
- package/dist/rule-set/rule-set.js +5 -0
- package/dist/rule-set/rule-set.js.map +1 -0
- package/package.json +59 -0
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
|
+
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
|
+
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
|
4
|
+
/**
|
|
5
|
+
* `FilterGroup` — one AND-combined (or OR-combined, per its own
|
|
6
|
+
* `combinator`) list of `FilterRule`s. A `SearchFilterStateValue` (and a
|
|
7
|
+
* saved filter, #4904) carries an ARRAY of these; groups themselves are
|
|
8
|
+
* always OR'd together, mirroring the selector grammar's `+` union
|
|
9
|
+
* (`IfcSlab, material=concrete + IfcDoor` → two groups).
|
|
10
|
+
*
|
|
11
|
+
* Kept in its own file (not folded into `filter-rules.ts`, already near the
|
|
12
|
+
* module-size cap) so both the evaluator and the serialization layer can
|
|
13
|
+
* import it without pulling in the whole rule taxonomy's JSON guards.
|
|
14
|
+
*/
|
|
15
|
+
import { isFilterRule } from './filter-rules.js';
|
|
16
|
+
/** A fresh, empty AND group — what a brand-new filter (or a brand-new
|
|
17
|
+
* group added in the builder) starts from. */
|
|
18
|
+
export function emptyFilterGroup() {
|
|
19
|
+
return { rules: [], combinator: 'AND' };
|
|
20
|
+
}
|
|
21
|
+
export function isFilterGroup(value) {
|
|
22
|
+
if (typeof value !== 'object' || value === null)
|
|
23
|
+
return false;
|
|
24
|
+
const g = value;
|
|
25
|
+
return (Array.isArray(g.rules) &&
|
|
26
|
+
g.rules.every(isFilterRule) &&
|
|
27
|
+
(g.combinator === 'AND' || g.combinator === 'OR'));
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Render `groups` back as selector-ish text — NOT a full inverse of
|
|
31
|
+
* `parseSelector`/`selectorToFilterRules` (most rule kinds have no single
|
|
32
|
+
* canonical spelling; a regex `Name` rule and a typed-in `contains` rule
|
|
33
|
+
* both become `Rule.name`, and only one of those round-trips through text).
|
|
34
|
+
* What this DOES round-trip exactly is the shape the selector's own union
|
|
35
|
+
* syntax cares about: class names (`IfcWall`, `IfcDoor`, …) and GlobalIds,
|
|
36
|
+
* `+`-joined across groups. Used by the Selector field to echo back what
|
|
37
|
+
* the CURRENT filter state reads as, so adding a second group in the
|
|
38
|
+
* builder is visible as a second `+`-joined clause without leaving the
|
|
39
|
+
* builder (#4904).
|
|
40
|
+
*
|
|
41
|
+
* All-or-nothing: an empty group, or a group whose rules do not reduce to
|
|
42
|
+
* exactly ONE renderable clause, makes the WHOLE echo `''` rather than a
|
|
43
|
+
* partial rendering — review (PR #4987) caught that joining per-group
|
|
44
|
+
* fragments with `+` regardless produced text that read as a DIFFERENT
|
|
45
|
+
* query than what evaluation actually runs: an empty group left a
|
|
46
|
+
* dangling ` + `, and several `ifcType`/`globalId` rules AND'd within one
|
|
47
|
+
* group rendered as comma-separated union terms (selector-text unions use
|
|
48
|
+
* `,` to ADD classes, not narrow them), so a two-rule AND group echoed as
|
|
49
|
+
* something the evaluator would read as broader than what it actually
|
|
50
|
+
* matches. Silently showing a narrower or broader query back to the user
|
|
51
|
+
* is the same "matched nothing, said nothing" defect class #4091 exists to
|
|
52
|
+
* avoid, so this omits the echo entirely rather than guess.
|
|
53
|
+
*/
|
|
54
|
+
export function groupsToSelectorText(groups) {
|
|
55
|
+
const clauses = [];
|
|
56
|
+
for (const group of groups) {
|
|
57
|
+
if (group.rules.length === 0)
|
|
58
|
+
continue;
|
|
59
|
+
if (group.rules.length !== 1)
|
|
60
|
+
return '';
|
|
61
|
+
const clause = ruleToSelectorClause(group.rules[0]);
|
|
62
|
+
if (clause === null)
|
|
63
|
+
return '';
|
|
64
|
+
clauses.push(clause);
|
|
65
|
+
}
|
|
66
|
+
return clauses.join(' + ');
|
|
67
|
+
}
|
|
68
|
+
function ruleToSelectorClause(rule) {
|
|
69
|
+
switch (rule.kind) {
|
|
70
|
+
case 'ifcType':
|
|
71
|
+
if (!Array.isArray(rule.values) || rule.values.length === 0)
|
|
72
|
+
return null;
|
|
73
|
+
return rule.values.map((v) => (rule.op === 'notIn' ? `! ${v}` : v)).join(', ');
|
|
74
|
+
case 'globalId':
|
|
75
|
+
if (!Array.isArray(rule.values) || rule.values.length === 0)
|
|
76
|
+
return null;
|
|
77
|
+
return rule.values.map((v) => (rule.op === 'notIn' ? `! ${v}` : v)).join(', ');
|
|
78
|
+
default:
|
|
79
|
+
// Every other rule kind has no single canonical selector spelling —
|
|
80
|
+
// omitted rather than guessed. See the module doc above.
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
/** The rules of `groups[activeIndex]`, clamped into range — what the
|
|
85
|
+
* builder's active-group actions (`addFilterRule` etc., `searchSlice.
|
|
86
|
+
* filterGroups.ts`) operate on. Callers outside the store (HierarchyPanel's
|
|
87
|
+
* upsert-by-kind) read this instead of reimplementing the clamp. */
|
|
88
|
+
export function activeGroupRules(groups, activeIndex) {
|
|
89
|
+
if (groups.length === 0)
|
|
90
|
+
return [];
|
|
91
|
+
const i = Math.max(0, Math.min(activeIndex, groups.length - 1));
|
|
92
|
+
return groups[i]?.rules ?? [];
|
|
93
|
+
}
|
|
94
|
+
/** Total rule count across every group — what "empty filter" checks and rule
|
|
95
|
+
* count badges read, since a `groups.length === 1` filter with zero rules
|
|
96
|
+
* in that one group must still read as empty. */
|
|
97
|
+
export function totalRuleCount(groups) {
|
|
98
|
+
let n = 0;
|
|
99
|
+
for (const g of groups)
|
|
100
|
+
n += g.rules.length;
|
|
101
|
+
return n;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Parse a persisted `groups` array, refusing (returning `null`) the WHOLE
|
|
105
|
+
* query rather than silently dropping a group this build cannot read. A
|
|
106
|
+
* saved filter with an unreadable group is exactly the "matched nothing,
|
|
107
|
+
* said nothing" defect class #4091 exists to avoid — narrowing it to the
|
|
108
|
+
* readable groups would change which elements a `+` union matches without
|
|
109
|
+
* telling anyone.
|
|
110
|
+
*/
|
|
111
|
+
export function parseFilterGroups(raw) {
|
|
112
|
+
if (!Array.isArray(raw) || raw.length === 0)
|
|
113
|
+
return null;
|
|
114
|
+
const groups = [];
|
|
115
|
+
for (const entry of raw) {
|
|
116
|
+
if (typeof entry !== 'object' || entry === null)
|
|
117
|
+
return null;
|
|
118
|
+
const g = entry;
|
|
119
|
+
if (!Array.isArray(g.rules))
|
|
120
|
+
return null;
|
|
121
|
+
// Every rule in a persisted group must parse — a group carrying even
|
|
122
|
+
// one rule this build cannot read is not "mostly readable", it is a
|
|
123
|
+
// different query, so the whole thing is refused (see module doc).
|
|
124
|
+
if (!g.rules.every(isFilterRule))
|
|
125
|
+
return null;
|
|
126
|
+
// A missing/corrupted combinator must refuse too, not default to AND —
|
|
127
|
+
// review (PR #4987): silently coercing a dropped/garbled `OR` into
|
|
128
|
+
// `AND` changes which elements the group matches without telling
|
|
129
|
+
// anyone, the exact defect class this whole function exists to avoid.
|
|
130
|
+
if (g.combinator !== 'AND' && g.combinator !== 'OR')
|
|
131
|
+
return null;
|
|
132
|
+
groups.push({ rules: g.rules, combinator: g.combinator });
|
|
133
|
+
}
|
|
134
|
+
return groups;
|
|
135
|
+
}
|
|
136
|
+
//# sourceMappingURL=filter-groups.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"filter-groups.js","sourceRoot":"","sources":["../../src/filter/filter-groups.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Iteration-source selection (index prefilter) and cheap-first rule
|
|
3
|
+
* ordering for the path-B evaluator.
|
|
4
|
+
*
|
|
5
|
+
* Split out of `filter-evaluate.ts` (which keeps the orchestration /
|
|
6
|
+
* per-entity loop) to stay under the module size cap — both pieces here are
|
|
7
|
+
* pure given `store`/`rules`, which is what makes them unit-testable without
|
|
8
|
+
* an `EvalContext`.
|
|
9
|
+
*/
|
|
10
|
+
import { type IfcDataStore } from '@ifc-lite/parser';
|
|
11
|
+
import { type MutablePropertyView } from '@ifc-lite/mutations';
|
|
12
|
+
import type { Combinator, FilterRule } from './filter-rules.js';
|
|
13
|
+
/** Return the raw expressId column as the iteration source. The per-entity
|
|
14
|
+
* loops already skip empty rows (`if (!expressId) continue`) so the
|
|
15
|
+
* typed-array shape is correctness-safe AND lets the federated entry report
|
|
16
|
+
* a `total` rather than streaming with `total = -1`. */
|
|
17
|
+
export declare function iterateAllExpressIds(store: IfcDataStore): ArrayLike<number>;
|
|
18
|
+
/**
|
|
19
|
+
* Decide which expressIds the evaluator walks. Public for testability —
|
|
20
|
+
* consumers should only depend on the results returned, not on the
|
|
21
|
+
* iteration count, but a benchmark / regression test may want to assert
|
|
22
|
+
* the prefilter actually narrows.
|
|
23
|
+
*/
|
|
24
|
+
export declare function selectIterationSource(store: IfcDataStore, rules: readonly FilterRule[], combinator: Combinator, candidateExpressIds: Iterable<number> | undefined, modelId?: string, mutationView?: MutablePropertyView): ArrayLike<number> | Iterable<number>;
|
|
25
|
+
export declare function orderRulesByCost(rules: readonly FilterRule[]): FilterRule[];
|
|
26
|
+
//# sourceMappingURL=filter-iteration-source.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"filter-iteration-source.d.ts","sourceRoot":"","sources":["../../src/filter/filter-iteration-source.ts"],"names":[],"mappings":"AAIA;;;;;;;;GAQG;AAEH,OAAO,EAAyB,KAAK,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAC5E,OAAO,EAA6B,KAAK,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAE1F,OAAO,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAKhE;;;yDAGyD;AACzD,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,YAAY,GAAG,SAAS,CAAC,MAAM,CAAC,CAE3E;AAgBD;;;;;GAKG;AACH,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,YAAY,EACnB,KAAK,EAAE,SAAS,UAAU,EAAE,EAC5B,UAAU,EAAE,UAAU,EACtB,mBAAmB,EAAE,QAAQ,CAAC,MAAM,CAAC,GAAG,SAAS,EACjD,OAAO,CAAC,EAAE,MAAM,EAChB,YAAY,CAAC,EAAE,mBAAmB,GACjC,SAAS,CAAC,MAAM,CAAC,GAAG,QAAQ,CAAC,MAAM,CAAC,CAuCtC;AAoED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,SAAS,UAAU,EAAE,GAAG,UAAU,EAAE,CAO3E"}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
|
+
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
|
+
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
|
4
|
+
/**
|
|
5
|
+
* Iteration-source selection (index prefilter) and cheap-first rule
|
|
6
|
+
* ordering for the path-B evaluator.
|
|
7
|
+
*
|
|
8
|
+
* Split out of `filter-evaluate.ts` (which keeps the orchestration /
|
|
9
|
+
* per-entity loop) to stay under the module size cap — both pieces here are
|
|
10
|
+
* pure given `store`/`rules`, which is what makes them unit-testable without
|
|
11
|
+
* an `EvalContext`.
|
|
12
|
+
*/
|
|
13
|
+
import { isQueryableObjectType } from '@ifc-lite/parser';
|
|
14
|
+
import { iterateEffectiveEntityIds } from '@ifc-lite/mutations';
|
|
15
|
+
import { unionByStorey } from './filter-match.js';
|
|
16
|
+
// ── Iteration source: index prefilter (AND + op:in) ──────────────────────────
|
|
17
|
+
/** Return the raw expressId column as the iteration source. The per-entity
|
|
18
|
+
* loops already skip empty rows (`if (!expressId) continue`) so the
|
|
19
|
+
* typed-array shape is correctness-safe AND lets the federated entry report
|
|
20
|
+
* a `total` rather than streaming with `total = -1`. */
|
|
21
|
+
export function iterateAllExpressIds(store) {
|
|
22
|
+
return store.entities.expressId;
|
|
23
|
+
}
|
|
24
|
+
function* effectiveFilterIds(store, view, types) {
|
|
25
|
+
const sourceIds = types ? undefined : store.entities.expressId;
|
|
26
|
+
for (const { expressId, type, overlayCreated } of iterateEffectiveEntityIds(store, view, types, sourceIds)) {
|
|
27
|
+
// An untyped search historically scanned EntityTable rows, not every STEP
|
|
28
|
+
// geometry primitive. A typed search already used the source type bucket.
|
|
29
|
+
if (!types && overlayCreated && !isQueryableObjectType(type))
|
|
30
|
+
continue;
|
|
31
|
+
yield expressId;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Decide which expressIds the evaluator walks. Public for testability —
|
|
36
|
+
* consumers should only depend on the results returned, not on the
|
|
37
|
+
* iteration count, but a benchmark / regression test may want to assert
|
|
38
|
+
* the prefilter actually narrows.
|
|
39
|
+
*/
|
|
40
|
+
export function selectIterationSource(store, rules, combinator, candidateExpressIds, modelId, mutationView) {
|
|
41
|
+
// Caller-supplied narrowing wins (Tier-1 candidates).
|
|
42
|
+
if (candidateExpressIds !== undefined)
|
|
43
|
+
return candidateExpressIds;
|
|
44
|
+
if (mutationView?.hasPendingChanges()) {
|
|
45
|
+
// A source type bucket omits entities retyped into it and newly created
|
|
46
|
+
// entities. Keep the same AND narrowing when it is safe, but apply it to
|
|
47
|
+
// the effective ID set rather than the parser's immutable index.
|
|
48
|
+
if (combinator === 'AND') {
|
|
49
|
+
const typeRule = rules.find((rule) => rule.kind === 'ifcType' && rule.op === 'in' && rule.values.length > 0);
|
|
50
|
+
if (typeRule?.kind === 'ifcType') {
|
|
51
|
+
return effectiveFilterIds(store, mutationView, typeRule.values);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return effectiveFilterIds(store, mutationView);
|
|
55
|
+
}
|
|
56
|
+
// Prefilter only applies under AND. OR rules are unioned; you can't
|
|
57
|
+
// shrink the candidate set from a single OR clause without losing
|
|
58
|
+
// results from the other clauses.
|
|
59
|
+
if (combinator !== 'AND')
|
|
60
|
+
return iterateAllExpressIds(store);
|
|
61
|
+
// Try to find the smallest narrowing source. Multiple op:in rules in
|
|
62
|
+
// the same query can each suggest a candidate bucket; we pick the
|
|
63
|
+
// smallest one (the per-entity loop re-checks every rule, so any one
|
|
64
|
+
// valid bucket is correctness-safe — fewer rows = less work).
|
|
65
|
+
let best = null;
|
|
66
|
+
for (const rule of rules) {
|
|
67
|
+
if (rule.kind === 'ifcType' && rule.op === 'in' && rule.values.length > 0) {
|
|
68
|
+
const bucket = unionByType(store, rule.values);
|
|
69
|
+
if (bucket && (best === null || bucket.length < best.length))
|
|
70
|
+
best = bucket;
|
|
71
|
+
}
|
|
72
|
+
else if (rule.kind === 'storey' && rule.op === 'in' && rule.values.length > 0) {
|
|
73
|
+
const bucket = unionByStorey(store, rule, modelId);
|
|
74
|
+
if (bucket && (best === null || bucket.length < best.length))
|
|
75
|
+
best = bucket;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return best ?? iterateAllExpressIds(store);
|
|
79
|
+
}
|
|
80
|
+
function unionByType(store, names) {
|
|
81
|
+
// @raw-entity-enumeration-ok selectIterationSource reaches this source-bucket fast path only when no mutation view has pending changes
|
|
82
|
+
const byType = store.entityIndex.byType;
|
|
83
|
+
if (!byType || byType.size === 0)
|
|
84
|
+
return null;
|
|
85
|
+
// STEP type names are stored UPPERCASE; rule values arrive in canonical
|
|
86
|
+
// PascalCase ("IfcWall") so we uppercase here at the boundary.
|
|
87
|
+
const out = [];
|
|
88
|
+
for (const name of names) {
|
|
89
|
+
const bucket = byType.get(name.toUpperCase());
|
|
90
|
+
if (bucket)
|
|
91
|
+
for (const id of bucket)
|
|
92
|
+
out.push(id);
|
|
93
|
+
}
|
|
94
|
+
return out.length > 0 ? out : null;
|
|
95
|
+
}
|
|
96
|
+
// ── Cheap-first rule ordering ────────────────────────────────────────────────
|
|
97
|
+
/**
|
|
98
|
+
* AGENTS.md §2: never call `extractPropertiesOnDemand` in a large loop.
|
|
99
|
+
* We can't avoid it entirely for `property`/`quantity` rules, but we can
|
|
100
|
+
* make sure cheap rules check first so AND/OR short-circuit skips the
|
|
101
|
+
* expensive parse for entities that already fail/pass.
|
|
102
|
+
*/
|
|
103
|
+
const RULE_COST = {
|
|
104
|
+
// Constant-time comparison against the entity's owning model.
|
|
105
|
+
model: 0,
|
|
106
|
+
// Constant-time set lookups against the owning model's tag set (#4215).
|
|
107
|
+
modelTag: 0,
|
|
108
|
+
// Column-only — single TypedArray read.
|
|
109
|
+
ifcType: 0,
|
|
110
|
+
// Pre-built reverse-map lookup.
|
|
111
|
+
storey: 1,
|
|
112
|
+
// Pre-built reverse-map lookup (elementToStorey → storeyElevations).
|
|
113
|
+
elevation: 1,
|
|
114
|
+
// String-table indirection.
|
|
115
|
+
name: 2,
|
|
116
|
+
// Column-only — same TypedArray read as ifcType, just a different column.
|
|
117
|
+
globalId: 0,
|
|
118
|
+
// Source-buffer parse (resolveEntityPredefinedType re-reads the entity's
|
|
119
|
+
// attributes) - same cost class as a pset parse, so order it after the
|
|
120
|
+
// cheap column checks that can short-circuit it. (#1462)
|
|
121
|
+
predefinedType: 10,
|
|
122
|
+
// Source-buffer parse (the AGENTS.md §2 hot path).
|
|
123
|
+
property: 10,
|
|
124
|
+
quantity: 10,
|
|
125
|
+
// Source-buffer parse (extractAllEntityAttributes) — same cost class.
|
|
126
|
+
attribute: 10,
|
|
127
|
+
// Relationship-graph walk + on-demand resolve — as costly as a pset parse.
|
|
128
|
+
material: 10,
|
|
129
|
+
classification: 10,
|
|
130
|
+
// One reverse-relationship lookup (IfcRelDefinesByType) + a columnar
|
|
131
|
+
// Name read on the resolved type id — no source-buffer parse, so it's
|
|
132
|
+
// cheap like `storey`/`elevation`, not `material`/`classification`.
|
|
133
|
+
type: 1,
|
|
134
|
+
// Relationship-graph walk (potentially multi-hop, containment +
|
|
135
|
+
// aggregation) plus a columnar Name read on each ancestor — no
|
|
136
|
+
// source-buffer parse, but costlier than the single-hop `type` lookup, so
|
|
137
|
+
// it sits with `material`/`classification`. No `op:'in'` prefilter bucket
|
|
138
|
+
// exists for it either (#4903's maintainer ruling: fall back to a full
|
|
139
|
+
// scan for `parent=`, the same trade-off `location=` already accepted,
|
|
140
|
+
// and narrow only if a real perf number asks for it).
|
|
141
|
+
parent: 10,
|
|
142
|
+
// One reverse-relationship lookup (IfcRelAssignsToGroup) + a columnar
|
|
143
|
+
// Name/class read per group, like `type`; no source-buffer parse.
|
|
144
|
+
group: 1,
|
|
145
|
+
};
|
|
146
|
+
export function orderRulesByCost(rules) {
|
|
147
|
+
// Stable sort — equal-cost rules retain their authored order so the
|
|
148
|
+
// user's intent is visible in debug logs / SQL preview.
|
|
149
|
+
return rules
|
|
150
|
+
.map((r, i) => ({ r, i, cost: RULE_COST[r.kind] }))
|
|
151
|
+
.sort((a, b) => a.cost - b.cost || a.i - b.i)
|
|
152
|
+
.map((x) => x.r);
|
|
153
|
+
}
|
|
154
|
+
//# sourceMappingURL=filter-iteration-source.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"filter-iteration-source.js","sourceRoot":"","sources":["../../src/filter/filter-iteration-source.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export declare function isNumericArrayLike(value: unknown): value is ArrayLike<number>;
|
|
2
|
+
export declare function toNumericIterable(source: ArrayLike<number> | Iterable<number>): Iterable<number>;
|
|
3
|
+
/** Materialise known-size collections for indexed chunking and progress. */
|
|
4
|
+
export declare function materialiseNumericIterable(source: ArrayLike<number> | Iterable<number>): ArrayLike<number> | null;
|
|
5
|
+
//# sourceMappingURL=filter-iteration.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"filter-iteration.d.ts","sourceRoot":"","sources":["../../src/filter/filter-iteration.ts"],"names":[],"mappings":"AAIA,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,SAAS,CAAC,MAAM,CAAC,CAG7E;AAED,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,SAAS,CAAC,MAAM,CAAC,GAAG,QAAQ,CAAC,MAAM,CAAC,GAC3C,QAAQ,CAAC,MAAM,CAAC,CAMlB;AAED,4EAA4E;AAC5E,wBAAgB,0BAA0B,CACxC,MAAM,EAAE,SAAS,CAAC,MAAM,CAAC,GAAG,QAAQ,CAAC,MAAM,CAAC,GAC3C,SAAS,CAAC,MAAM,CAAC,GAAG,IAAI,CAI1B"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
|
+
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
|
+
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
|
4
|
+
export function isNumericArrayLike(value) {
|
|
5
|
+
return typeof value === 'object' && value !== null
|
|
6
|
+
&& typeof value.length === 'number';
|
|
7
|
+
}
|
|
8
|
+
export function toNumericIterable(source) {
|
|
9
|
+
if (Symbol.iterator in Object(source))
|
|
10
|
+
return source;
|
|
11
|
+
return (function* () {
|
|
12
|
+
const arr = source;
|
|
13
|
+
for (let i = 0; i < arr.length; i++)
|
|
14
|
+
yield arr[i];
|
|
15
|
+
})();
|
|
16
|
+
}
|
|
17
|
+
/** Materialise known-size collections for indexed chunking and progress. */
|
|
18
|
+
export function materialiseNumericIterable(source) {
|
|
19
|
+
if (Array.isArray(source) || isNumericArrayLike(source))
|
|
20
|
+
return source;
|
|
21
|
+
if (source instanceof Set)
|
|
22
|
+
return Array.from(source);
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=filter-iteration.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"filter-iteration.js","sourceRoot":"","sources":["../../src/filter/filter-iteration.ts"],"names":[],"mappings":"AAAA;;+DAE+D;AAE/D,MAAM,UAAU,kBAAkB,CAAC,KAAc;IAC/C,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI;WAC7C,OAAQ,KAA8B,CAAC,MAAM,KAAK,QAAQ,CAAC;AAClE,CAAC;AAED,MAAM,UAAU,iBAAiB,CAC/B,MAA4C;IAE5C,IAAI,MAAM,CAAC,QAAQ,IAAI,MAAM,CAAC,MAAM,CAAC;QAAE,OAAO,MAA0B,CAAC;IACzE,OAAO,CAAC,QAAQ,CAAC;QACf,MAAM,GAAG,GAAG,MAA2B,CAAC;QACxC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,GAAG,CAAC,MAAM,EAAE,CAAC,EAAE;YAAE,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC;IACpD,CAAC,CAAC,EAAE,CAAC;AACP,CAAC;AAED,4EAA4E;AAC5E,MAAM,UAAU,0BAA0B,CACxC,MAA4C;IAE5C,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,kBAAkB,CAAC,MAAM,CAAC;QAAE,OAAO,MAAM,CAAC;IACvE,IAAI,MAAM,YAAY,GAAG;QAAE,OAAO,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACrD,OAAO,IAAI,CAAC;AACd,CAAC"}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-rule value resolution + matching for the path-B evaluator.
|
|
3
|
+
*
|
|
4
|
+
* Split out of `filter-evaluate.ts` (which keeps the iteration /
|
|
5
|
+
* orchestration logic) to stay under the module size cap. These helpers
|
|
6
|
+
* are pure given their inputs, which is what makes them unit-testable in
|
|
7
|
+
* `filter-evaluate.test.ts` via the evaluator's `__internal` re-export.
|
|
8
|
+
*/
|
|
9
|
+
import { extractPropertiesOnDemand, extractQuantitiesOnDemand, type IfcDataStore, type MaterialInfo, type ClassificationInfo } from '@ifc-lite/parser';
|
|
10
|
+
import { type PropertyRule, type QuantityRule, type ClassificationRule, type AttributeRule, type StoreyRule, type ParentRule, type TextKind } from './filter-rules.js';
|
|
11
|
+
/**
|
|
12
|
+
* Compare a rule's property-set / property name against a row's.
|
|
13
|
+
*
|
|
14
|
+
* `kind` is the rule saying what it holds (see {@link TextKind}). A `'regex'`
|
|
15
|
+
* name is a SOURCE and the whole string is the pattern, which is what makes
|
|
16
|
+
* the selector syntax's `/Pset_.*Common/.FireRating` reach `Pset_WallCommon`
|
|
17
|
+
* and `Pset_SlabCommon` in one rule; a `'literal'` name is compared as text
|
|
18
|
+
* even when it is spelled with slashes, which is what the grammar's quoting
|
|
19
|
+
* means and the only reason `"/Wall/".FireRating` can be asked for at all.
|
|
20
|
+
*
|
|
21
|
+
* A name with no declared kind was typed into a chip field, where the
|
|
22
|
+
* Lists-panel `/…/` convention (#1591) is the user's only way to say
|
|
23
|
+
* "pattern"; anything else there is the historical case-insensitive equality.
|
|
24
|
+
*
|
|
25
|
+
* `compileNameMatcher` throws when the regex body is syntactically valid but
|
|
26
|
+
* rejected by `@ifc-lite/regex-guard` as unsafe (catastrophic-backtracking
|
|
27
|
+
* shaped, or over the length cap) — see `filter-ops.ts`'s `regexOpMatches`
|
|
28
|
+
* docstring for the full contract. This function does not catch that; it
|
|
29
|
+
* propagates to `filter-evaluate.ts`'s caller, which must.
|
|
30
|
+
*/
|
|
31
|
+
export declare function nameMatches(rulePattern: string, rowName: string, kind?: TextKind): boolean;
|
|
32
|
+
export interface PsetRow {
|
|
33
|
+
setName: string;
|
|
34
|
+
propertyName: string;
|
|
35
|
+
value: string;
|
|
36
|
+
}
|
|
37
|
+
export type PsetRows = ReadonlyArray<PsetRow>;
|
|
38
|
+
export interface QtyRow {
|
|
39
|
+
setName: string;
|
|
40
|
+
quantityName: string;
|
|
41
|
+
value: number;
|
|
42
|
+
}
|
|
43
|
+
export type QtyRows = ReadonlyArray<QtyRow>;
|
|
44
|
+
export declare function flattenPsets(psets: ReturnType<typeof extractPropertiesOnDemand>): PsetRows;
|
|
45
|
+
export declare function flattenQtys(qtos: ReturnType<typeof extractQuantitiesOnDemand>): QtyRows;
|
|
46
|
+
export declare function stringifyValue(value: unknown): string;
|
|
47
|
+
export declare function matchPropertyRule(rule: PropertyRule, rows: PsetRows): boolean;
|
|
48
|
+
/** One entity's generic named attributes, as `extractAllEntityAttributes`
|
|
49
|
+
* returns them — schema-driven, string/number/boolean values only. */
|
|
50
|
+
export type AttrRows = ReadonlyArray<{
|
|
51
|
+
name: string;
|
|
52
|
+
value: string | number | boolean;
|
|
53
|
+
}>;
|
|
54
|
+
/**
|
|
55
|
+
* Match an `attribute` rule (Description, ObjectType, Tag, LongName, any
|
|
56
|
+
* other schema-named attribute) against one entity's extracted attributes.
|
|
57
|
+
* Attribute NAME matching is case-insensitive, same as the IDS attribute
|
|
58
|
+
* facet this reuses the extraction from; the VALUE comparison reuses
|
|
59
|
+
* `valueOpMatches`, the same comparator `matchPropertyRule` uses, so a
|
|
60
|
+
* numeric attribute value compares the same way a numeric property does.
|
|
61
|
+
*/
|
|
62
|
+
export declare function matchAttributeRule(rule: AttributeRule, attrs: AttrRows): boolean;
|
|
63
|
+
export declare function matchQuantityRule(rule: QuantityRule, rows: QtyRows): boolean;
|
|
64
|
+
export declare function defaultStoreyName(store: IfcDataStore, expressId: number): string;
|
|
65
|
+
/** Does `expressId` (in `modelId`) sit in a storey ref'd by `rule.refs`?
|
|
66
|
+
* `IfcBuildingStorey.Name` isn't unique, so once a `StoreyRule` carries
|
|
67
|
+
* an exact (modelId, expressId) ref (mirrored from a HierarchyPanel
|
|
68
|
+
* click), matching bypasses Name entirely. Reaches through a containing
|
|
69
|
+
* space via {@link storeyIdOf}, same as the Name-matched path. */
|
|
70
|
+
export declare function storeyMatchesRefs(store: IfcDataStore, expressId: number, modelId: string, rule: StoreyRule): boolean;
|
|
71
|
+
/** Index-prefilter twin of {@link storeyMatchesRefs}/Name matching: the
|
|
72
|
+
* bucket of elements a `storey op:'in'` rule narrows to for `modelId`.
|
|
73
|
+
* Includes each matched storey's directly-contained elements AND every
|
|
74
|
+
* element `bySpace` lists for a space belonging to that storey, so this
|
|
75
|
+
* prefilter can never exclude a candidate the (also widened) per-entity
|
|
76
|
+
* check in `filter-evaluate.ts` would go on to match. */
|
|
77
|
+
export declare function unionByStorey(store: IfcDataStore, rule: StoreyRule, modelId: string | undefined): number[] | null;
|
|
78
|
+
/** Collect the *individual* material names an element exposes - each layer /
|
|
79
|
+
* constituent / profile material, or the single plain material - for the
|
|
80
|
+
* multi-valued `material` rule matcher and the material dropdown. Shares the
|
|
81
|
+
* #1366 lens collector, so filtering by material groups by the real materials
|
|
82
|
+
* rather than the layer-set / Revit family+type name that masked them. (#1462) */
|
|
83
|
+
export declare function materialNamesOf(info: MaterialInfo | null): string[];
|
|
84
|
+
/** `materialNamesOf` plus every material Category (`material=` matches
|
|
85
|
+
* Name OR Category, #4094) — categories come from `flattenMaterials`
|
|
86
|
+
* (`@ifc-lite/ids`'s material-facet flattener), reused rather than a
|
|
87
|
+
* second layer/profile/constituent walk; only its `.category` field is
|
|
88
|
+
* used, since its `.name` field also surfaces a layer's own label, which
|
|
89
|
+
* `materialNamesOf` deliberately excludes (#1462). */
|
|
90
|
+
export declare function materialMatchCandidates(info: MaterialInfo | null): string[];
|
|
91
|
+
/**
|
|
92
|
+
* Match a classification rule against an element's classification refs.
|
|
93
|
+
* `system` (when set) scopes to one system; value ops match a ref's code
|
|
94
|
+
* (identification) OR name. Pushes both through even when `undefined`
|
|
95
|
+
* (absent) rather than filtering on truthy — `matchStringAnyNone` decides
|
|
96
|
+
* absent-vs-empty explicitly (#4930).
|
|
97
|
+
*/
|
|
98
|
+
export declare function matchClassificationRule(rule: ClassificationRule, refs: readonly ClassificationInfo[]): boolean;
|
|
99
|
+
/** Element elevation in metres, derived from its building storey's
|
|
100
|
+
* elevation. Returns null when the element isn't placed in the spatial
|
|
101
|
+
* hierarchy (so an elevation rule simply doesn't match it). */
|
|
102
|
+
export declare function elevationOf(store: IfcDataStore, expressId: number): number | null;
|
|
103
|
+
/**
|
|
104
|
+
* `parent=Foo` (#4903) — does ANY ancestor of `expressId`, walking upward
|
|
105
|
+
* through spatial containment and aggregation to any depth via
|
|
106
|
+
* `collectSpatialAncestors` (`@ifc-lite/data`, the single shared resolver),
|
|
107
|
+
* have a Name the rule's op matches? Reuses `matchStringAnyNone`, the same
|
|
108
|
+
* multi-valued convention `material`/`classification` rules use: an element
|
|
109
|
+
* with NO ancestors — or none whose Name matches — satisfies neither a
|
|
110
|
+
* positive nor a negative op (#4659). `getNameOrUndefined`, not `getName`,
|
|
111
|
+
* so an ancestor with no Name at all is a real absent candidate (#4930).
|
|
112
|
+
*/
|
|
113
|
+
export declare function matchParentRule(rule: ParentRule, store: IfcDataStore, expressId: number): boolean;
|
|
114
|
+
//# sourceMappingURL=filter-match.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"filter-match.d.ts","sourceRoot":"","sources":["../../src/filter/filter-match.ts"],"names":[],"mappings":"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"}
|