@legalplace/conditions-runner 2.2.20 → 2.3.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.
@@ -1,7 +1,185 @@
1
- import { Types } from "@legalplace/referencesparser";
1
+ import { Types, extractConditionDataMap, lowerScope } from "@legalplace/referencesparser";
2
+ import type { ConditionV3 } from "@legalplace/models-v3-types";
2
3
  import ConditionsRunner from "./ConditionsRunner";
4
+ import {
5
+ CONDITION_COMPARATOR_EMPTY_SENTINEL,
6
+ resolveConditionComparators,
7
+ } from "./ComparatorResolver";
3
8
  import dummyInputs from "./fixtures/inputs-type.json";
4
9
  import dummyReferences from "./fixtures/references.json";
10
+ import type { ConditionDataMap } from "./types/conditions.d";
11
+
12
+ const emptyOptionRelations = {
13
+ options: [],
14
+ variables: [],
15
+ sections: {},
16
+ documents: [] as string[],
17
+ prefillers: [],
18
+ validators: { options: [], variables: [] },
19
+ parents: [] as number[],
20
+ children: { options: [] as number[], variables: [] as number[] },
21
+ dependants: [] as number[],
22
+ };
23
+
24
+ /**
25
+ * Builds a minimal references graph: multiple #2 with radio children 3/4.
26
+ */
27
+ const buildRelatedDocumentReferences = (): Types.ReferencesType =>
28
+ ({
29
+ options: {
30
+ 2: {
31
+ meta: {
32
+ id: 2,
33
+ type: "static",
34
+ label: "Partner",
35
+ step: "*",
36
+ multiple: { enabled: true },
37
+ },
38
+ options: [3, 4],
39
+ variables: [],
40
+ },
41
+ 3: {
42
+ meta: { id: 3, type: "radio", label: "Yes", step: "*" },
43
+ options: [],
44
+ variables: [],
45
+ },
46
+ 4: {
47
+ meta: { id: 4, type: "radio", label: "No", step: "*" },
48
+ options: [],
49
+ variables: [],
50
+ },
51
+ },
52
+ variables: {},
53
+ outputs: {},
54
+ boxes: {},
55
+ documents: {
56
+ annex: {
57
+ name: "Annex",
58
+ params: { multiple: { enabled: true, repeatOption: 2 } },
59
+ },
60
+ },
61
+ sections: {},
62
+ relations: {
63
+ options: {
64
+ 2: {
65
+ ...emptyOptionRelations,
66
+ children: { options: [3, 4], variables: [] },
67
+ },
68
+ 3: { ...emptyOptionRelations, parents: [2] },
69
+ 4: { ...emptyOptionRelations, parents: [2] },
70
+ },
71
+ variables: {},
72
+ sections: {},
73
+ },
74
+ conditions: {
75
+ options: {},
76
+ variables: {},
77
+ documents: {},
78
+ sections: { main: {} },
79
+ prefillers: {},
80
+ validators: { options: {}, variables: {} },
81
+ },
82
+ defaults: {},
83
+ evaluations: {},
84
+ optionTags: {},
85
+ variableTags: {},
86
+ } as unknown as Types.ReferencesType);
87
+
88
+ /**
89
+ * Multiple #10 hosting sibling variables 3 and 4 (same root parent).
90
+ */
91
+ const buildSiblingVariableReferences = (): Types.ReferencesType =>
92
+ ({
93
+ options: {
94
+ 10: {
95
+ meta: {
96
+ id: 10,
97
+ type: "static",
98
+ label: "Repeat",
99
+ step: "*",
100
+ multiple: { enabled: true },
101
+ },
102
+ options: [],
103
+ variables: [3, 4],
104
+ },
105
+ 20: {
106
+ meta: { id: 20, type: "static", label: "Other", step: "*" },
107
+ options: [],
108
+ variables: [5],
109
+ },
110
+ },
111
+ variables: {
112
+ 3: { meta: { id: 3, type: "string", label: "Left" }, options: [] },
113
+ 4: { meta: { id: 4, type: "string", label: "Right" }, options: [] },
114
+ 5: { meta: { id: 5, type: "string", label: "Elsewhere" }, options: [] },
115
+ },
116
+ outputs: {},
117
+ boxes: {},
118
+ documents: {},
119
+ sections: {},
120
+ relations: {
121
+ options: {
122
+ 10: {
123
+ ...emptyOptionRelations,
124
+ children: { options: [], variables: [3, 4] },
125
+ },
126
+ 20: {
127
+ ...emptyOptionRelations,
128
+ children: { options: [], variables: [5] },
129
+ },
130
+ },
131
+ variables: {
132
+ 3: { ...emptyOptionRelations, parents: [10] },
133
+ 4: { ...emptyOptionRelations, parents: [10] },
134
+ 5: { ...emptyOptionRelations, parents: [20] },
135
+ },
136
+ sections: {},
137
+ },
138
+ conditions: {
139
+ options: {},
140
+ variables: {},
141
+ documents: {},
142
+ sections: { main: {} },
143
+ prefillers: {},
144
+ validators: { options: {}, variables: {} },
145
+ },
146
+ defaults: {},
147
+ evaluations: {},
148
+ optionTags: {},
149
+ variableTags: {},
150
+ } as unknown as Types.ReferencesType);
151
+
152
+ const relatedOvc = {
153
+ options: {
154
+ 2: [true, true],
155
+ 3: [true, false],
156
+ 4: [false, true],
157
+ },
158
+ variables: {},
159
+ };
160
+
161
+ const siblingOvc = {
162
+ options: {
163
+ 10: [true, true, true],
164
+ 20: [true],
165
+ },
166
+ variables: {
167
+ 3: ["same", "b", "c"],
168
+ 4: ["same", "y", "c"],
169
+ 5: ["solo-a", "solo-b"],
170
+ },
171
+ };
172
+
173
+ /**
174
+ * Builds executeCondition input from an authored ConditionV3 (scope on the node).
175
+ * @param conditions Authored condition tree
176
+ */
177
+ const mappedCondition = (
178
+ conditions: ConditionV3
179
+ ): { dataMap: ConditionDataMap; conditions: ConditionV3 } => ({
180
+ dataMap: extractConditionDataMap(conditions),
181
+ conditions,
182
+ });
5
183
 
6
184
  describe("ConditionsRunner class", () => {
7
185
  it("should not raise error when instanciating class", () => {
@@ -12,4 +190,186 @@ describe("ConditionsRunner class", () => {
12
190
  )
13
191
  ).toBeDefined();
14
192
  });
193
+
194
+ it("slices related document conditions to the current occurrence", () => {
195
+ const runner = new ConditionsRunner(
196
+ buildRelatedDocumentReferences(),
197
+ relatedOvc
198
+ );
199
+ const condition = mappedCondition({
200
+ selected: [{ var: "o.3" }],
201
+ scope: "related",
202
+ });
203
+
204
+ expect(runner.executeCondition(condition, 2, 0, "documents")).toBe(true);
205
+ expect(runner.executeCondition(condition, 2, 1, "documents")).toBe(false);
206
+ });
207
+
208
+ it("evaluates selected + first against the first row after id/index slicing", () => {
209
+ const runner = new ConditionsRunner(
210
+ buildRelatedDocumentReferences(),
211
+ relatedOvc
212
+ );
213
+ const condition = mappedCondition({
214
+ selected: [{ var: "o.3" }],
215
+ scope: "first",
216
+ });
217
+
218
+ expect(runner.executeCondition(condition, 2, 1, "documents")).toBe(true);
219
+ });
220
+
221
+ it("related sibling [var:N] uses the current occurrence index", () => {
222
+ const runner = new ConditionsRunner(
223
+ buildSiblingVariableReferences(),
224
+ siblingOvc
225
+ );
226
+ const condition = mappedCondition({
227
+ "=": [{ var: "v.3" }, "[var:4]"],
228
+ scope: "related",
229
+ });
230
+
231
+ // index 0: "same" === "same"
232
+ expect(runner.executeCondition(condition, 3, 0, "variables")).toBe(true);
233
+ // index 1: "b" === "y"
234
+ expect(runner.executeCondition(condition, 3, 1, "variables")).toBe(false);
235
+ // index 2: "c" === "c" (not "same" from occurrence 0)
236
+ expect(runner.executeCondition(condition, 3, 2, "variables")).toBe(true);
237
+ });
238
+
239
+ it("first-scoped sibling [var:N] uses occurrence 0", () => {
240
+ const runner = new ConditionsRunner(
241
+ buildSiblingVariableReferences(),
242
+ siblingOvc
243
+ );
244
+ const condition = mappedCondition({
245
+ "=": [{ var: "v.3" }, "[var:4]"],
246
+ scope: "first",
247
+ });
248
+
249
+ // Outer index 2 still binds both sides to occurrence 0 → "same" === "same"
250
+ expect(runner.executeCondition(condition, 3, 2, "variables")).toBe(true);
251
+ });
252
+
253
+ it("all-scope sibling [var:N] is pairwise true when every row matches", () => {
254
+ const runner = new ConditionsRunner(buildSiblingVariableReferences(), {
255
+ options: { 10: [true, true], 20: [true] },
256
+ variables: {
257
+ 3: [1, 2],
258
+ 4: [2, 3],
259
+ 5: ["solo-a"],
260
+ },
261
+ });
262
+ const condition = mappedCondition({
263
+ "<": [{ var: "v.3" }, "[var:4]"],
264
+ scope: "all",
265
+ });
266
+
267
+ expect(runner.executeCondition(condition, 3, 0, "options")).toBe(true);
268
+ });
269
+
270
+ it("all-scope sibling [var:N] is pairwise false when a row fails", () => {
271
+ const runner = new ConditionsRunner(buildSiblingVariableReferences(), {
272
+ options: { 10: [true, true], 20: [true] },
273
+ variables: {
274
+ 3: [1, 5],
275
+ 4: [2, 3],
276
+ 5: ["solo-a"],
277
+ },
278
+ });
279
+ const condition = mappedCondition({
280
+ "<": [{ var: "v.3" }, "[var:4]"],
281
+ scope: "all",
282
+ });
283
+
284
+ expect(runner.executeCondition(condition, 3, 0, "options")).toBe(false);
285
+ });
286
+
287
+ it("any-scope sibling [var:N] succeeds on a later pairwise row", () => {
288
+ const runner = new ConditionsRunner(buildSiblingVariableReferences(), {
289
+ options: { 10: [true, true], 20: [true] },
290
+ variables: {
291
+ 3: [5, 1],
292
+ 4: [2, 10],
293
+ 5: ["solo-a"],
294
+ },
295
+ });
296
+ const condition = mappedCondition({
297
+ "<": [{ var: "v.3" }, "[var:4]"],
298
+ scope: "any",
299
+ });
300
+
301
+ expect(runner.executeCondition(condition, 5, 0, "options")).toBe(true);
302
+ });
303
+
304
+ it("joins non-sibling [var:N] values instead of slicing by row", () => {
305
+ const references = buildSiblingVariableReferences();
306
+ const resolved = resolveConditionComparators(
307
+ lowerScope({
308
+ "=": [{ var: "v.3" }, "[var:5]"],
309
+ scope: "related",
310
+ }),
311
+ { optionId: 10, index: 2, conditionVariableId: 3 },
312
+ { references, ovc: siblingOvc }
313
+ );
314
+
315
+ expect(resolved).toEqual({
316
+ "=": [{ var: "v.3" }, "solo-a, solo-b"],
317
+ });
318
+ });
319
+ });
320
+
321
+ describe("resolveConditionComparators", () => {
322
+ const references = buildSiblingVariableReferences();
323
+ const deps = { references, ovc: siblingOvc };
324
+
325
+ it("related leaf: sibling [var:N] uses the current occurrence index", () => {
326
+ const resolved = resolveConditionComparators(
327
+ lowerScope({
328
+ "=": [{ var: "v.3" }, "[var:4]"],
329
+ scope: "related",
330
+ }),
331
+ { optionId: 10, index: 2, conditionVariableId: 3 },
332
+ deps
333
+ );
334
+
335
+ expect(resolved).toEqual({
336
+ "=": [{ var: "v.3" }, "c"],
337
+ });
338
+ });
339
+
340
+ it("first-scoped leaf: sibling [var:N] uses occurrence 0", () => {
341
+ const resolved = resolveConditionComparators(
342
+ lowerScope({
343
+ "=": [{ var: "v.3" }, "[var:4]"],
344
+ scope: "first",
345
+ }),
346
+ { optionId: 10, index: 2, conditionVariableId: 3 },
347
+ deps
348
+ );
349
+
350
+ expect(resolved).toEqual({
351
+ "=-first": [{ var: "v.3" }, "same"],
352
+ });
353
+ });
354
+
355
+ it("uses empty sentinel for missing sibling values", () => {
356
+ const resolved = resolveConditionComparators(
357
+ lowerScope({
358
+ "=": [{ var: "v.3" }, "[var:4]"],
359
+ scope: "related",
360
+ }),
361
+ { optionId: 10, index: 5, conditionVariableId: 3 },
362
+ {
363
+ references,
364
+ ovc: {
365
+ options: siblingOvc.options,
366
+ variables: { 3: ["a"], 4: ["b"], 5: [] },
367
+ },
368
+ }
369
+ );
370
+
371
+ expect(resolved).toEqual({
372
+ "=": [{ var: "v.3" }, CONDITION_COMPARATOR_EMPTY_SENTINEL],
373
+ });
374
+ });
15
375
  });
@@ -1,10 +1,17 @@
1
1
  /* eslint-disable max-len */
2
2
  import LpLogic from "@legalplace/lplogic";
3
3
  import type { Types } from "@legalplace/referencesparser";
4
+ import { lowerScope } from "@legalplace/referencesparser";
4
5
  import type { ConditionV3 } from "@legalplace/models-v3-types";
5
6
  import type InputsType from "@legalplace/types/dist/inputs";
6
7
  import DataPopulator from "./DataPopulator";
7
8
  import type { ConditionDataMap } from "./types/conditions.d";
9
+ import { aggregateScopedLeaves } from "./ScopedLeafEvaluator";
10
+ import {
11
+ getConditionVariableOptionId,
12
+ resolveConditionComparators,
13
+ type ComparatorResolverContext,
14
+ } from "./ComparatorResolver";
8
15
 
9
16
  class ConditionsRunner {
10
17
  private references: Types.ReferencesType;
@@ -171,10 +178,64 @@ class ConditionsRunner {
171
178
  ).getData();
172
179
  }
173
180
 
174
- const result = LpLogic(condition.conditions, currentData);
181
+ const resolverContext = this.buildComparatorResolverContext(
182
+ id,
183
+ index,
184
+ conditionType
185
+ );
186
+ const deps = { references: this.references, ovc: this.ovc };
187
+
188
+ // Pairwise sibling `[var:N]` (all/any/first) must run before string
189
+ // substitution collapses the tag to a single value.
190
+ const aggregatedConditions = aggregateScopedLeaves(
191
+ lowerScope(condition.conditions),
192
+ new DataPopulator(
193
+ this,
194
+ this.references,
195
+ this.ovc,
196
+ condition.dataMap
197
+ ).getData(),
198
+ resolverContext,
199
+ deps
200
+ );
201
+
202
+ const resolvedConditions = resolveConditionComparators(
203
+ aggregatedConditions,
204
+ resolverContext,
205
+ deps
206
+ );
207
+
208
+ const result = LpLogic(resolvedConditions, currentData);
175
209
  this.executeConditionMemo[memoizedKey] = result;
176
210
  return result;
177
211
  }
212
+
213
+ /**
214
+ * Builds option/index context for comparator `[var:N]` substitution.
215
+ */
216
+ private buildComparatorResolverContext(
217
+ id: number,
218
+ index: number,
219
+ conditionType: string
220
+ ): ComparatorResolverContext {
221
+ if (conditionType === "options" || conditionType === "optionValidator") {
222
+ return { optionId: id, index, conditionOptionId: id };
223
+ }
224
+
225
+ if (
226
+ conditionType === "variables" ||
227
+ conditionType === "prefillers" ||
228
+ conditionType === "variableValidator"
229
+ ) {
230
+ return {
231
+ optionId: getConditionVariableOptionId(this.references, id),
232
+ index,
233
+ conditionVariableId: id,
234
+ };
235
+ }
236
+
237
+ return { optionId: id, index };
238
+ }
178
239
  }
179
240
 
180
241
  export default ConditionsRunner;
@@ -0,0 +1,236 @@
1
+ import LpLogic from "@legalplace/lplogic";
2
+ import type { ConditionData } from "./types/conditions.d";
3
+ import {
4
+ areVariablesSiblings,
5
+ extractLhsVariableId,
6
+ extractVarTagIds,
7
+ getConditionVariableOptionId,
8
+ resolveComparatorString,
9
+ SCOPED_OPERATOR_REGEX,
10
+ type ComparatorResolverContext,
11
+ type ComparatorResolverDeps,
12
+ } from "./ComparatorResolver";
13
+
14
+ const UI_METADATA_KEYS = new Set(["scope", "groupMeta"]);
15
+
16
+ /**
17
+ * Whether the comparator references a same-multiple sibling of the LHS variable.
18
+ */
19
+ const hasSiblingComparatorTag = (
20
+ deps: ComparatorResolverDeps,
21
+ comparator: string,
22
+ lhsVariableId: number
23
+ ): boolean =>
24
+ extractVarTagIds(comparator).some((variableId) =>
25
+ areVariablesSiblings(deps.references, variableId, lhsVariableId)
26
+ );
27
+
28
+ /**
29
+ * Whether this leaf must be evaluated pairwise (one RHS index per LHS row).
30
+ */
31
+ const shouldEvaluatePairwiseSiblingLeaf = (
32
+ operatorKey: string,
33
+ args: unknown[],
34
+ context: ComparatorResolverContext,
35
+ deps: ComparatorResolverDeps
36
+ ): boolean => {
37
+ const comparator = args[1];
38
+ if (typeof comparator !== "string") return false;
39
+
40
+ const lhsVariableId = extractLhsVariableId(args[0]);
41
+ if (lhsVariableId === undefined) return false;
42
+ if (!hasSiblingComparatorTag(deps, comparator, lhsVariableId)) return false;
43
+
44
+ return (
45
+ context.conditionVariableId === undefined ||
46
+ SCOPED_OPERATOR_REGEX.test(operatorKey)
47
+ );
48
+ };
49
+
50
+ /**
51
+ * Builds leaf context bound to a specific LHS variable occurrence.
52
+ */
53
+ const withLhsLeafContext = (
54
+ context: ComparatorResolverContext,
55
+ deps: ComparatorResolverDeps,
56
+ lhsVariableId: number,
57
+ index: number
58
+ ): ComparatorResolverContext => ({
59
+ ...context,
60
+ index,
61
+ evalVariableId: lhsVariableId,
62
+ optionId: getConditionVariableOptionId(deps.references, lhsVariableId),
63
+ });
64
+
65
+ /**
66
+ * Evaluates one occurrence of a comparison leaf.
67
+ */
68
+ const evaluateRowComparison = (
69
+ baseOperator: string,
70
+ lhs: unknown,
71
+ comparator: string,
72
+ lhsVariableId: number,
73
+ lhsValue: string | number,
74
+ leafContext: ComparatorResolverContext,
75
+ deps: ComparatorResolverDeps
76
+ ): boolean => {
77
+ const resolvedComparator = resolveComparatorString(
78
+ comparator,
79
+ leafContext,
80
+ deps
81
+ );
82
+ const leafData: ConditionData = {
83
+ o: {},
84
+ v: { [lhsVariableId]: [lhsValue] },
85
+ c: { o: {}, s: {}, v: {} },
86
+ };
87
+ return Boolean(
88
+ LpLogic({ [baseOperator]: [lhs, resolvedComparator] }, leafData)
89
+ );
90
+ };
91
+
92
+ /**
93
+ * Evaluates a sibling `[var:N]` leaf pairwise across occurrences.
94
+ */
95
+ const evaluatePairwiseSiblingLeaf = (
96
+ operatorKey: string,
97
+ args: unknown[],
98
+ context: ComparatorResolverContext,
99
+ deps: ComparatorResolverDeps
100
+ ): boolean => {
101
+ const scopeMatch = operatorKey.match(SCOPED_OPERATOR_REGEX);
102
+ const scope = scopeMatch === null ? "all" : scopeMatch[1];
103
+ const baseOperator = operatorKey.replace(SCOPED_OPERATOR_REGEX, "");
104
+ const lhsVariableId = extractLhsVariableId(args[0]);
105
+ if (lhsVariableId === undefined) return false;
106
+
107
+ const lhsValues = deps.ovc.variables[lhsVariableId] ?? [];
108
+ if (lhsValues.length === 0) {
109
+ return scope !== "any" && scope !== "first";
110
+ }
111
+
112
+ const comparator = args[1] as string;
113
+ const lhs = args[0];
114
+
115
+ if (scope === "first") {
116
+ return evaluateRowComparison(
117
+ baseOperator,
118
+ lhs,
119
+ comparator,
120
+ lhsVariableId,
121
+ lhsValues[0] as string | number,
122
+ withLhsLeafContext(context, deps, lhsVariableId, 0),
123
+ deps
124
+ );
125
+ }
126
+
127
+ if (scope === "any") {
128
+ return lhsValues.some((lhsValue, index) =>
129
+ evaluateRowComparison(
130
+ baseOperator,
131
+ lhs,
132
+ comparator,
133
+ lhsVariableId,
134
+ lhsValue as string | number,
135
+ withLhsLeafContext(context, deps, lhsVariableId, index),
136
+ deps
137
+ )
138
+ );
139
+ }
140
+
141
+ return lhsValues.every((lhsValue, index) =>
142
+ evaluateRowComparison(
143
+ baseOperator,
144
+ lhs,
145
+ comparator,
146
+ lhsVariableId,
147
+ lhsValue as string | number,
148
+ withLhsLeafContext(context, deps, lhsVariableId, index),
149
+ deps
150
+ )
151
+ );
152
+ };
153
+
154
+ /**
155
+ * Evaluates a scoped `-first` / `-any` leaf (non-pairwise) against unsliced data.
156
+ */
157
+ const evaluateScopedLeaf = (
158
+ operatorKey: string,
159
+ args: unknown[],
160
+ unslicedData: ConditionData,
161
+ context: ComparatorResolverContext,
162
+ deps: ComparatorResolverDeps
163
+ ): boolean => {
164
+ const lhsVariableId = extractLhsVariableId(args[0]);
165
+ const comparator = args[1];
166
+ const leafContext =
167
+ lhsVariableId === undefined
168
+ ? { ...context, index: 0 }
169
+ : withLhsLeafContext(context, deps, lhsVariableId, 0);
170
+
171
+ const resolvedArgs = [...args];
172
+ if (typeof comparator === "string") {
173
+ resolvedArgs[1] = resolveComparatorString(comparator, leafContext, deps);
174
+ }
175
+
176
+ return Boolean(LpLogic({ [operatorKey]: resolvedArgs }, unslicedData));
177
+ };
178
+
179
+ /**
180
+ * Replaces `-first` / `-any` leaves and pairwise sibling `[var:N]` leaves with booleans.
181
+ *
182
+ * DataPopulator slices same-multiple rows for `related`; scoped operators must
183
+ * still aggregate across every occurrence. Sibling comparators on `all` / `any`
184
+ * / `first` must compare matching indexes pairwise.
185
+ */
186
+ export const aggregateScopedLeaves = (
187
+ tree: unknown,
188
+ unslicedData: ConditionData,
189
+ context: ComparatorResolverContext,
190
+ deps: ComparatorResolverDeps
191
+ ): unknown => {
192
+ if (tree === null || tree === undefined) return tree;
193
+
194
+ if (Array.isArray(tree)) {
195
+ return tree.map((child) =>
196
+ aggregateScopedLeaves(child, unslicedData, context, deps)
197
+ );
198
+ }
199
+
200
+ if (typeof tree !== "object") return tree;
201
+
202
+ const node = tree as Record<string, unknown>;
203
+ const structuralKeys = Object.keys(node).filter(
204
+ (key) => !UI_METADATA_KEYS.has(key)
205
+ );
206
+
207
+ if (structuralKeys.length === 0) return node;
208
+
209
+ const operatorKey = structuralKeys[0];
210
+
211
+ if (operatorKey === "and" || operatorKey === "or") {
212
+ const children = node[operatorKey];
213
+ if (!Array.isArray(children)) return node;
214
+ return {
215
+ ...node,
216
+ [operatorKey]: children.map((child) =>
217
+ aggregateScopedLeaves(child, unslicedData, context, deps)
218
+ ),
219
+ };
220
+ }
221
+
222
+ const args = node[operatorKey];
223
+ if (
224
+ Array.isArray(args) &&
225
+ shouldEvaluatePairwiseSiblingLeaf(operatorKey, args, context, deps)
226
+ ) {
227
+ return evaluatePairwiseSiblingLeaf(operatorKey, args, context, deps);
228
+ }
229
+
230
+ if (SCOPED_OPERATOR_REGEX.test(operatorKey)) {
231
+ if (!Array.isArray(args)) return node;
232
+ return evaluateScopedLeaf(operatorKey, args, unslicedData, context, deps);
233
+ }
234
+
235
+ return node;
236
+ };