@fintekkers/ledger-models 0.4.26 → 0.4.28
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/node/wrappers/models/position/position_matcher.d.ts +27 -0
- package/node/wrappers/models/position/position_matcher.js +53 -0
- package/node/wrappers/models/position/position_matcher.test.d.ts +1 -0
- package/node/wrappers/models/position/position_matcher.test.js +77 -0
- package/node/wrappers/models/position/position_matcher.test.ts +96 -0
- package/node/wrappers/models/position/position_matcher.ts +71 -0
- package/package.json +1 -1
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { FieldProto } from '../../../fintekkers/models/position/field_pb';
|
|
2
|
+
import { PositionFilterOperator } from '../../../fintekkers/models/position/position_util_pb';
|
|
3
|
+
/** A single filter clause: the field, operator and filter value. */
|
|
4
|
+
export interface FilterSpec {
|
|
5
|
+
field: FieldProto;
|
|
6
|
+
operator: PositionFilterOperator;
|
|
7
|
+
value: any;
|
|
8
|
+
}
|
|
9
|
+
/** A row the matcher can read: the Security and Position wrappers qualify. */
|
|
10
|
+
export interface FilterableRow {
|
|
11
|
+
getField(field: FieldProto): any;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Single-field evaluator (LM-281), mirroring Java's PositionFilter.matches:
|
|
15
|
+
* ASSET_CLASS with EQUALS / NOT_EQUALS uses the shared assetClassMatches
|
|
16
|
+
* rule, so a group code such as FIXED_INCOME matches its members and labels;
|
|
17
|
+
* every other field compares exactly. A null or undefined stored value never
|
|
18
|
+
* throws: EQUALS and the ordering operators drop the row, NOT_EQUALS keeps
|
|
19
|
+
* it. An unknown operator matches nothing. Errors raised by row.getField
|
|
20
|
+
* propagate (a row represents an absent value by returning null).
|
|
21
|
+
*/
|
|
22
|
+
export declare function matches(field: FieldProto, operator: PositionFilterOperator, filterValue: any, storedValue: any): boolean;
|
|
23
|
+
/**
|
|
24
|
+
* Keep the rows matching every clause in `filters` (AND semantics, input
|
|
25
|
+
* order preserved).
|
|
26
|
+
*/
|
|
27
|
+
export declare function filterRows<T extends FilterableRow>(rows: T[], filters: FilterSpec[]): T[];
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.filterRows = exports.matches = void 0;
|
|
4
|
+
const field_pb_1 = require("../../../fintekkers/models/position/field_pb");
|
|
5
|
+
const position_util_pb_1 = require("../../../fintekkers/models/position/position_util_pb");
|
|
6
|
+
const product_hierarchy_1 = require("../security/product_hierarchy");
|
|
7
|
+
/**
|
|
8
|
+
* Single-field evaluator (LM-281), mirroring Java's PositionFilter.matches:
|
|
9
|
+
* ASSET_CLASS with EQUALS / NOT_EQUALS uses the shared assetClassMatches
|
|
10
|
+
* rule, so a group code such as FIXED_INCOME matches its members and labels;
|
|
11
|
+
* every other field compares exactly. A null or undefined stored value never
|
|
12
|
+
* throws: EQUALS and the ordering operators drop the row, NOT_EQUALS keeps
|
|
13
|
+
* it. An unknown operator matches nothing. Errors raised by row.getField
|
|
14
|
+
* propagate (a row represents an absent value by returning null).
|
|
15
|
+
*/
|
|
16
|
+
function matches(field, operator, filterValue, storedValue) {
|
|
17
|
+
if (storedValue === null || storedValue === undefined) {
|
|
18
|
+
return operator === position_util_pb_1.PositionFilterOperator.NOT_EQUALS;
|
|
19
|
+
}
|
|
20
|
+
if (field === field_pb_1.FieldProto.ASSET_CLASS &&
|
|
21
|
+
(operator === position_util_pb_1.PositionFilterOperator.EQUALS ||
|
|
22
|
+
operator === position_util_pb_1.PositionFilterOperator.NOT_EQUALS)) {
|
|
23
|
+
const equals = (0, product_hierarchy_1.assetClassMatches)(filterValue, storedValue);
|
|
24
|
+
return operator === position_util_pb_1.PositionFilterOperator.EQUALS ? equals : !equals;
|
|
25
|
+
}
|
|
26
|
+
switch (operator) {
|
|
27
|
+
case position_util_pb_1.PositionFilterOperator.EQUALS:
|
|
28
|
+
return storedValue === filterValue;
|
|
29
|
+
case position_util_pb_1.PositionFilterOperator.NOT_EQUALS:
|
|
30
|
+
return storedValue !== filterValue;
|
|
31
|
+
case position_util_pb_1.PositionFilterOperator.LESS_THAN:
|
|
32
|
+
return storedValue < filterValue;
|
|
33
|
+
case position_util_pb_1.PositionFilterOperator.LESS_THAN_OR_EQUALS:
|
|
34
|
+
return storedValue <= filterValue;
|
|
35
|
+
case position_util_pb_1.PositionFilterOperator.MORE_THAN:
|
|
36
|
+
return storedValue > filterValue;
|
|
37
|
+
case position_util_pb_1.PositionFilterOperator.MORE_THAN_OR_EQUALS:
|
|
38
|
+
return storedValue >= filterValue;
|
|
39
|
+
default:
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
exports.matches = matches;
|
|
44
|
+
/**
|
|
45
|
+
* Keep the rows matching every clause in `filters` (AND semantics, input
|
|
46
|
+
* order preserved).
|
|
47
|
+
*/
|
|
48
|
+
function filterRows(rows, filters) {
|
|
49
|
+
return rows.filter((row) => {
|
|
50
|
+
return filters.every((spec) => matches(spec.field, spec.operator, spec.value, row.getField(spec.field)));
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
exports.filterRows = filterRows;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* LM-281: the JS filter helper matches Java's PositionFilter row for row.
|
|
4
|
+
*
|
|
5
|
+
* ASSET_CLASS EQUALS / NOT_EQUALS use the shared assetClassMatches rule;
|
|
6
|
+
* every other field compares exactly. A null stored value never throws.
|
|
7
|
+
*/
|
|
8
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
9
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
const field_pb_1 = require("../../../fintekkers/models/position/field_pb");
|
|
13
|
+
const position_util_pb_1 = require("../../../fintekkers/models/position/position_util_pb");
|
|
14
|
+
const security_pb_1 = require("../../../fintekkers/models/security/security_pb");
|
|
15
|
+
const security_1 = __importDefault(require("../security/security"));
|
|
16
|
+
const position_matcher_1 = require("./position_matcher");
|
|
17
|
+
function securityRow(assetClass) {
|
|
18
|
+
const proto = new security_pb_1.SecurityProto();
|
|
19
|
+
proto.setAssetClass(assetClass);
|
|
20
|
+
return new security_1.default(proto);
|
|
21
|
+
}
|
|
22
|
+
function stubRow(assetClass) {
|
|
23
|
+
return {
|
|
24
|
+
getField: (_field) => assetClass,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
function assetClasses(rows) {
|
|
28
|
+
return rows.map((row) => row.getField(field_pb_1.FieldProto.ASSET_CLASS));
|
|
29
|
+
}
|
|
30
|
+
function assetClassFilter(operator) {
|
|
31
|
+
return [{ field: field_pb_1.FieldProto.ASSET_CLASS, operator, value: 'FIXED_INCOME' }];
|
|
32
|
+
}
|
|
33
|
+
describe('position_matcher', () => {
|
|
34
|
+
test('ASSET_CLASS EQUALS FIXED_INCOME matches group members', () => {
|
|
35
|
+
const rows = ['RATES', 'CREDIT', 'Fixed Income', 'EQUITY'].map(securityRow);
|
|
36
|
+
const result = (0, position_matcher_1.filterRows)(rows, assetClassFilter(position_util_pb_1.PositionFilterOperator.EQUALS));
|
|
37
|
+
expect(assetClasses(result)).toEqual(['RATES', 'CREDIT', 'Fixed Income']);
|
|
38
|
+
});
|
|
39
|
+
test('ASSET_CLASS NOT_EQUALS FIXED_INCOME keeps only non-members', () => {
|
|
40
|
+
const rows = ['RATES', 'CREDIT', 'Fixed Income', 'EQUITY'].map(securityRow);
|
|
41
|
+
const result = (0, position_matcher_1.filterRows)(rows, assetClassFilter(position_util_pb_1.PositionFilterOperator.NOT_EQUALS));
|
|
42
|
+
expect(assetClasses(result)).toEqual(['EQUITY']);
|
|
43
|
+
});
|
|
44
|
+
test('null, empty and unknown stored values never throw', () => {
|
|
45
|
+
const rows = [stubRow(null), securityRow(''), securityRow('NOT_A_CLASS')];
|
|
46
|
+
const equals = assetClassFilter(position_util_pb_1.PositionFilterOperator.EQUALS);
|
|
47
|
+
const notEquals = assetClassFilter(position_util_pb_1.PositionFilterOperator.NOT_EQUALS);
|
|
48
|
+
expect(() => (0, position_matcher_1.filterRows)(rows, equals)).not.toThrow();
|
|
49
|
+
expect((0, position_matcher_1.filterRows)(rows, equals)).toEqual([]);
|
|
50
|
+
expect(() => (0, position_matcher_1.filterRows)(rows, notEquals)).not.toThrow();
|
|
51
|
+
expect(assetClasses((0, position_matcher_1.filterRows)(rows, notEquals))).toEqual([null, '', 'NOT_A_CLASS']);
|
|
52
|
+
});
|
|
53
|
+
test('other fields compare exactly', () => {
|
|
54
|
+
expect((0, position_matcher_1.matches)(field_pb_1.FieldProto.PORTFOLIO_NAME, position_util_pb_1.PositionFilterOperator.EQUALS, 'a', 'a')).toBe(true);
|
|
55
|
+
expect((0, position_matcher_1.matches)(field_pb_1.FieldProto.PORTFOLIO_NAME, position_util_pb_1.PositionFilterOperator.EQUALS, 'a', 'b')).toBe(false);
|
|
56
|
+
expect((0, position_matcher_1.matches)(field_pb_1.FieldProto.PORTFOLIO_NAME, position_util_pb_1.PositionFilterOperator.NOT_EQUALS, 'a', 'b')).toBe(true);
|
|
57
|
+
expect((0, position_matcher_1.matches)(field_pb_1.FieldProto.PORTFOLIO_NAME, position_util_pb_1.PositionFilterOperator.NOT_EQUALS, 'a', 'a')).toBe(false);
|
|
58
|
+
expect((0, position_matcher_1.matches)(field_pb_1.FieldProto.MATURITY_DATE, position_util_pb_1.PositionFilterOperator.MORE_THAN, 1, 2)).toBe(true);
|
|
59
|
+
expect((0, position_matcher_1.matches)(field_pb_1.FieldProto.MATURITY_DATE, position_util_pb_1.PositionFilterOperator.MORE_THAN, 2, 1)).toBe(false);
|
|
60
|
+
expect((0, position_matcher_1.matches)(field_pb_1.FieldProto.PORTFOLIO_NAME, position_util_pb_1.PositionFilterOperator.EQUALS, 'a', null)).toBe(false);
|
|
61
|
+
expect((0, position_matcher_1.matches)(field_pb_1.FieldProto.PORTFOLIO_NAME, position_util_pb_1.PositionFilterOperator.NOT_EQUALS, 'a', null)).toBe(true);
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
describe('the published (compiled) position_matcher.js', () => {
|
|
65
|
+
// npm publish runs no tsc: the tracked .js is what consumers get.
|
|
66
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
67
|
+
const compiled = require('./position_matcher.js');
|
|
68
|
+
test('exports working matches and filterRows', () => {
|
|
69
|
+
expect(typeof compiled.matches).toBe('function');
|
|
70
|
+
expect(typeof compiled.filterRows).toBe('function');
|
|
71
|
+
expect(compiled.matches(field_pb_1.FieldProto.ASSET_CLASS, position_util_pb_1.PositionFilterOperator.EQUALS, 'FIXED_INCOME', 'RATES')).toBe(true);
|
|
72
|
+
expect(compiled.matches(field_pb_1.FieldProto.ASSET_CLASS, position_util_pb_1.PositionFilterOperator.NOT_EQUALS, 'FIXED_INCOME', 'EQUITY')).toBe(true);
|
|
73
|
+
const rows = ['RATES', 'EQUITY'].map(securityRow);
|
|
74
|
+
const kept = compiled.filterRows(rows, assetClassFilter(position_util_pb_1.PositionFilterOperator.EQUALS));
|
|
75
|
+
expect(assetClasses(kept)).toEqual(['RATES']);
|
|
76
|
+
});
|
|
77
|
+
});
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LM-281: the JS filter helper matches Java's PositionFilter row for row.
|
|
3
|
+
*
|
|
4
|
+
* ASSET_CLASS EQUALS / NOT_EQUALS use the shared assetClassMatches rule;
|
|
5
|
+
* every other field compares exactly. A null stored value never throws.
|
|
6
|
+
*/
|
|
7
|
+
import { FieldProto } from '../../../fintekkers/models/position/field_pb';
|
|
8
|
+
import { PositionFilterOperator } from '../../../fintekkers/models/position/position_util_pb';
|
|
9
|
+
import { SecurityProto } from '../../../fintekkers/models/security/security_pb';
|
|
10
|
+
import Security from '../security/security';
|
|
11
|
+
import { FilterableRow, FilterSpec, filterRows, matches } from './position_matcher';
|
|
12
|
+
|
|
13
|
+
function securityRow(assetClass: string): Security {
|
|
14
|
+
const proto = new SecurityProto();
|
|
15
|
+
proto.setAssetClass(assetClass);
|
|
16
|
+
return new Security(proto);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function stubRow(assetClass: string | null): FilterableRow {
|
|
20
|
+
return {
|
|
21
|
+
getField: (_field: FieldProto) => assetClass,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function assetClasses(rows: FilterableRow[]): Array<string | null> {
|
|
26
|
+
return rows.map((row) => row.getField(FieldProto.ASSET_CLASS));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function assetClassFilter(operator: PositionFilterOperator): FilterSpec[] {
|
|
30
|
+
return [{ field: FieldProto.ASSET_CLASS, operator, value: 'FIXED_INCOME' }];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
describe('position_matcher', () => {
|
|
34
|
+
test('ASSET_CLASS EQUALS FIXED_INCOME matches group members', () => {
|
|
35
|
+
const rows = ['RATES', 'CREDIT', 'Fixed Income', 'EQUITY'].map(securityRow);
|
|
36
|
+
const result = filterRows(rows, assetClassFilter(PositionFilterOperator.EQUALS));
|
|
37
|
+
expect(assetClasses(result)).toEqual(['RATES', 'CREDIT', 'Fixed Income']);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test('ASSET_CLASS NOT_EQUALS FIXED_INCOME keeps only non-members', () => {
|
|
41
|
+
const rows = ['RATES', 'CREDIT', 'Fixed Income', 'EQUITY'].map(securityRow);
|
|
42
|
+
const result = filterRows(rows, assetClassFilter(PositionFilterOperator.NOT_EQUALS));
|
|
43
|
+
expect(assetClasses(result)).toEqual(['EQUITY']);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test('null, empty and unknown stored values never throw', () => {
|
|
47
|
+
const rows = [stubRow(null), securityRow(''), securityRow('NOT_A_CLASS')];
|
|
48
|
+
const equals = assetClassFilter(PositionFilterOperator.EQUALS);
|
|
49
|
+
const notEquals = assetClassFilter(PositionFilterOperator.NOT_EQUALS);
|
|
50
|
+
expect(() => filterRows(rows, equals)).not.toThrow();
|
|
51
|
+
expect(filterRows(rows, equals)).toEqual([]);
|
|
52
|
+
expect(() => filterRows(rows, notEquals)).not.toThrow();
|
|
53
|
+
expect(assetClasses(filterRows(rows, notEquals))).toEqual([null, '', 'NOT_A_CLASS']);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test('other fields compare exactly', () => {
|
|
57
|
+
expect(matches(FieldProto.PORTFOLIO_NAME, PositionFilterOperator.EQUALS, 'a', 'a')).toBe(true);
|
|
58
|
+
expect(matches(FieldProto.PORTFOLIO_NAME, PositionFilterOperator.EQUALS, 'a', 'b')).toBe(false);
|
|
59
|
+
expect(matches(FieldProto.PORTFOLIO_NAME, PositionFilterOperator.NOT_EQUALS, 'a', 'b')).toBe(true);
|
|
60
|
+
expect(matches(FieldProto.PORTFOLIO_NAME, PositionFilterOperator.NOT_EQUALS, 'a', 'a')).toBe(false);
|
|
61
|
+
expect(matches(FieldProto.MATURITY_DATE, PositionFilterOperator.MORE_THAN, 1, 2)).toBe(true);
|
|
62
|
+
expect(matches(FieldProto.MATURITY_DATE, PositionFilterOperator.MORE_THAN, 2, 1)).toBe(false);
|
|
63
|
+
expect(matches(FieldProto.PORTFOLIO_NAME, PositionFilterOperator.EQUALS, 'a', null)).toBe(false);
|
|
64
|
+
expect(matches(FieldProto.PORTFOLIO_NAME, PositionFilterOperator.NOT_EQUALS, 'a', null)).toBe(true);
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
describe('the published (compiled) position_matcher.js', () => {
|
|
69
|
+
// npm publish runs no tsc: the tracked .js is what consumers get.
|
|
70
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
71
|
+
const compiled = require('./position_matcher.js');
|
|
72
|
+
|
|
73
|
+
test('exports working matches and filterRows', () => {
|
|
74
|
+
expect(typeof compiled.matches).toBe('function');
|
|
75
|
+
expect(typeof compiled.filterRows).toBe('function');
|
|
76
|
+
expect(
|
|
77
|
+
compiled.matches(
|
|
78
|
+
FieldProto.ASSET_CLASS,
|
|
79
|
+
PositionFilterOperator.EQUALS,
|
|
80
|
+
'FIXED_INCOME',
|
|
81
|
+
'RATES',
|
|
82
|
+
),
|
|
83
|
+
).toBe(true);
|
|
84
|
+
expect(
|
|
85
|
+
compiled.matches(
|
|
86
|
+
FieldProto.ASSET_CLASS,
|
|
87
|
+
PositionFilterOperator.NOT_EQUALS,
|
|
88
|
+
'FIXED_INCOME',
|
|
89
|
+
'EQUITY',
|
|
90
|
+
),
|
|
91
|
+
).toBe(true);
|
|
92
|
+
const rows = ['RATES', 'EQUITY'].map(securityRow);
|
|
93
|
+
const kept = compiled.filterRows(rows, assetClassFilter(PositionFilterOperator.EQUALS));
|
|
94
|
+
expect(assetClasses(kept)).toEqual(['RATES']);
|
|
95
|
+
});
|
|
96
|
+
});
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { FieldProto } from '../../../fintekkers/models/position/field_pb';
|
|
2
|
+
import { PositionFilterOperator } from '../../../fintekkers/models/position/position_util_pb';
|
|
3
|
+
import { assetClassMatches } from '../security/product_hierarchy';
|
|
4
|
+
|
|
5
|
+
/** A single filter clause: the field, operator and filter value. */
|
|
6
|
+
export interface FilterSpec {
|
|
7
|
+
field: FieldProto;
|
|
8
|
+
operator: PositionFilterOperator;
|
|
9
|
+
value: any;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/** A row the matcher can read: the Security and Position wrappers qualify. */
|
|
13
|
+
export interface FilterableRow {
|
|
14
|
+
getField(field: FieldProto): any;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Single-field evaluator (LM-281), mirroring Java's PositionFilter.matches:
|
|
19
|
+
* ASSET_CLASS with EQUALS / NOT_EQUALS uses the shared assetClassMatches
|
|
20
|
+
* rule, so a group code such as FIXED_INCOME matches its members and labels;
|
|
21
|
+
* every other field compares exactly. A null or undefined stored value never
|
|
22
|
+
* throws: EQUALS and the ordering operators drop the row, NOT_EQUALS keeps
|
|
23
|
+
* it. An unknown operator matches nothing. Errors raised by row.getField
|
|
24
|
+
* propagate (a row represents an absent value by returning null).
|
|
25
|
+
*/
|
|
26
|
+
export function matches(
|
|
27
|
+
field: FieldProto,
|
|
28
|
+
operator: PositionFilterOperator,
|
|
29
|
+
filterValue: any,
|
|
30
|
+
storedValue: any,
|
|
31
|
+
): boolean {
|
|
32
|
+
if (storedValue === null || storedValue === undefined) {
|
|
33
|
+
return operator === PositionFilterOperator.NOT_EQUALS;
|
|
34
|
+
}
|
|
35
|
+
if (
|
|
36
|
+
field === FieldProto.ASSET_CLASS &&
|
|
37
|
+
(operator === PositionFilterOperator.EQUALS ||
|
|
38
|
+
operator === PositionFilterOperator.NOT_EQUALS)
|
|
39
|
+
) {
|
|
40
|
+
const equals = assetClassMatches(filterValue, storedValue);
|
|
41
|
+
return operator === PositionFilterOperator.EQUALS ? equals : !equals;
|
|
42
|
+
}
|
|
43
|
+
switch (operator) {
|
|
44
|
+
case PositionFilterOperator.EQUALS:
|
|
45
|
+
return storedValue === filterValue;
|
|
46
|
+
case PositionFilterOperator.NOT_EQUALS:
|
|
47
|
+
return storedValue !== filterValue;
|
|
48
|
+
case PositionFilterOperator.LESS_THAN:
|
|
49
|
+
return storedValue < filterValue;
|
|
50
|
+
case PositionFilterOperator.LESS_THAN_OR_EQUALS:
|
|
51
|
+
return storedValue <= filterValue;
|
|
52
|
+
case PositionFilterOperator.MORE_THAN:
|
|
53
|
+
return storedValue > filterValue;
|
|
54
|
+
case PositionFilterOperator.MORE_THAN_OR_EQUALS:
|
|
55
|
+
return storedValue >= filterValue;
|
|
56
|
+
default:
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Keep the rows matching every clause in `filters` (AND semantics, input
|
|
63
|
+
* order preserved).
|
|
64
|
+
*/
|
|
65
|
+
export function filterRows<T extends FilterableRow>(rows: T[], filters: FilterSpec[]): T[] {
|
|
66
|
+
return rows.filter((row) => {
|
|
67
|
+
return filters.every((spec) =>
|
|
68
|
+
matches(spec.field, spec.operator, spec.value, row.getField(spec.field)),
|
|
69
|
+
);
|
|
70
|
+
});
|
|
71
|
+
}
|