@forwardimpact/model 0.5.0 → 0.7.1
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 +2 -2
- package/package.json +17 -15
- package/{lib → src}/agent.js +25 -19
- package/{lib → src}/checklist.js +2 -10
- package/{lib → src}/derivation.js +17 -15
- package/{lib → src}/index.js +88 -17
- package/src/interview.js +1001 -0
- package/{lib → src}/job-cache.js +7 -7
- package/{lib → src}/matching.js +65 -41
- package/{lib → src}/modifiers.js +4 -4
- package/src/policies/composed.js +135 -0
- package/src/policies/filters.js +104 -0
- package/src/policies/index.js +160 -0
- package/src/policies/orderings.js +312 -0
- package/src/policies/predicates.js +177 -0
- package/src/policies/thresholds.js +317 -0
- package/src/profile.js +145 -0
- package/{lib → src}/progression.js +8 -13
- package/{lib → src}/toolkit.js +3 -3
- package/lib/interview.js +0 -539
- package/lib/profile.js +0 -262
- /package/{lib → src}/job.js +0 -0
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Orderings and Comparator Functions
|
|
3
|
+
*
|
|
4
|
+
* Canonical orderings for entity types and comparator functions for sorting.
|
|
5
|
+
*
|
|
6
|
+
* Naming conventions:
|
|
7
|
+
* - ORDER_* - canonical ordering arrays
|
|
8
|
+
* - compareBy* - comparator functions for Array.sort()
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import {
|
|
12
|
+
getSkillLevelIndex,
|
|
13
|
+
getBehaviourMaturityIndex,
|
|
14
|
+
getCapabilityOrder,
|
|
15
|
+
} from "@forwardimpact/schema/levels";
|
|
16
|
+
|
|
17
|
+
// =============================================================================
|
|
18
|
+
// Canonical Orderings
|
|
19
|
+
// =============================================================================
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Skill type ordering (T-shaped profile: core → broad)
|
|
23
|
+
* Primary skills first, then secondary, broad, and track-added skills.
|
|
24
|
+
*/
|
|
25
|
+
export const ORDER_SKILL_TYPE = ["primary", "secondary", "broad", "track"];
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Engineering lifecycle stages in execution order
|
|
29
|
+
*/
|
|
30
|
+
export const ORDER_STAGE = ["specify", "plan", "code", "review", "deploy"];
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Agent stage ordering (subset used for agent generation)
|
|
34
|
+
*/
|
|
35
|
+
export const ORDER_AGENT_STAGE = ["plan", "code", "review"];
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Stage-to-handoff mapping for checklist derivation
|
|
39
|
+
*
|
|
40
|
+
* Maps stage IDs to the stage whose `.ready` criteria should be shown
|
|
41
|
+
* before leaving that stage.
|
|
42
|
+
*/
|
|
43
|
+
export const CHECKLIST_STAGE_MAP = {
|
|
44
|
+
plan: "plan",
|
|
45
|
+
code: "code",
|
|
46
|
+
review: "review",
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
// =============================================================================
|
|
50
|
+
// Skill Comparators
|
|
51
|
+
// =============================================================================
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Compare skills by level descending (higher level first)
|
|
55
|
+
* @param {Object} a - First skill entry
|
|
56
|
+
* @param {Object} b - Second skill entry
|
|
57
|
+
* @returns {number} Comparison result
|
|
58
|
+
*/
|
|
59
|
+
export function compareByLevelDesc(a, b) {
|
|
60
|
+
return getSkillLevelIndex(b.level) - getSkillLevelIndex(a.level);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Compare skills by level ascending (lower level first)
|
|
65
|
+
* @param {Object} a - First skill entry
|
|
66
|
+
* @param {Object} b - Second skill entry
|
|
67
|
+
* @returns {number} Comparison result
|
|
68
|
+
*/
|
|
69
|
+
export function compareByLevelAsc(a, b) {
|
|
70
|
+
return getSkillLevelIndex(a.level) - getSkillLevelIndex(b.level);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Compare skills by type (primary first)
|
|
75
|
+
* @param {Object} a - First skill entry
|
|
76
|
+
* @param {Object} b - Second skill entry
|
|
77
|
+
* @returns {number} Comparison result
|
|
78
|
+
*/
|
|
79
|
+
export function compareByType(a, b) {
|
|
80
|
+
return ORDER_SKILL_TYPE.indexOf(a.type) - ORDER_SKILL_TYPE.indexOf(b.type);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Compare skills by name alphabetically
|
|
85
|
+
* @param {Object} a - First skill entry
|
|
86
|
+
* @param {Object} b - Second skill entry
|
|
87
|
+
* @returns {number} Comparison result
|
|
88
|
+
*/
|
|
89
|
+
export function compareByName(a, b) {
|
|
90
|
+
const nameA = a.skillName || a.name;
|
|
91
|
+
const nameB = b.skillName || b.name;
|
|
92
|
+
return nameA.localeCompare(nameB);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Compare skills by level (desc), then type (asc), then name (asc)
|
|
97
|
+
*
|
|
98
|
+
* Standard priority ordering for skill display:
|
|
99
|
+
* - Higher levels first
|
|
100
|
+
* - Within same level, primary before secondary before broad
|
|
101
|
+
* - Within same type, alphabetical by name
|
|
102
|
+
*
|
|
103
|
+
* @param {Object} a - First skill entry
|
|
104
|
+
* @param {Object} b - Second skill entry
|
|
105
|
+
* @returns {number} Comparison result
|
|
106
|
+
*/
|
|
107
|
+
export function compareBySkillPriority(a, b) {
|
|
108
|
+
// Level descending (higher level first)
|
|
109
|
+
const levelDiff = getSkillLevelIndex(b.level) - getSkillLevelIndex(a.level);
|
|
110
|
+
if (levelDiff !== 0) return levelDiff;
|
|
111
|
+
|
|
112
|
+
// Type ascending (primary first)
|
|
113
|
+
const typeA = ORDER_SKILL_TYPE.indexOf(a.type);
|
|
114
|
+
const typeB = ORDER_SKILL_TYPE.indexOf(b.type);
|
|
115
|
+
if (typeA !== typeB) return typeA - typeB;
|
|
116
|
+
|
|
117
|
+
// Name ascending (alphabetical)
|
|
118
|
+
const nameA = a.skillName || a.name;
|
|
119
|
+
const nameB = b.skillName || b.name;
|
|
120
|
+
return nameA.localeCompare(nameB);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Compare skills by type (asc), then name (asc)
|
|
125
|
+
*
|
|
126
|
+
* Standard ordering for job skill matrix display:
|
|
127
|
+
* - Primary skills first, then secondary, then broad, then track
|
|
128
|
+
* - Within same type, alphabetical by name
|
|
129
|
+
*
|
|
130
|
+
* @param {Object} a - First skill entry
|
|
131
|
+
* @param {Object} b - Second skill entry
|
|
132
|
+
* @returns {number} Comparison result
|
|
133
|
+
*/
|
|
134
|
+
export function compareByTypeAndName(a, b) {
|
|
135
|
+
const typeCompare = compareByType(a, b);
|
|
136
|
+
if (typeCompare !== 0) return typeCompare;
|
|
137
|
+
return compareByName(a, b);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// =============================================================================
|
|
141
|
+
// Behaviour Comparators
|
|
142
|
+
// =============================================================================
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Compare behaviours by maturity descending (higher maturity first)
|
|
146
|
+
* @param {Object} a - First behaviour entry
|
|
147
|
+
* @param {Object} b - Second behaviour entry
|
|
148
|
+
* @returns {number} Comparison result
|
|
149
|
+
*/
|
|
150
|
+
export function compareByMaturityDesc(a, b) {
|
|
151
|
+
return (
|
|
152
|
+
getBehaviourMaturityIndex(b.maturity) -
|
|
153
|
+
getBehaviourMaturityIndex(a.maturity)
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Compare behaviours by maturity ascending (lower maturity first)
|
|
159
|
+
* @param {Object} a - First behaviour entry
|
|
160
|
+
* @param {Object} b - Second behaviour entry
|
|
161
|
+
* @returns {number} Comparison result
|
|
162
|
+
*/
|
|
163
|
+
export function compareByMaturityAsc(a, b) {
|
|
164
|
+
return (
|
|
165
|
+
getBehaviourMaturityIndex(a.maturity) -
|
|
166
|
+
getBehaviourMaturityIndex(b.maturity)
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Compare behaviours by name alphabetically
|
|
172
|
+
* @param {Object} a - First behaviour entry
|
|
173
|
+
* @param {Object} b - Second behaviour entry
|
|
174
|
+
* @returns {number} Comparison result
|
|
175
|
+
*/
|
|
176
|
+
export function compareByBehaviourName(a, b) {
|
|
177
|
+
const nameA = a.behaviourName || a.name;
|
|
178
|
+
const nameB = b.behaviourName || b.name;
|
|
179
|
+
return nameA.localeCompare(nameB);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Compare behaviours by maturity (desc), then name (asc)
|
|
184
|
+
*
|
|
185
|
+
* Standard priority ordering for behaviour display:
|
|
186
|
+
* - Higher maturity first
|
|
187
|
+
* - Within same maturity, alphabetical by name
|
|
188
|
+
*
|
|
189
|
+
* @param {Object} a - First behaviour entry
|
|
190
|
+
* @param {Object} b - Second behaviour entry
|
|
191
|
+
* @returns {number} Comparison result
|
|
192
|
+
*/
|
|
193
|
+
export function compareByBehaviourPriority(a, b) {
|
|
194
|
+
const maturityDiff =
|
|
195
|
+
getBehaviourMaturityIndex(b.maturity) -
|
|
196
|
+
getBehaviourMaturityIndex(a.maturity);
|
|
197
|
+
if (maturityDiff !== 0) return maturityDiff;
|
|
198
|
+
return compareByBehaviourName(a, b);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// =============================================================================
|
|
202
|
+
// Capability Comparators
|
|
203
|
+
// =============================================================================
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Create a comparator for sorting by capability ordinal rank
|
|
207
|
+
*
|
|
208
|
+
* The returned comparator uses ordinalRank from loaded capability data,
|
|
209
|
+
* making the ordering data-driven rather than hardcoded.
|
|
210
|
+
*
|
|
211
|
+
* @param {Object[]} capabilities - Loaded capabilities array
|
|
212
|
+
* @returns {(a: Object, b: Object) => number} Comparator function
|
|
213
|
+
*/
|
|
214
|
+
export function compareByCapability(capabilities) {
|
|
215
|
+
const order = getCapabilityOrder(capabilities);
|
|
216
|
+
return (a, b) => {
|
|
217
|
+
const capA = a.capability || "";
|
|
218
|
+
const capB = b.capability || "";
|
|
219
|
+
return order.indexOf(capA) - order.indexOf(capB);
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Sort skills by capability (display order), then by name
|
|
225
|
+
*
|
|
226
|
+
* @param {Object[]} skills - Array of skills to sort
|
|
227
|
+
* @param {Object[]} capabilities - Loaded capabilities array
|
|
228
|
+
* @returns {Object[]} Sorted array (new array, does not mutate input)
|
|
229
|
+
*/
|
|
230
|
+
export function sortSkillsByCapability(skills, capabilities) {
|
|
231
|
+
const capabilityComparator = compareByCapability(capabilities);
|
|
232
|
+
return [...skills].sort((a, b) => {
|
|
233
|
+
const capCompare = capabilityComparator(a, b);
|
|
234
|
+
if (capCompare !== 0) return capCompare;
|
|
235
|
+
const nameA = a.skillName || a.name;
|
|
236
|
+
const nameB = b.skillName || b.name;
|
|
237
|
+
return nameA.localeCompare(nameB);
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// =============================================================================
|
|
242
|
+
// Generic Comparator Factory
|
|
243
|
+
// =============================================================================
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Create comparator from an ordering array
|
|
247
|
+
*
|
|
248
|
+
* @param {string[]} order - Canonical order
|
|
249
|
+
* @param {(item: Object) => string} accessor - Extract value to compare
|
|
250
|
+
* @returns {(a: Object, b: Object) => number}
|
|
251
|
+
*/
|
|
252
|
+
export function compareByOrder(order, accessor) {
|
|
253
|
+
return (a, b) => order.indexOf(accessor(a)) - order.indexOf(accessor(b));
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Chain multiple comparators together
|
|
258
|
+
*
|
|
259
|
+
* Returns first non-zero result, or 0 if all comparators return 0.
|
|
260
|
+
*
|
|
261
|
+
* @param {...Function} comparators - Comparator functions
|
|
262
|
+
* @returns {(a: Object, b: Object) => number}
|
|
263
|
+
*/
|
|
264
|
+
export function chainComparators(...comparators) {
|
|
265
|
+
return (a, b) => {
|
|
266
|
+
for (const comparator of comparators) {
|
|
267
|
+
const result = comparator(a, b);
|
|
268
|
+
if (result !== 0) return result;
|
|
269
|
+
}
|
|
270
|
+
return 0;
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// =============================================================================
|
|
275
|
+
// Skill Change Comparators (for progression)
|
|
276
|
+
// =============================================================================
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Compare skill changes by change magnitude (largest first), then type, then name
|
|
280
|
+
*
|
|
281
|
+
* Used for career progression analysis where biggest changes are most important.
|
|
282
|
+
*
|
|
283
|
+
* @param {Object} a - First skill change
|
|
284
|
+
* @param {Object} b - Second skill change
|
|
285
|
+
* @returns {number} Comparison result
|
|
286
|
+
*/
|
|
287
|
+
export function compareBySkillChange(a, b) {
|
|
288
|
+
// Change descending (largest improvement first)
|
|
289
|
+
if (b.change !== a.change) return b.change - a.change;
|
|
290
|
+
|
|
291
|
+
// Type ascending (primary first)
|
|
292
|
+
const typeA = ORDER_SKILL_TYPE.indexOf(a.type);
|
|
293
|
+
const typeB = ORDER_SKILL_TYPE.indexOf(b.type);
|
|
294
|
+
if (typeA !== typeB) return typeA - typeB;
|
|
295
|
+
|
|
296
|
+
// Name ascending
|
|
297
|
+
return a.name.localeCompare(b.name);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Compare behaviour changes by change magnitude (largest first), then name
|
|
302
|
+
*
|
|
303
|
+
* Used for career progression analysis.
|
|
304
|
+
*
|
|
305
|
+
* @param {Object} a - First behaviour change
|
|
306
|
+
* @param {Object} b - Second behaviour change
|
|
307
|
+
* @returns {number} Comparison result
|
|
308
|
+
*/
|
|
309
|
+
export function compareByBehaviourChange(a, b) {
|
|
310
|
+
if (b.change !== a.change) return b.change - a.change;
|
|
311
|
+
return a.name.localeCompare(b.name);
|
|
312
|
+
}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Entry-Level Predicate Functions
|
|
3
|
+
*
|
|
4
|
+
* Pure predicates that operate on single skill/behaviour matrix entries.
|
|
5
|
+
* Each predicate takes one entry and returns boolean.
|
|
6
|
+
*
|
|
7
|
+
* Naming conventions:
|
|
8
|
+
* - is* - checks a boolean condition
|
|
9
|
+
* - has* - checks presence of a value
|
|
10
|
+
* - allOf/anyOf/not - combinators
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { getSkillLevelIndex } from "@forwardimpact/schema/levels";
|
|
14
|
+
|
|
15
|
+
// =============================================================================
|
|
16
|
+
// Identity Predicates
|
|
17
|
+
// =============================================================================
|
|
18
|
+
|
|
19
|
+
/** Always returns true (identity predicate for optional filtering) */
|
|
20
|
+
export const isAny = () => true;
|
|
21
|
+
|
|
22
|
+
/** Always returns false (null predicate) */
|
|
23
|
+
export const isNone = () => false;
|
|
24
|
+
|
|
25
|
+
// =============================================================================
|
|
26
|
+
// Human-Only Predicates
|
|
27
|
+
// =============================================================================
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Returns true if skill is marked as human-only
|
|
31
|
+
* @param {Object} entry - Skill matrix entry
|
|
32
|
+
* @returns {boolean}
|
|
33
|
+
*/
|
|
34
|
+
export const isHumanOnly = (entry) => entry.isHumanOnly === true;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Returns true if skill is NOT human-only (agent-eligible)
|
|
38
|
+
* @param {Object} entry - Skill matrix entry
|
|
39
|
+
* @returns {boolean}
|
|
40
|
+
*/
|
|
41
|
+
export const isAgentEligible = (entry) => !entry.isHumanOnly;
|
|
42
|
+
|
|
43
|
+
// =============================================================================
|
|
44
|
+
// Skill Type Predicates
|
|
45
|
+
// =============================================================================
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Returns true if skill type is primary
|
|
49
|
+
* @param {Object} entry - Skill matrix entry
|
|
50
|
+
* @returns {boolean}
|
|
51
|
+
*/
|
|
52
|
+
export const isPrimary = (entry) => entry.type === "primary";
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Returns true if skill type is secondary
|
|
56
|
+
* @param {Object} entry - Skill matrix entry
|
|
57
|
+
* @returns {boolean}
|
|
58
|
+
*/
|
|
59
|
+
export const isSecondary = (entry) => entry.type === "secondary";
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Returns true if skill type is broad
|
|
63
|
+
* @param {Object} entry - Skill matrix entry
|
|
64
|
+
* @returns {boolean}
|
|
65
|
+
*/
|
|
66
|
+
export const isBroad = (entry) => entry.type === "broad";
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Returns true if skill type is track
|
|
70
|
+
* @param {Object} entry - Skill matrix entry
|
|
71
|
+
* @returns {boolean}
|
|
72
|
+
*/
|
|
73
|
+
export const isTrack = (entry) => entry.type === "track";
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Returns true if skill is primary or secondary (core skills)
|
|
77
|
+
* @param {Object} entry - Skill matrix entry
|
|
78
|
+
* @returns {boolean}
|
|
79
|
+
*/
|
|
80
|
+
export const isCore = (entry) =>
|
|
81
|
+
entry.type === "primary" || entry.type === "secondary";
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Returns true if skill is broad or track (supporting skills)
|
|
85
|
+
* @param {Object} entry - Skill matrix entry
|
|
86
|
+
* @returns {boolean}
|
|
87
|
+
*/
|
|
88
|
+
export const isSupporting = (entry) =>
|
|
89
|
+
entry.type === "broad" || entry.type === "track";
|
|
90
|
+
|
|
91
|
+
// =============================================================================
|
|
92
|
+
// Skill Level Predicates
|
|
93
|
+
// =============================================================================
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Create predicate for skills at or above a minimum level
|
|
97
|
+
* @param {string} minLevel - Minimum skill level
|
|
98
|
+
* @returns {(entry: Object) => boolean}
|
|
99
|
+
*/
|
|
100
|
+
export function hasMinLevel(minLevel) {
|
|
101
|
+
const minIndex = getSkillLevelIndex(minLevel);
|
|
102
|
+
return (entry) => getSkillLevelIndex(entry.level) >= minIndex;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Create predicate for skills at exactly a specific level
|
|
107
|
+
* @param {string} level - Exact skill level to match
|
|
108
|
+
* @returns {(entry: Object) => boolean}
|
|
109
|
+
*/
|
|
110
|
+
export function hasLevel(level) {
|
|
111
|
+
const targetIndex = getSkillLevelIndex(level);
|
|
112
|
+
return (entry) => getSkillLevelIndex(entry.level) === targetIndex;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Create predicate for skills below a level threshold
|
|
117
|
+
* @param {string} maxLevel - Level that must NOT be reached
|
|
118
|
+
* @returns {(entry: Object) => boolean}
|
|
119
|
+
*/
|
|
120
|
+
export function hasBelowLevel(maxLevel) {
|
|
121
|
+
const maxIndex = getSkillLevelIndex(maxLevel);
|
|
122
|
+
return (entry) => getSkillLevelIndex(entry.level) < maxIndex;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// =============================================================================
|
|
126
|
+
// Capability Predicates
|
|
127
|
+
// =============================================================================
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Create predicate for skills in a specific capability
|
|
131
|
+
* @param {string} capability - Capability to match
|
|
132
|
+
* @returns {(entry: Object) => boolean}
|
|
133
|
+
*/
|
|
134
|
+
export function isInCapability(capability) {
|
|
135
|
+
return (entry) => entry.capability === capability;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Create predicate for skills in any of the specified capabilities
|
|
140
|
+
* @param {string[]} capabilities - Capabilities to match
|
|
141
|
+
* @returns {(entry: Object) => boolean}
|
|
142
|
+
*/
|
|
143
|
+
export function isInAnyCapability(capabilities) {
|
|
144
|
+
const set = new Set(capabilities);
|
|
145
|
+
return (entry) => set.has(entry.capability);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// =============================================================================
|
|
149
|
+
// Combinators
|
|
150
|
+
// =============================================================================
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Compose predicates with AND logic (all must pass)
|
|
154
|
+
* @param {...Function} predicates - Predicates to combine
|
|
155
|
+
* @returns {(entry: Object) => boolean}
|
|
156
|
+
*/
|
|
157
|
+
export function allOf(...predicates) {
|
|
158
|
+
return (entry) => predicates.every((p) => p(entry));
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Compose predicates with OR logic (any must pass)
|
|
163
|
+
* @param {...Function} predicates - Predicates to combine
|
|
164
|
+
* @returns {(entry: Object) => boolean}
|
|
165
|
+
*/
|
|
166
|
+
export function anyOf(...predicates) {
|
|
167
|
+
return (entry) => predicates.some((p) => p(entry));
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Negate a predicate
|
|
172
|
+
* @param {Function} predicate - Predicate to negate
|
|
173
|
+
* @returns {(entry: Object) => boolean}
|
|
174
|
+
*/
|
|
175
|
+
export function not(predicate) {
|
|
176
|
+
return (entry) => !predicate(entry);
|
|
177
|
+
}
|