@object-ui/core 3.1.5 → 3.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.
- package/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +12 -0
- package/dist/adapters/ValueDataSource.d.ts +5 -1
- package/dist/adapters/ValueDataSource.js +27 -0
- package/dist/errors/index.js +2 -3
- package/dist/evaluator/ExpressionCache.d.ts +9 -10
- package/dist/evaluator/ExpressionCache.js +29 -8
- package/dist/evaluator/SafeExpressionParser.d.ts +131 -0
- package/dist/evaluator/SafeExpressionParser.js +851 -0
- package/dist/evaluator/index.d.ts +1 -0
- package/dist/evaluator/index.js +1 -0
- package/dist/protocols/DndProtocol.js +2 -14
- package/dist/protocols/KeyboardProtocol.js +1 -4
- package/dist/protocols/NotificationProtocol.js +3 -13
- package/dist/utils/debug.js +2 -1
- package/package.json +6 -6
- package/src/__tests__/protocols/DndProtocol.test.ts +4 -4
- package/src/__tests__/protocols/KeyboardProtocol.test.ts +2 -2
- package/src/__tests__/protocols/NotificationProtocol.test.ts +3 -3
- package/src/adapters/ValueDataSource.ts +21 -0
- package/src/adapters/__tests__/ValueDataSource.test.ts +99 -0
- package/src/errors/index.ts +4 -5
- package/src/evaluator/ExpressionCache.ts +24 -10
- package/src/evaluator/SafeExpressionParser.ts +893 -0
- package/src/evaluator/__tests__/ExpressionEvaluator.test.ts +427 -0
- package/src/evaluator/index.ts +1 -0
- package/src/protocols/DndProtocol.ts +2 -18
- package/src/protocols/KeyboardProtocol.ts +1 -5
- package/src/protocols/NotificationProtocol.ts +3 -12
- package/src/utils/debug.ts +2 -1
- package/tsconfig.tsbuildinfo +1 -1
package/dist/evaluator/index.js
CHANGED
|
@@ -35,16 +35,10 @@ export function resolveDndConfig(config) {
|
|
|
35
35
|
* @returns Component props object for a draggable element
|
|
36
36
|
*/
|
|
37
37
|
export function createDragItemProps(item) {
|
|
38
|
-
const ariaLabel = typeof item.ariaLabel === 'string'
|
|
39
|
-
? item.ariaLabel
|
|
40
|
-
: item.ariaLabel?.defaultValue;
|
|
41
|
-
const label = typeof item.label === 'string'
|
|
42
|
-
? item.label
|
|
43
|
-
: item.label?.defaultValue;
|
|
44
38
|
return {
|
|
45
39
|
draggable: !(item.disabled ?? false),
|
|
46
40
|
'aria-roledescription': 'draggable',
|
|
47
|
-
'aria-label': ariaLabel ?? label,
|
|
41
|
+
'aria-label': item.ariaLabel ?? item.label,
|
|
48
42
|
'aria-describedby': item.ariaDescribedBy,
|
|
49
43
|
role: item.role ?? 'listitem',
|
|
50
44
|
'data-drag-type': item.type,
|
|
@@ -63,15 +57,9 @@ export function createDragItemProps(item) {
|
|
|
63
57
|
* @returns Component props object for a droppable area
|
|
64
58
|
*/
|
|
65
59
|
export function createDropZoneProps(zone) {
|
|
66
|
-
const ariaLabel = typeof zone.ariaLabel === 'string'
|
|
67
|
-
? zone.ariaLabel
|
|
68
|
-
: zone.ariaLabel?.defaultValue;
|
|
69
|
-
const label = typeof zone.label === 'string'
|
|
70
|
-
? zone.label
|
|
71
|
-
: zone.label?.defaultValue;
|
|
72
60
|
return {
|
|
73
61
|
'aria-dropeffect': zone.dropEffect ?? 'move',
|
|
74
|
-
'aria-label': ariaLabel ?? label,
|
|
62
|
+
'aria-label': zone.ariaLabel ?? zone.label,
|
|
75
63
|
'aria-describedby': zone.ariaDescribedBy,
|
|
76
64
|
role: zone.role ?? 'list',
|
|
77
65
|
'data-drop-accept': zone.accept.join(','),
|
|
@@ -15,14 +15,11 @@
|
|
|
15
15
|
* @returns Fully resolved keyboard navigation configuration
|
|
16
16
|
*/
|
|
17
17
|
export function resolveKeyboardConfig(config) {
|
|
18
|
-
const ariaLabel = typeof config.ariaLabel === 'string'
|
|
19
|
-
? config.ariaLabel
|
|
20
|
-
: config.ariaLabel?.defaultValue;
|
|
21
18
|
return {
|
|
22
19
|
shortcuts: config.shortcuts ?? [],
|
|
23
20
|
focusManagement: resolveFocusManagement(config.focusManagement),
|
|
24
21
|
rovingTabindex: config.rovingTabindex ?? false,
|
|
25
|
-
ariaLabel,
|
|
22
|
+
ariaLabel: config.ariaLabel,
|
|
26
23
|
ariaDescribedBy: config.ariaDescribedBy,
|
|
27
24
|
role: config.role,
|
|
28
25
|
};
|
|
@@ -65,16 +65,6 @@ export function resolveNotificationConfig(config) {
|
|
|
65
65
|
// ============================================================================
|
|
66
66
|
// Spec Notification → Toast
|
|
67
67
|
// ============================================================================
|
|
68
|
-
/**
|
|
69
|
-
* Extract the display string from a translatable value (string or Translation object).
|
|
70
|
-
*/
|
|
71
|
-
function resolveTranslatableString(value) {
|
|
72
|
-
if (value === undefined)
|
|
73
|
-
return undefined;
|
|
74
|
-
if (typeof value === 'string')
|
|
75
|
-
return value;
|
|
76
|
-
return value.defaultValue;
|
|
77
|
-
}
|
|
78
68
|
/**
|
|
79
69
|
* Convert a spec Notification to a toast-compatible object.
|
|
80
70
|
*
|
|
@@ -83,13 +73,13 @@ function resolveTranslatableString(value) {
|
|
|
83
73
|
*/
|
|
84
74
|
export function specNotificationToToast(notification) {
|
|
85
75
|
const actions = (notification.actions ?? []).map((a) => ({
|
|
86
|
-
label:
|
|
76
|
+
label: a.label,
|
|
87
77
|
action: a.action,
|
|
88
78
|
variant: a.variant ?? 'primary',
|
|
89
79
|
}));
|
|
90
80
|
return {
|
|
91
|
-
title:
|
|
92
|
-
description:
|
|
81
|
+
title: notification.title,
|
|
82
|
+
description: notification.message ?? '',
|
|
93
83
|
variant: mapSeverityToVariant(notification.severity ?? 'info'),
|
|
94
84
|
position: mapPosition(notification.position ?? 'top_right'),
|
|
95
85
|
duration: notification.duration ?? 5000,
|
package/dist/utils/debug.js
CHANGED
|
@@ -66,7 +66,8 @@ export function isDebugEnabled() {
|
|
|
66
66
|
if (g === true || g === 'true')
|
|
67
67
|
return true;
|
|
68
68
|
// 3. process.env
|
|
69
|
-
|
|
69
|
+
const proc = globalThis.process;
|
|
70
|
+
if (proc?.env?.OBJECTUI_DEBUG === 'true')
|
|
70
71
|
return true;
|
|
71
72
|
return false;
|
|
72
73
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@object-ui/core",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.3.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"license": "MIT",
|
|
@@ -25,14 +25,14 @@
|
|
|
25
25
|
}
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@objectstack/spec": "^
|
|
29
|
-
"lodash": "^4.
|
|
28
|
+
"@objectstack/spec": "^4.0.3",
|
|
29
|
+
"lodash": "^4.18.1",
|
|
30
30
|
"zod": "^4.3.6",
|
|
31
|
-
"@object-ui/types": "3.
|
|
31
|
+
"@object-ui/types": "3.3.0"
|
|
32
32
|
},
|
|
33
33
|
"devDependencies": {
|
|
34
|
-
"typescript": "^
|
|
35
|
-
"vitest": "^4.1.
|
|
34
|
+
"typescript": "^6.0.2",
|
|
35
|
+
"vitest": "^4.1.4"
|
|
36
36
|
},
|
|
37
37
|
"scripts": {
|
|
38
38
|
"build": "tsc",
|
|
@@ -82,10 +82,10 @@ describe('DndProtocol', () => {
|
|
|
82
82
|
expect(props['aria-label']).toBe('Aria Label');
|
|
83
83
|
});
|
|
84
84
|
|
|
85
|
-
it('should
|
|
85
|
+
it('should use ariaLabel when provided as string', () => {
|
|
86
86
|
const item = {
|
|
87
87
|
type: 'card',
|
|
88
|
-
ariaLabel:
|
|
88
|
+
ariaLabel: 'Translated label',
|
|
89
89
|
} as unknown as DragItem;
|
|
90
90
|
const props = createDragItemProps(item);
|
|
91
91
|
|
|
@@ -131,10 +131,10 @@ describe('DndProtocol', () => {
|
|
|
131
131
|
expect(props['data-drop-max-items']).toBe(10);
|
|
132
132
|
});
|
|
133
133
|
|
|
134
|
-
it('should
|
|
134
|
+
it('should use ariaLabel string when provided', () => {
|
|
135
135
|
const zone = {
|
|
136
136
|
accept: ['card'],
|
|
137
|
-
ariaLabel:
|
|
137
|
+
ariaLabel: 'Drop here',
|
|
138
138
|
} as unknown as DropZone;
|
|
139
139
|
const props = createDropZoneProps(zone);
|
|
140
140
|
|
|
@@ -32,9 +32,9 @@ describe('KeyboardProtocol', () => {
|
|
|
32
32
|
expect(resolved.ariaLabel).toBe('Navigation');
|
|
33
33
|
});
|
|
34
34
|
|
|
35
|
-
it('should resolve ariaLabel from
|
|
35
|
+
it('should resolve ariaLabel from string', () => {
|
|
36
36
|
const config = {
|
|
37
|
-
ariaLabel:
|
|
37
|
+
ariaLabel: 'Nav panel',
|
|
38
38
|
} as unknown as KeyboardNavigationConfig;
|
|
39
39
|
const resolved = resolveKeyboardConfig(config);
|
|
40
40
|
|
|
@@ -101,10 +101,10 @@ describe('NotificationProtocol', () => {
|
|
|
101
101
|
expect(toast.actions).toEqual([]);
|
|
102
102
|
});
|
|
103
103
|
|
|
104
|
-
it('should handle
|
|
104
|
+
it('should handle string title and message', () => {
|
|
105
105
|
const notification = {
|
|
106
|
-
title:
|
|
107
|
-
message:
|
|
106
|
+
title: 'Heads up',
|
|
107
|
+
message: 'Something happened',
|
|
108
108
|
} as unknown as SpecNotification;
|
|
109
109
|
const toast = specNotificationToToast(notification);
|
|
110
110
|
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
|
|
12
12
|
import type {
|
|
13
13
|
DataSource,
|
|
14
|
+
MutationEvent,
|
|
14
15
|
QueryParams,
|
|
15
16
|
QueryResult,
|
|
16
17
|
AggregateParams,
|
|
@@ -228,6 +229,7 @@ function selectFields<T>(record: T, fields?: string[]): T {
|
|
|
228
229
|
export class ValueDataSource<T = any> implements DataSource<T> {
|
|
229
230
|
private items: T[];
|
|
230
231
|
private idField: string | undefined;
|
|
232
|
+
private mutationListeners = new Set<(event: MutationEvent<T>) => void>();
|
|
231
233
|
|
|
232
234
|
constructor(config: ValueDataSourceConfig<T>) {
|
|
233
235
|
// Deep clone to prevent external mutation
|
|
@@ -235,6 +237,13 @@ export class ValueDataSource<T = any> implements DataSource<T> {
|
|
|
235
237
|
this.idField = config.idField;
|
|
236
238
|
}
|
|
237
239
|
|
|
240
|
+
/** Notify all mutation subscribers */
|
|
241
|
+
private emitMutation(event: MutationEvent<T>): void {
|
|
242
|
+
for (const listener of this.mutationListeners) {
|
|
243
|
+
try { listener(event); } catch (err) { console.warn('ValueDataSource: mutation listener error', err); }
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
238
247
|
// -----------------------------------------------------------------------
|
|
239
248
|
// DataSource interface
|
|
240
249
|
// -----------------------------------------------------------------------
|
|
@@ -308,6 +317,7 @@ export class ValueDataSource<T = any> implements DataSource<T> {
|
|
|
308
317
|
(record as any)[field] = `auto_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
|
309
318
|
}
|
|
310
319
|
this.items.push(record);
|
|
320
|
+
this.emitMutation({ type: 'create', resource: _resource, record: { ...record } });
|
|
311
321
|
return { ...record };
|
|
312
322
|
}
|
|
313
323
|
|
|
@@ -323,6 +333,7 @@ export class ValueDataSource<T = any> implements DataSource<T> {
|
|
|
323
333
|
throw new Error(`ValueDataSource: Record with id "${id}" not found`);
|
|
324
334
|
}
|
|
325
335
|
this.items[index] = { ...this.items[index], ...data };
|
|
336
|
+
this.emitMutation({ type: 'update', resource: _resource, id, record: { ...this.items[index] } });
|
|
326
337
|
return { ...this.items[index] };
|
|
327
338
|
}
|
|
328
339
|
|
|
@@ -332,6 +343,7 @@ export class ValueDataSource<T = any> implements DataSource<T> {
|
|
|
332
343
|
);
|
|
333
344
|
if (index === -1) return false;
|
|
334
345
|
this.items.splice(index, 1);
|
|
346
|
+
this.emitMutation({ type: 'delete', resource: _resource, id });
|
|
335
347
|
return true;
|
|
336
348
|
}
|
|
337
349
|
|
|
@@ -422,6 +434,15 @@ export class ValueDataSource<T = any> implements DataSource<T> {
|
|
|
422
434
|
});
|
|
423
435
|
}
|
|
424
436
|
|
|
437
|
+
// -----------------------------------------------------------------------
|
|
438
|
+
// Mutation subscription (P2 — Event Bus)
|
|
439
|
+
// -----------------------------------------------------------------------
|
|
440
|
+
|
|
441
|
+
onMutation(callback: (event: MutationEvent<T>) => void): () => void {
|
|
442
|
+
this.mutationListeners.add(callback);
|
|
443
|
+
return () => { this.mutationListeners.delete(callback); };
|
|
444
|
+
}
|
|
445
|
+
|
|
425
446
|
// -----------------------------------------------------------------------
|
|
426
447
|
// Extra utilities
|
|
427
448
|
// -----------------------------------------------------------------------
|
|
@@ -470,3 +470,102 @@ describe('ValueDataSource — aggregate', () => {
|
|
|
470
470
|
expect(result.find((r: any) => r.category === 'B')?.amount).toBe(50);
|
|
471
471
|
});
|
|
472
472
|
});
|
|
473
|
+
|
|
474
|
+
// ---------------------------------------------------------------------------
|
|
475
|
+
// onMutation (P2 — Event Bus)
|
|
476
|
+
// ---------------------------------------------------------------------------
|
|
477
|
+
|
|
478
|
+
describe('ValueDataSource — onMutation', () => {
|
|
479
|
+
it('should emit "create" event when a record is created', async () => {
|
|
480
|
+
const ds = createDS();
|
|
481
|
+
const events: any[] = [];
|
|
482
|
+
ds.onMutation((e) => events.push(e));
|
|
483
|
+
|
|
484
|
+
await ds.create('users', { name: 'Zara', age: 40 });
|
|
485
|
+
|
|
486
|
+
expect(events).toHaveLength(1);
|
|
487
|
+
expect(events[0].type).toBe('create');
|
|
488
|
+
expect(events[0].resource).toBe('users');
|
|
489
|
+
expect(events[0].record.name).toBe('Zara');
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
it('should emit "update" event when a record is updated', async () => {
|
|
493
|
+
const ds = createDS();
|
|
494
|
+
const events: any[] = [];
|
|
495
|
+
ds.onMutation((e) => events.push(e));
|
|
496
|
+
|
|
497
|
+
await ds.update('users', '1', { age: 31 });
|
|
498
|
+
|
|
499
|
+
expect(events).toHaveLength(1);
|
|
500
|
+
expect(events[0].type).toBe('update');
|
|
501
|
+
expect(events[0].resource).toBe('users');
|
|
502
|
+
expect(events[0].id).toBe('1');
|
|
503
|
+
expect(events[0].record.age).toBe(31);
|
|
504
|
+
});
|
|
505
|
+
|
|
506
|
+
it('should emit "delete" event when a record is deleted', async () => {
|
|
507
|
+
const ds = createDS();
|
|
508
|
+
const events: any[] = [];
|
|
509
|
+
ds.onMutation((e) => events.push(e));
|
|
510
|
+
|
|
511
|
+
await ds.delete('users', '2');
|
|
512
|
+
|
|
513
|
+
expect(events).toHaveLength(1);
|
|
514
|
+
expect(events[0].type).toBe('delete');
|
|
515
|
+
expect(events[0].resource).toBe('users');
|
|
516
|
+
expect(events[0].id).toBe('2');
|
|
517
|
+
expect(events[0].record).toBeUndefined();
|
|
518
|
+
});
|
|
519
|
+
|
|
520
|
+
it('should not emit "delete" for non-existent record', async () => {
|
|
521
|
+
const ds = createDS();
|
|
522
|
+
const events: any[] = [];
|
|
523
|
+
ds.onMutation((e) => events.push(e));
|
|
524
|
+
|
|
525
|
+
await ds.delete('users', '999');
|
|
526
|
+
|
|
527
|
+
expect(events).toHaveLength(0);
|
|
528
|
+
});
|
|
529
|
+
|
|
530
|
+
it('should support multiple subscribers', async () => {
|
|
531
|
+
const ds = createDS();
|
|
532
|
+
const eventsA: any[] = [];
|
|
533
|
+
const eventsB: any[] = [];
|
|
534
|
+
ds.onMutation((e) => eventsA.push(e));
|
|
535
|
+
ds.onMutation((e) => eventsB.push(e));
|
|
536
|
+
|
|
537
|
+
await ds.create('users', { name: 'Multi' });
|
|
538
|
+
|
|
539
|
+
expect(eventsA).toHaveLength(1);
|
|
540
|
+
expect(eventsB).toHaveLength(1);
|
|
541
|
+
});
|
|
542
|
+
|
|
543
|
+
it('should unsubscribe correctly', async () => {
|
|
544
|
+
const ds = createDS();
|
|
545
|
+
const events: any[] = [];
|
|
546
|
+
const unsub = ds.onMutation((e) => events.push(e));
|
|
547
|
+
|
|
548
|
+
await ds.create('users', { name: 'Before' });
|
|
549
|
+
expect(events).toHaveLength(1);
|
|
550
|
+
|
|
551
|
+
unsub();
|
|
552
|
+
|
|
553
|
+
await ds.create('users', { name: 'After' });
|
|
554
|
+
expect(events).toHaveLength(1); // No new event
|
|
555
|
+
});
|
|
556
|
+
|
|
557
|
+
it('should emit events for bulk operations', async () => {
|
|
558
|
+
const ds = createDS();
|
|
559
|
+
const events: any[] = [];
|
|
560
|
+
ds.onMutation((e) => events.push(e));
|
|
561
|
+
|
|
562
|
+
await ds.bulk!('users', 'create', [
|
|
563
|
+
{ name: 'Bulk1' },
|
|
564
|
+
{ name: 'Bulk2' },
|
|
565
|
+
]);
|
|
566
|
+
|
|
567
|
+
// Bulk create calls create() for each item
|
|
568
|
+
expect(events).toHaveLength(2);
|
|
569
|
+
expect(events.every((e: any) => e.type === 'create')).toBe(true);
|
|
570
|
+
});
|
|
571
|
+
});
|
package/src/errors/index.ts
CHANGED
|
@@ -120,8 +120,8 @@ export class ObjectUIError extends Error {
|
|
|
120
120
|
this.name = 'ObjectUIError';
|
|
121
121
|
|
|
122
122
|
// Maintains proper stack trace for where error was thrown (only in V8)
|
|
123
|
-
if (Error.captureStackTrace) {
|
|
124
|
-
Error.captureStackTrace(this, this.constructor);
|
|
123
|
+
if ((Error as any).captureStackTrace) {
|
|
124
|
+
(Error as any).captureStackTrace(this, this.constructor);
|
|
125
125
|
}
|
|
126
126
|
}
|
|
127
127
|
|
|
@@ -203,7 +203,7 @@ function interpolate(
|
|
|
203
203
|
params: Record<string, string>,
|
|
204
204
|
): string {
|
|
205
205
|
return template.replace(/\$\{(\w+)\}/g, (_match, key: string) => {
|
|
206
|
-
if (!(key in params) &&
|
|
206
|
+
if (!(key in params) && (globalThis as any).process?.env?.NODE_ENV !== 'production') {
|
|
207
207
|
console.warn(`[ObjectUI] Missing interpolation parameter "${key}" in error message template.`);
|
|
208
208
|
}
|
|
209
209
|
return params[key] ?? `\${${key}}`;
|
|
@@ -254,8 +254,7 @@ export function createError(
|
|
|
254
254
|
*/
|
|
255
255
|
export function formatErrorMessage(
|
|
256
256
|
error: ObjectUIError,
|
|
257
|
-
isDev: boolean =
|
|
258
|
-
process.env?.NODE_ENV !== 'production',
|
|
257
|
+
isDev: boolean = (globalThis as any).process?.env?.NODE_ENV !== 'production',
|
|
259
258
|
): string {
|
|
260
259
|
const entry = ERROR_CODES[error.code];
|
|
261
260
|
|
|
@@ -8,14 +8,16 @@
|
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* @object-ui/core - Expression Cache
|
|
11
|
-
*
|
|
11
|
+
*
|
|
12
12
|
* Caches compiled expressions to avoid re-parsing on every render.
|
|
13
13
|
* Provides significant performance improvement for frequently evaluated expressions.
|
|
14
|
-
*
|
|
14
|
+
*
|
|
15
15
|
* @module evaluator
|
|
16
16
|
* @packageDocumentation
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
|
+
import { SafeExpressionParser } from './SafeExpressionParser.js';
|
|
20
|
+
|
|
19
21
|
/**
|
|
20
22
|
* A compiled expression function that can be executed with context values
|
|
21
23
|
*/
|
|
@@ -112,16 +114,28 @@ export class ExpressionCache {
|
|
|
112
114
|
}
|
|
113
115
|
|
|
114
116
|
/**
|
|
115
|
-
* Compile an expression into a function
|
|
117
|
+
* Compile an expression into a CSP-safe callable function.
|
|
118
|
+
*
|
|
119
|
+
* Uses `SafeExpressionParser` — a recursive-descent interpreter — instead of
|
|
120
|
+
* `new Function()` so that the expression engine works under strict
|
|
121
|
+
* Content Security Policy headers that forbid `'unsafe-eval'`.
|
|
122
|
+
*
|
|
123
|
+
* A single parser instance is created per compiled expression and reused
|
|
124
|
+
* across all invocations of the returned closure (`evaluate()` resets all
|
|
125
|
+
* internal state on every call), avoiding repeated allocations on hot paths.
|
|
116
126
|
*/
|
|
117
127
|
private compileExpression(expression: string, varNames: string[]): CompiledExpression {
|
|
118
|
-
//
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
128
|
+
// One parser per compiled expression — reused across hot-path calls.
|
|
129
|
+
const parser = new SafeExpressionParser();
|
|
130
|
+
|
|
131
|
+
return (...args: unknown[]) => {
|
|
132
|
+
// Reconstruct the named variable context from positional arguments.
|
|
133
|
+
const context: Record<string, unknown> = {};
|
|
134
|
+
for (let i = 0; i < varNames.length; i++) {
|
|
135
|
+
context[varNames[i]] = args[i];
|
|
136
|
+
}
|
|
137
|
+
return parser.evaluate(expression, context);
|
|
138
|
+
};
|
|
125
139
|
}
|
|
126
140
|
|
|
127
141
|
/**
|