@praxisui/specification 0.0.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.
@@ -0,0 +1,3495 @@
1
+ import { Specification } from '@praxisui/specification-core';
2
+ export * from '@praxisui/specification-core';
3
+
4
+ var ComparisonOperator;
5
+ (function (ComparisonOperator) {
6
+ ComparisonOperator["EQUALS"] = "eq";
7
+ ComparisonOperator["NOT_EQUALS"] = "neq";
8
+ ComparisonOperator["LESS_THAN"] = "lt";
9
+ ComparisonOperator["LESS_THAN_OR_EQUAL"] = "lte";
10
+ ComparisonOperator["GREATER_THAN"] = "gt";
11
+ ComparisonOperator["GREATER_THAN_OR_EQUAL"] = "gte";
12
+ ComparisonOperator["CONTAINS"] = "contains";
13
+ ComparisonOperator["STARTS_WITH"] = "startsWith";
14
+ ComparisonOperator["ENDS_WITH"] = "endsWith";
15
+ ComparisonOperator["IN"] = "in";
16
+ })(ComparisonOperator || (ComparisonOperator = {}));
17
+ const OPERATOR_SYMBOLS = {
18
+ [ComparisonOperator.EQUALS]: '==',
19
+ [ComparisonOperator.NOT_EQUALS]: '!=',
20
+ [ComparisonOperator.LESS_THAN]: '<',
21
+ [ComparisonOperator.LESS_THAN_OR_EQUAL]: '<=',
22
+ [ComparisonOperator.GREATER_THAN]: '>',
23
+ [ComparisonOperator.GREATER_THAN_OR_EQUAL]: '>=',
24
+ [ComparisonOperator.CONTAINS]: 'contains',
25
+ [ComparisonOperator.STARTS_WITH]: 'startsWith',
26
+ [ComparisonOperator.ENDS_WITH]: 'endsWith',
27
+ [ComparisonOperator.IN]: 'in'
28
+ };
29
+
30
+ class FieldSpecification extends Specification {
31
+ field;
32
+ operator;
33
+ value;
34
+ constructor(field, operator, value, metadata) {
35
+ super(metadata);
36
+ this.field = field;
37
+ this.operator = operator;
38
+ this.value = value;
39
+ }
40
+ isSatisfiedBy(obj) {
41
+ const fieldValue = obj[this.field];
42
+ switch (this.operator) {
43
+ case ComparisonOperator.EQUALS:
44
+ return fieldValue === this.value;
45
+ case ComparisonOperator.NOT_EQUALS:
46
+ return fieldValue !== this.value;
47
+ case ComparisonOperator.LESS_THAN:
48
+ return fieldValue < this.value;
49
+ case ComparisonOperator.LESS_THAN_OR_EQUAL:
50
+ return fieldValue <= this.value;
51
+ case ComparisonOperator.GREATER_THAN:
52
+ return fieldValue > this.value;
53
+ case ComparisonOperator.GREATER_THAN_OR_EQUAL:
54
+ return fieldValue >= this.value;
55
+ case ComparisonOperator.CONTAINS:
56
+ return String(fieldValue).includes(String(this.value));
57
+ case ComparisonOperator.STARTS_WITH:
58
+ return String(fieldValue).startsWith(String(this.value));
59
+ case ComparisonOperator.ENDS_WITH:
60
+ return String(fieldValue).endsWith(String(this.value));
61
+ case ComparisonOperator.IN:
62
+ return Array.isArray(this.value) && this.value.includes(fieldValue);
63
+ default:
64
+ throw new Error(`Unsupported operator: ${this.operator}`);
65
+ }
66
+ }
67
+ toJSON() {
68
+ return {
69
+ type: 'field',
70
+ field: String(this.field),
71
+ operator: this.operator,
72
+ value: this.value,
73
+ metadata: this.metadata,
74
+ };
75
+ }
76
+ static fromJSON(json) {
77
+ return new FieldSpecification(json.field, json.operator, json.value, json.metadata);
78
+ }
79
+ clone() {
80
+ return new FieldSpecification(this.field, this.operator, this.value, this.metadata);
81
+ }
82
+ toDSL() {
83
+ const fieldName = String(this.field);
84
+ const symbol = OPERATOR_SYMBOLS[this.operator];
85
+ switch (this.operator) {
86
+ case ComparisonOperator.CONTAINS:
87
+ return `contains(${fieldName}, ${JSON.stringify(this.value)})`;
88
+ case ComparisonOperator.STARTS_WITH:
89
+ return `startsWith(${fieldName}, ${JSON.stringify(this.value)})`;
90
+ case ComparisonOperator.ENDS_WITH:
91
+ return `endsWith(${fieldName}, ${JSON.stringify(this.value)})`;
92
+ case ComparisonOperator.IN:
93
+ const values = Array.isArray(this.value)
94
+ ? this.value.map((v) => JSON.stringify(v)).join(', ')
95
+ : JSON.stringify(this.value);
96
+ return `${fieldName} in (${values})`;
97
+ default:
98
+ return `${fieldName} ${symbol} ${JSON.stringify(this.value)}`;
99
+ }
100
+ }
101
+ getField() {
102
+ return this.field;
103
+ }
104
+ getOperator() {
105
+ return this.operator;
106
+ }
107
+ getValue() {
108
+ return this.value;
109
+ }
110
+ }
111
+
112
+ class AndSpecification extends Specification {
113
+ specifications;
114
+ constructor(specifications, metadata) {
115
+ super(metadata);
116
+ this.specifications = specifications;
117
+ if (specifications.length === 0) {
118
+ throw new Error('AndSpecification requires at least one specification');
119
+ }
120
+ }
121
+ isSatisfiedBy(obj) {
122
+ return this.specifications.every((spec) => spec.isSatisfiedBy(obj));
123
+ }
124
+ toJSON() {
125
+ return {
126
+ type: 'and',
127
+ specs: this.specifications.map((spec) => spec.toJSON()),
128
+ };
129
+ }
130
+ static fromJSON(json) {
131
+ const specs = json.specs.map((specJson) => Specification.fromJSON(specJson));
132
+ return new AndSpecification(specs);
133
+ }
134
+ toDSL() {
135
+ if (this.specifications.length === 1) {
136
+ return this.specifications[0].toDSL();
137
+ }
138
+ const parts = this.specifications.map((spec) => {
139
+ const dsl = spec.toDSL();
140
+ // Add parentheses if needed for precedence
141
+ const kind = spec.getOperatorKind?.();
142
+ if (kind === 'or' || kind === 'xor') {
143
+ return `(${dsl})`;
144
+ }
145
+ return dsl;
146
+ });
147
+ return parts.join(' && ');
148
+ }
149
+ getSpecifications() {
150
+ return [...this.specifications];
151
+ }
152
+ add(specification) {
153
+ return new AndSpecification([...this.specifications, specification], this.metadata);
154
+ }
155
+ clone() {
156
+ return new AndSpecification(this.specifications.map((spec) => spec.clone()), this.metadata);
157
+ }
158
+ getOperatorKind() {
159
+ return 'and';
160
+ }
161
+ }
162
+
163
+ class OrSpecification extends Specification {
164
+ specifications;
165
+ constructor(specifications, metadata) {
166
+ super(metadata);
167
+ this.specifications = specifications;
168
+ if (specifications.length === 0) {
169
+ throw new Error('OrSpecification requires at least one specification');
170
+ }
171
+ }
172
+ isSatisfiedBy(obj) {
173
+ return this.specifications.some((spec) => spec.isSatisfiedBy(obj));
174
+ }
175
+ toJSON() {
176
+ return {
177
+ type: 'or',
178
+ specs: this.specifications.map((spec) => spec.toJSON()),
179
+ };
180
+ }
181
+ static fromJSON(json) {
182
+ const specs = json.specs.map((specJson) => Specification.fromJSON(specJson));
183
+ return new OrSpecification(specs);
184
+ }
185
+ toDSL() {
186
+ if (this.specifications.length === 1) {
187
+ return this.specifications[0].toDSL();
188
+ }
189
+ const parts = this.specifications.map((spec) => {
190
+ const dsl = spec.toDSL();
191
+ // Add parentheses if needed for precedence
192
+ const kind = spec.getOperatorKind?.();
193
+ if (kind === 'and') {
194
+ return `(${dsl})`;
195
+ }
196
+ return dsl;
197
+ });
198
+ return parts.join(' || ');
199
+ }
200
+ getSpecifications() {
201
+ return [...this.specifications];
202
+ }
203
+ add(specification) {
204
+ return new OrSpecification([...this.specifications, specification], this.metadata);
205
+ }
206
+ clone() {
207
+ return new OrSpecification(this.specifications.map((spec) => spec.clone()), this.metadata);
208
+ }
209
+ getOperatorKind() {
210
+ return 'or';
211
+ }
212
+ }
213
+
214
+ class XorSpecification extends Specification {
215
+ specifications;
216
+ constructor(specifications, metadata) {
217
+ super(metadata);
218
+ this.specifications = specifications;
219
+ if (specifications.length < 2) {
220
+ throw new Error('XorSpecification requires at least two specifications');
221
+ }
222
+ }
223
+ isSatisfiedBy(obj) {
224
+ const satisfiedCount = this.specifications.filter((spec) => spec.isSatisfiedBy(obj)).length;
225
+ return satisfiedCount === 1;
226
+ }
227
+ toJSON() {
228
+ return {
229
+ type: 'xor',
230
+ specs: this.specifications.map((spec) => spec.toJSON()),
231
+ };
232
+ }
233
+ static fromJSON(json) {
234
+ const specs = json.specs.map((specJson) => Specification.fromJSON(specJson));
235
+ return new XorSpecification(specs);
236
+ }
237
+ toDSL() {
238
+ const parts = this.specifications.map((spec) => {
239
+ const dsl = spec.toDSL();
240
+ // Add parentheses if needed for precedence
241
+ const kind = spec.getOperatorKind?.();
242
+ if (kind === 'and' || kind === 'or') {
243
+ return `(${dsl})`;
244
+ }
245
+ return dsl;
246
+ });
247
+ return parts.join(' xor ');
248
+ }
249
+ getSpecifications() {
250
+ return [...this.specifications];
251
+ }
252
+ clone() {
253
+ return new XorSpecification(this.specifications.map((spec) => spec.clone()), this.metadata);
254
+ }
255
+ getOperatorKind() {
256
+ return 'xor';
257
+ }
258
+ }
259
+
260
+ class ImpliesSpecification extends Specification {
261
+ antecedent;
262
+ consequent;
263
+ constructor(antecedent, consequent, metadata) {
264
+ super(metadata);
265
+ this.antecedent = antecedent;
266
+ this.consequent = consequent;
267
+ }
268
+ isSatisfiedBy(obj) {
269
+ // A implies B is equivalent to (!A || B)
270
+ return (!this.antecedent.isSatisfiedBy(obj) || this.consequent.isSatisfiedBy(obj));
271
+ }
272
+ toJSON() {
273
+ return {
274
+ type: 'implies',
275
+ antecedent: this.antecedent.toJSON(),
276
+ consequent: this.consequent.toJSON(),
277
+ };
278
+ }
279
+ static fromJSON(json) {
280
+ const antecedent = Specification.fromJSON(json.antecedent);
281
+ const consequent = Specification.fromJSON(json.consequent);
282
+ return new ImpliesSpecification(antecedent, consequent);
283
+ }
284
+ toDSL() {
285
+ const antecedentDsl = this.antecedent.toDSL();
286
+ const consequentDsl = this.consequent.toDSL();
287
+ // Add parentheses for complex expressions
288
+ const leftPart = this.antecedent instanceof AndSpecification ||
289
+ this.antecedent instanceof OrSpecification ||
290
+ this.antecedent instanceof XorSpecification
291
+ ? `(${antecedentDsl})`
292
+ : antecedentDsl;
293
+ const rightPart = this.consequent instanceof AndSpecification ||
294
+ this.consequent instanceof OrSpecification ||
295
+ this.consequent instanceof XorSpecification
296
+ ? `(${consequentDsl})`
297
+ : consequentDsl;
298
+ return `${leftPart} implies ${rightPart}`;
299
+ }
300
+ getAntecedent() {
301
+ return this.antecedent;
302
+ }
303
+ getConsequent() {
304
+ return this.consequent;
305
+ }
306
+ clone() {
307
+ return new ImpliesSpecification(this.antecedent.clone(), this.consequent.clone(), this.metadata);
308
+ }
309
+ }
310
+
311
+ class NotSpecification extends Specification {
312
+ specification;
313
+ constructor(specification, metadata) {
314
+ super(metadata);
315
+ this.specification = specification;
316
+ }
317
+ isSatisfiedBy(obj) {
318
+ return !this.specification.isSatisfiedBy(obj);
319
+ }
320
+ toJSON() {
321
+ return {
322
+ type: 'not',
323
+ spec: this.specification.toJSON(),
324
+ };
325
+ }
326
+ static fromJSON(json) {
327
+ const spec = Specification.fromJSON(json.spec);
328
+ return new NotSpecification(spec);
329
+ }
330
+ toDSL() {
331
+ const innerDsl = this.specification.toDSL();
332
+ // Add parentheses for complex expressions
333
+ if (this.specification instanceof AndSpecification ||
334
+ this.specification instanceof OrSpecification ||
335
+ this.specification instanceof XorSpecification ||
336
+ this.specification instanceof ImpliesSpecification) {
337
+ return `!(${innerDsl})`;
338
+ }
339
+ return `!${innerDsl}`;
340
+ }
341
+ getSpecification() {
342
+ return this.specification;
343
+ }
344
+ clone() {
345
+ return new NotSpecification(this.specification.clone(), this.metadata);
346
+ }
347
+ }
348
+
349
+ class FunctionRegistry {
350
+ contextKey;
351
+ static instances = new Map();
352
+ functions = new Map();
353
+ constructor(contextKey) {
354
+ this.contextKey = contextKey;
355
+ }
356
+ static getInstance(contextKey = 'default') {
357
+ if (!this.instances.has(contextKey)) {
358
+ this.instances.set(contextKey, new FunctionRegistry(contextKey));
359
+ }
360
+ return this.instances.get(contextKey);
361
+ }
362
+ register(name, fn) {
363
+ this.functions.set(name, fn);
364
+ }
365
+ unregister(name) {
366
+ return this.functions.delete(name);
367
+ }
368
+ get(name) {
369
+ return this.functions.get(name);
370
+ }
371
+ has(name) {
372
+ return this.functions.has(name);
373
+ }
374
+ getAll() {
375
+ return new Map(this.functions);
376
+ }
377
+ clear() {
378
+ this.functions.clear();
379
+ }
380
+ execute(name, obj, ...args) {
381
+ const fn = this.functions.get(name);
382
+ if (!fn) {
383
+ throw new Error(`Function '${name}' not found in registry`);
384
+ }
385
+ return fn(obj, ...args);
386
+ }
387
+ getContextKey() {
388
+ return this.contextKey;
389
+ }
390
+ }
391
+
392
+ class FunctionSpecification extends Specification {
393
+ functionName;
394
+ args;
395
+ registry;
396
+ constructor(functionName, args, registry, metadata) {
397
+ super(metadata);
398
+ this.functionName = functionName;
399
+ this.args = args;
400
+ this.registry = registry;
401
+ }
402
+ isSatisfiedBy(obj) {
403
+ const registry = this.registry || FunctionRegistry.getInstance();
404
+ return registry.execute(this.functionName, obj, ...this.args);
405
+ }
406
+ toJSON() {
407
+ return {
408
+ type: 'function',
409
+ name: this.functionName,
410
+ args: this.args,
411
+ };
412
+ }
413
+ static fromJSON(json, registry) {
414
+ return new FunctionSpecification(json.name, json.args, registry);
415
+ }
416
+ toDSL() {
417
+ const argsStr = this.args
418
+ .map((arg) => {
419
+ if (typeof arg === 'string') {
420
+ return JSON.stringify(arg);
421
+ }
422
+ if (typeof arg === 'object' && arg !== null && arg.type === 'field') {
423
+ return `${arg.field}`;
424
+ }
425
+ return JSON.stringify(arg);
426
+ })
427
+ .join(', ');
428
+ return `${this.functionName}(${argsStr})`;
429
+ }
430
+ getFunctionName() {
431
+ return this.functionName;
432
+ }
433
+ getArgs() {
434
+ return [...this.args];
435
+ }
436
+ getRegistry() {
437
+ return this.registry;
438
+ }
439
+ clone() {
440
+ return new FunctionSpecification(this.functionName, [...this.args], this.registry, this.metadata);
441
+ }
442
+ }
443
+
444
+ class AtLeastSpecification extends Specification {
445
+ minimum;
446
+ specifications;
447
+ constructor(minimum, specifications, metadata) {
448
+ super(metadata);
449
+ this.minimum = minimum;
450
+ this.specifications = specifications;
451
+ if (minimum < 0) {
452
+ throw new Error('Minimum count must be non-negative');
453
+ }
454
+ if (specifications.length === 0) {
455
+ throw new Error('AtLeastSpecification requires at least one specification');
456
+ }
457
+ }
458
+ isSatisfiedBy(obj) {
459
+ const satisfiedCount = this.specifications.filter((spec) => spec.isSatisfiedBy(obj)).length;
460
+ return satisfiedCount >= this.minimum;
461
+ }
462
+ toJSON() {
463
+ return {
464
+ type: 'atLeast',
465
+ minimum: this.minimum,
466
+ specs: this.specifications.map((spec) => spec.toJSON()),
467
+ metadata: this.metadata,
468
+ };
469
+ }
470
+ static fromJSON(json) {
471
+ const specs = json.specs.map((specJson) => Specification.fromJSON(specJson));
472
+ return new AtLeastSpecification(json.minimum, specs, json.metadata);
473
+ }
474
+ toDSL() {
475
+ const specDsls = this.specifications.map((spec) => spec.toDSL());
476
+ const specsStr = specDsls.join(', ');
477
+ return `atLeast(${this.minimum}, [${specsStr}])`;
478
+ }
479
+ getMinimum() {
480
+ return this.minimum;
481
+ }
482
+ getSpecifications() {
483
+ return [...this.specifications];
484
+ }
485
+ getSatisfiedCount(obj) {
486
+ return this.specifications.filter((spec) => spec.isSatisfiedBy(obj)).length;
487
+ }
488
+ clone() {
489
+ const clonedSpecs = this.specifications.map((spec) => spec.clone());
490
+ return new AtLeastSpecification(this.minimum, clonedSpecs, this.metadata);
491
+ }
492
+ }
493
+
494
+ class ExactlySpecification extends Specification {
495
+ exact;
496
+ specifications;
497
+ constructor(exact, specifications, metadata) {
498
+ super(metadata);
499
+ this.exact = exact;
500
+ this.specifications = specifications;
501
+ if (exact < 0) {
502
+ throw new Error('Exact count must be non-negative');
503
+ }
504
+ if (specifications.length === 0) {
505
+ throw new Error('ExactlySpecification requires at least one specification');
506
+ }
507
+ }
508
+ isSatisfiedBy(obj) {
509
+ const satisfiedCount = this.specifications.filter((spec) => spec.isSatisfiedBy(obj)).length;
510
+ return satisfiedCount === this.exact;
511
+ }
512
+ toJSON() {
513
+ return {
514
+ type: 'exactly',
515
+ exact: this.exact,
516
+ specs: this.specifications.map((spec) => spec.toJSON()),
517
+ metadata: this.metadata,
518
+ };
519
+ }
520
+ static fromJSON(json) {
521
+ const specs = json.specs.map((specJson) => Specification.fromJSON(specJson));
522
+ return new ExactlySpecification(json.exact, specs, json.metadata);
523
+ }
524
+ toDSL() {
525
+ const specDsls = this.specifications.map((spec) => spec.toDSL());
526
+ const specsStr = specDsls.join(', ');
527
+ return `exactly(${this.exact}, [${specsStr}])`;
528
+ }
529
+ getExact() {
530
+ return this.exact;
531
+ }
532
+ getSpecifications() {
533
+ return [...this.specifications];
534
+ }
535
+ getSatisfiedCount(obj) {
536
+ return this.specifications.filter((spec) => spec.isSatisfiedBy(obj)).length;
537
+ }
538
+ clone() {
539
+ const clonedSpecs = this.specifications.map((spec) => spec.clone());
540
+ return new ExactlySpecification(this.exact, clonedSpecs, this.metadata);
541
+ }
542
+ }
543
+
544
+ class TransformRegistry {
545
+ static instance;
546
+ transforms = new Map();
547
+ constructor() {
548
+ // Register default transforms
549
+ this.registerDefaults();
550
+ }
551
+ static getInstance() {
552
+ if (!this.instance) {
553
+ this.instance = new TransformRegistry();
554
+ }
555
+ return this.instance;
556
+ }
557
+ registerDefaults() {
558
+ this.register('toLowerCase', (value) => String(value).toLowerCase());
559
+ this.register('toUpperCase', (value) => String(value).toUpperCase());
560
+ this.register('trim', (value) => String(value).trim());
561
+ this.register('toString', (value) => String(value));
562
+ this.register('toNumber', (value) => Number(value));
563
+ this.register('toBoolean', (value) => Boolean(value));
564
+ this.register('length', (value) => {
565
+ if (typeof value === 'string' || Array.isArray(value)) {
566
+ return value.length;
567
+ }
568
+ return 0;
569
+ });
570
+ }
571
+ register(name, fn) {
572
+ this.transforms.set(name, fn);
573
+ }
574
+ unregister(name) {
575
+ return this.transforms.delete(name);
576
+ }
577
+ get(name) {
578
+ return this.transforms.get(name);
579
+ }
580
+ has(name) {
581
+ return this.transforms.has(name);
582
+ }
583
+ getAll() {
584
+ return new Map(this.transforms);
585
+ }
586
+ clear() {
587
+ this.transforms.clear();
588
+ this.registerDefaults();
589
+ }
590
+ apply(name, value) {
591
+ const transform = this.transforms.get(name);
592
+ if (!transform) {
593
+ throw new Error(`Transform '${name}' not found in registry`);
594
+ }
595
+ return transform(value);
596
+ }
597
+ applyChain(transforms, value) {
598
+ return transforms.reduce((acc, transformName) => {
599
+ return this.apply(transformName, acc);
600
+ }, value);
601
+ }
602
+ }
603
+
604
+ class FieldToFieldSpecification extends Specification {
605
+ fieldA;
606
+ operator;
607
+ fieldB;
608
+ transformA;
609
+ transformB;
610
+ transformRegistry;
611
+ constructor(fieldA, operator, fieldB, transformA, transformB, transformRegistry, metadata) {
612
+ super(metadata);
613
+ this.fieldA = fieldA;
614
+ this.operator = operator;
615
+ this.fieldB = fieldB;
616
+ this.transformA = transformA;
617
+ this.transformB = transformB;
618
+ this.transformRegistry = transformRegistry;
619
+ }
620
+ isSatisfiedBy(obj) {
621
+ let valueA = obj[this.fieldA];
622
+ let valueB = obj[this.fieldB];
623
+ const registry = this.transformRegistry || TransformRegistry.getInstance();
624
+ // Apply transforms if specified
625
+ if (this.transformA && this.transformA.length > 0) {
626
+ valueA = registry.applyChain(this.transformA, valueA);
627
+ }
628
+ if (this.transformB && this.transformB.length > 0) {
629
+ valueB = registry.applyChain(this.transformB, valueB);
630
+ }
631
+ switch (this.operator) {
632
+ case ComparisonOperator.EQUALS:
633
+ return valueA === valueB;
634
+ case ComparisonOperator.NOT_EQUALS:
635
+ return valueA !== valueB;
636
+ case ComparisonOperator.LESS_THAN:
637
+ return valueA < valueB;
638
+ case ComparisonOperator.LESS_THAN_OR_EQUAL:
639
+ return valueA <= valueB;
640
+ case ComparisonOperator.GREATER_THAN:
641
+ return valueA > valueB;
642
+ case ComparisonOperator.GREATER_THAN_OR_EQUAL:
643
+ return valueA >= valueB;
644
+ case ComparisonOperator.CONTAINS:
645
+ return String(valueA).includes(String(valueB));
646
+ case ComparisonOperator.STARTS_WITH:
647
+ return String(valueA).startsWith(String(valueB));
648
+ case ComparisonOperator.ENDS_WITH:
649
+ return String(valueA).endsWith(String(valueB));
650
+ case ComparisonOperator.IN:
651
+ return Array.isArray(valueB) && valueB.includes(valueA);
652
+ default:
653
+ throw new Error(`Unsupported operator: ${this.operator}`);
654
+ }
655
+ }
656
+ toJSON() {
657
+ return {
658
+ type: 'fieldToField',
659
+ fieldA: String(this.fieldA),
660
+ operator: this.operator,
661
+ fieldB: String(this.fieldB),
662
+ transformA: this.transformA,
663
+ transformB: this.transformB,
664
+ metadata: this.metadata,
665
+ };
666
+ }
667
+ static fromJSON(json, transformRegistry) {
668
+ return new FieldToFieldSpecification(json.fieldA, json.operator, json.fieldB, json.transformA, json.transformB, transformRegistry, json.metadata);
669
+ }
670
+ toDSL() {
671
+ const fieldAName = String(this.fieldA);
672
+ const fieldBName = String(this.fieldB);
673
+ let leftField = fieldAName;
674
+ let rightField = fieldBName;
675
+ // Apply transform notation
676
+ if (this.transformA && this.transformA.length > 0) {
677
+ leftField = `${fieldAName}.${this.transformA.join('.')}`;
678
+ }
679
+ if (this.transformB && this.transformB.length > 0) {
680
+ rightField = `${fieldBName}.${this.transformB.join('.')}`;
681
+ }
682
+ const symbol = OPERATOR_SYMBOLS[this.operator];
683
+ switch (this.operator) {
684
+ case ComparisonOperator.CONTAINS:
685
+ return `contains(${leftField}, ${rightField})`;
686
+ case ComparisonOperator.STARTS_WITH:
687
+ return `startsWith(${leftField}, ${rightField})`;
688
+ case ComparisonOperator.ENDS_WITH:
689
+ return `endsWith(${leftField}, ${rightField})`;
690
+ case ComparisonOperator.IN:
691
+ return `${leftField} in ${rightField}`;
692
+ default:
693
+ return `${leftField} ${symbol} ${rightField}`;
694
+ }
695
+ }
696
+ getFieldA() {
697
+ return this.fieldA;
698
+ }
699
+ getFieldB() {
700
+ return this.fieldB;
701
+ }
702
+ getOperator() {
703
+ return this.operator;
704
+ }
705
+ getTransformA() {
706
+ return this.transformA ? [...this.transformA] : undefined;
707
+ }
708
+ getTransformB() {
709
+ return this.transformB ? [...this.transformB] : undefined;
710
+ }
711
+ clone() {
712
+ return new FieldToFieldSpecification(this.fieldA, this.operator, this.fieldB, this.transformA ? [...this.transformA] : undefined, this.transformB ? [...this.transformB] : undefined, this.transformRegistry, this.metadata);
713
+ }
714
+ }
715
+
716
+ class DefaultContextProvider {
717
+ context;
718
+ constructor(context = {}) {
719
+ this.context = context;
720
+ }
721
+ getValue(path) {
722
+ return this.getNestedValue(this.context, path);
723
+ }
724
+ hasValue(path) {
725
+ try {
726
+ const value = this.getNestedValue(this.context, path);
727
+ return value !== undefined;
728
+ }
729
+ catch {
730
+ return false;
731
+ }
732
+ }
733
+ getNestedValue(obj, path) {
734
+ return path.split('.').reduce((current, key) => {
735
+ return current && current[key] !== undefined ? current[key] : undefined;
736
+ }, obj);
737
+ }
738
+ setContext(context) {
739
+ this.context = context;
740
+ }
741
+ updateContext(updates) {
742
+ this.context = { ...this.context, ...updates };
743
+ }
744
+ getContext() {
745
+ return { ...this.context };
746
+ }
747
+ }
748
+ class DateContextProvider {
749
+ getValue(path) {
750
+ switch (path) {
751
+ case 'now':
752
+ return new Date();
753
+ case 'today':
754
+ const today = new Date();
755
+ today.setHours(0, 0, 0, 0);
756
+ return today;
757
+ case 'timestamp':
758
+ return Date.now();
759
+ default:
760
+ return undefined;
761
+ }
762
+ }
763
+ hasValue(path) {
764
+ return ['now', 'today', 'timestamp'].includes(path);
765
+ }
766
+ }
767
+ class CompositeContextProvider {
768
+ providers;
769
+ constructor(providers) {
770
+ this.providers = providers;
771
+ }
772
+ getValue(path) {
773
+ for (const provider of this.providers) {
774
+ if (provider.hasValue(path)) {
775
+ return provider.getValue(path);
776
+ }
777
+ }
778
+ return undefined;
779
+ }
780
+ hasValue(path) {
781
+ return this.providers.some(provider => provider.hasValue(path));
782
+ }
783
+ addProvider(provider) {
784
+ this.providers.push(provider);
785
+ }
786
+ removeProvider(provider) {
787
+ const index = this.providers.indexOf(provider);
788
+ if (index > -1) {
789
+ this.providers.splice(index, 1);
790
+ }
791
+ }
792
+ }
793
+
794
+ class ContextualSpecification extends Specification {
795
+ template;
796
+ contextProvider;
797
+ static TOKEN_REGEX = /\$\{([^}]+)\}/g;
798
+ constructor(template, contextProvider, metadata) {
799
+ super(metadata);
800
+ this.template = template;
801
+ this.contextProvider = contextProvider;
802
+ }
803
+ isSatisfiedBy(obj) {
804
+ const resolvedExpression = this.resolveTokens(this.template, obj);
805
+ // This would need to be evaluated by the DSL parser
806
+ // For now, we'll throw an error indicating this needs the parser
807
+ throw new Error(`ContextualSpecification requires DSL parser to evaluate: ${resolvedExpression}`);
808
+ }
809
+ toJSON() {
810
+ return {
811
+ type: 'contextual',
812
+ template: this.template,
813
+ metadata: this.metadata,
814
+ };
815
+ }
816
+ static fromJSON(json, contextProvider) {
817
+ return new ContextualSpecification(json.template, contextProvider, json.metadata);
818
+ }
819
+ toDSL() {
820
+ return this.template;
821
+ }
822
+ resolveTokens(template, obj) {
823
+ const provider = this.contextProvider || new DefaultContextProvider();
824
+ return template.replace(ContextualSpecification.TOKEN_REGEX, (match, tokenPath) => {
825
+ // First try to resolve from context
826
+ if (provider.hasValue(tokenPath)) {
827
+ const value = provider.getValue(tokenPath);
828
+ return this.valueToString(value);
829
+ }
830
+ // Then try to resolve from object fields
831
+ if (tokenPath in obj) {
832
+ const value = obj[tokenPath];
833
+ return this.valueToString(value);
834
+ }
835
+ // If not found, return the original token
836
+ return match;
837
+ });
838
+ }
839
+ valueToString(value) {
840
+ if (value === null || value === undefined) {
841
+ return 'null';
842
+ }
843
+ if (typeof value === 'string') {
844
+ return `"${value}"`;
845
+ }
846
+ if (typeof value === 'number' || typeof value === 'boolean') {
847
+ return String(value);
848
+ }
849
+ if (value instanceof Date) {
850
+ return `"${value.toISOString()}"`;
851
+ }
852
+ return JSON.stringify(value);
853
+ }
854
+ getTemplate() {
855
+ return this.template;
856
+ }
857
+ getContextProvider() {
858
+ return this.contextProvider;
859
+ }
860
+ setContextProvider(provider) {
861
+ return new ContextualSpecification(this.template, provider, this.metadata);
862
+ }
863
+ getTokens() {
864
+ const tokens = [];
865
+ let match;
866
+ const regex = new RegExp(ContextualSpecification.TOKEN_REGEX);
867
+ while ((match = regex.exec(this.template)) !== null) {
868
+ tokens.push(match[1]);
869
+ }
870
+ return tokens;
871
+ }
872
+ clone() {
873
+ return new ContextualSpecification(this.template, this.contextProvider, this.metadata);
874
+ }
875
+ }
876
+
877
+ /**
878
+ * Types of conditional validation
879
+ */
880
+ var ConditionalType;
881
+ (function (ConditionalType) {
882
+ ConditionalType["REQUIRED_IF"] = "requiredIf";
883
+ ConditionalType["VISIBLE_IF"] = "visibleIf";
884
+ ConditionalType["DISABLED_IF"] = "disabledIf";
885
+ ConditionalType["READONLY_IF"] = "readonlyIf";
886
+ })(ConditionalType || (ConditionalType = {}));
887
+ /**
888
+ * Base class for conditional validators
889
+ */
890
+ class ConditionalSpecification extends Specification {
891
+ condition;
892
+ conditionalType;
893
+ constructor(condition, conditionalType, metadata) {
894
+ super(metadata);
895
+ this.condition = condition;
896
+ this.conditionalType = conditionalType;
897
+ }
898
+ getCondition() {
899
+ return this.condition;
900
+ }
901
+ getConditionalType() {
902
+ return this.conditionalType;
903
+ }
904
+ }
905
+ /**
906
+ * Validates that a field is required when a condition is met
907
+ */
908
+ class RequiredIfSpecification extends ConditionalSpecification {
909
+ field;
910
+ constructor(field, condition, metadata) {
911
+ super(condition, ConditionalType.REQUIRED_IF, metadata);
912
+ this.field = field;
913
+ }
914
+ isSatisfiedBy(obj) {
915
+ // If condition is not met, validation passes (field is not required)
916
+ if (!this.condition.isSatisfiedBy(obj)) {
917
+ return true;
918
+ }
919
+ // If condition is met, check if field has a value
920
+ const fieldValue = obj[this.field];
921
+ return fieldValue !== null && fieldValue !== undefined && fieldValue !== '';
922
+ }
923
+ toJSON() {
924
+ return {
925
+ type: 'requiredIf',
926
+ field: String(this.field),
927
+ condition: this.condition.toJSON(),
928
+ metadata: this.metadata,
929
+ };
930
+ }
931
+ static fromJSON(json) {
932
+ // This would need the SpecificationFactory to reconstruct the condition
933
+ throw new Error('Use SpecificationFactory.fromJSON() for proper reconstruction');
934
+ }
935
+ toDSL() {
936
+ return `requiredIf(${String(this.field)}, ${this.condition.toDSL()})`;
937
+ }
938
+ clone() {
939
+ return new RequiredIfSpecification(this.field, this.condition.clone(), this.metadata);
940
+ }
941
+ getField() {
942
+ return this.field;
943
+ }
944
+ }
945
+ /**
946
+ * Determines if a field should be visible based on a condition
947
+ */
948
+ class VisibleIfSpecification extends ConditionalSpecification {
949
+ field;
950
+ constructor(field, condition, metadata) {
951
+ super(condition, ConditionalType.VISIBLE_IF, metadata);
952
+ this.field = field;
953
+ }
954
+ isSatisfiedBy(obj) {
955
+ // This returns whether the field should be visible
956
+ return this.condition.isSatisfiedBy(obj);
957
+ }
958
+ toJSON() {
959
+ return {
960
+ type: 'visibleIf',
961
+ field: String(this.field),
962
+ condition: this.condition.toJSON(),
963
+ metadata: this.metadata,
964
+ };
965
+ }
966
+ static fromJSON(json) {
967
+ throw new Error('Use SpecificationFactory.fromJSON() for proper reconstruction');
968
+ }
969
+ toDSL() {
970
+ return `visibleIf(${String(this.field)}, ${this.condition.toDSL()})`;
971
+ }
972
+ clone() {
973
+ return new VisibleIfSpecification(this.field, this.condition.clone(), this.metadata);
974
+ }
975
+ getField() {
976
+ return this.field;
977
+ }
978
+ }
979
+ /**
980
+ * Determines if a field should be disabled based on a condition
981
+ */
982
+ class DisabledIfSpecification extends ConditionalSpecification {
983
+ field;
984
+ constructor(field, condition, metadata) {
985
+ super(condition, ConditionalType.DISABLED_IF, metadata);
986
+ this.field = field;
987
+ }
988
+ isSatisfiedBy(obj) {
989
+ // This returns whether the field should be disabled
990
+ return this.condition.isSatisfiedBy(obj);
991
+ }
992
+ toJSON() {
993
+ return {
994
+ type: 'disabledIf',
995
+ field: String(this.field),
996
+ condition: this.condition.toJSON(),
997
+ metadata: this.metadata,
998
+ };
999
+ }
1000
+ static fromJSON(json) {
1001
+ throw new Error('Use SpecificationFactory.fromJSON() for proper reconstruction');
1002
+ }
1003
+ toDSL() {
1004
+ return `disabledIf(${String(this.field)}, ${this.condition.toDSL()})`;
1005
+ }
1006
+ clone() {
1007
+ return new DisabledIfSpecification(this.field, this.condition.clone(), this.metadata);
1008
+ }
1009
+ getField() {
1010
+ return this.field;
1011
+ }
1012
+ }
1013
+ /**
1014
+ * Determines if a field should be readonly based on a condition
1015
+ */
1016
+ class ReadonlyIfSpecification extends ConditionalSpecification {
1017
+ field;
1018
+ constructor(field, condition, metadata) {
1019
+ super(condition, ConditionalType.READONLY_IF, metadata);
1020
+ this.field = field;
1021
+ }
1022
+ isSatisfiedBy(obj) {
1023
+ // This returns whether the field should be readonly
1024
+ return this.condition.isSatisfiedBy(obj);
1025
+ }
1026
+ toJSON() {
1027
+ return {
1028
+ type: 'readonlyIf',
1029
+ field: String(this.field),
1030
+ condition: this.condition.toJSON(),
1031
+ metadata: this.metadata,
1032
+ };
1033
+ }
1034
+ static fromJSON(json) {
1035
+ throw new Error('Use SpecificationFactory.fromJSON() for proper reconstruction');
1036
+ }
1037
+ toDSL() {
1038
+ return `readonlyIf(${String(this.field)}, ${this.condition.toDSL()})`;
1039
+ }
1040
+ clone() {
1041
+ return new ReadonlyIfSpecification(this.field, this.condition.clone(), this.metadata);
1042
+ }
1043
+ getField() {
1044
+ return this.field;
1045
+ }
1046
+ }
1047
+
1048
+ /**
1049
+ * Applies a specification to all elements in an array field
1050
+ */
1051
+ class ForEachSpecification extends Specification {
1052
+ arrayField;
1053
+ itemSpecification;
1054
+ constructor(arrayField, itemSpecification, metadata) {
1055
+ super(metadata);
1056
+ this.arrayField = arrayField;
1057
+ this.itemSpecification = itemSpecification;
1058
+ }
1059
+ isSatisfiedBy(obj) {
1060
+ const arrayValue = obj[this.arrayField];
1061
+ // If not an array, fail validation
1062
+ if (!Array.isArray(arrayValue)) {
1063
+ return false;
1064
+ }
1065
+ // Check if all items satisfy the specification
1066
+ return arrayValue.every((item) => {
1067
+ try {
1068
+ return this.itemSpecification.isSatisfiedBy(item);
1069
+ }
1070
+ catch (error) {
1071
+ // If item doesn't match expected structure, fail validation
1072
+ return false;
1073
+ }
1074
+ });
1075
+ }
1076
+ toJSON() {
1077
+ return {
1078
+ type: 'forEach',
1079
+ arrayField: String(this.arrayField),
1080
+ itemSpecification: this.itemSpecification.toJSON(),
1081
+ metadata: this.metadata,
1082
+ };
1083
+ }
1084
+ static fromJSON(json) {
1085
+ throw new Error('Use SpecificationFactory.fromJSON() for proper reconstruction');
1086
+ }
1087
+ toDSL() {
1088
+ return `forEach(${String(this.arrayField)}, ${this.itemSpecification.toDSL()})`;
1089
+ }
1090
+ clone() {
1091
+ return new ForEachSpecification(this.arrayField, this.itemSpecification.clone(), this.metadata);
1092
+ }
1093
+ getArrayField() {
1094
+ return this.arrayField;
1095
+ }
1096
+ getItemSpecification() {
1097
+ return this.itemSpecification;
1098
+ }
1099
+ /**
1100
+ * Gets items that fail the specification
1101
+ */
1102
+ getFailingItems(obj) {
1103
+ const arrayValue = obj[this.arrayField];
1104
+ if (!Array.isArray(arrayValue)) {
1105
+ return [];
1106
+ }
1107
+ const failingItems = [];
1108
+ arrayValue.forEach((item, index) => {
1109
+ if (!this.itemSpecification.isSatisfiedBy(item)) {
1110
+ failingItems.push({
1111
+ index,
1112
+ item,
1113
+ errors: [`Item at index ${index} failed validation`],
1114
+ });
1115
+ }
1116
+ });
1117
+ return failingItems;
1118
+ }
1119
+ }
1120
+ /**
1121
+ * Ensures all items in an array are unique based on a field or key selector
1122
+ */
1123
+ class UniqueBySpecification extends Specification {
1124
+ arrayField;
1125
+ keySelector;
1126
+ constructor(arrayField, keySelector, metadata) {
1127
+ super(metadata);
1128
+ this.arrayField = arrayField;
1129
+ this.keySelector = keySelector;
1130
+ }
1131
+ isSatisfiedBy(obj) {
1132
+ const arrayValue = obj[this.arrayField];
1133
+ // If not an array, pass validation (empty case)
1134
+ if (!Array.isArray(arrayValue)) {
1135
+ return true;
1136
+ }
1137
+ // If empty array, pass validation
1138
+ if (arrayValue.length === 0) {
1139
+ return true;
1140
+ }
1141
+ const seen = new Set();
1142
+ for (const item of arrayValue) {
1143
+ let key;
1144
+ if (typeof this.keySelector === 'string') {
1145
+ // Field name selector
1146
+ key = item?.[this.keySelector];
1147
+ }
1148
+ else {
1149
+ // Function selector
1150
+ key = this.keySelector(item);
1151
+ }
1152
+ // Convert key to string for Set comparison
1153
+ const keyStr = JSON.stringify(key);
1154
+ if (seen.has(keyStr)) {
1155
+ return false; // Duplicate found
1156
+ }
1157
+ seen.add(keyStr);
1158
+ }
1159
+ return true;
1160
+ }
1161
+ toJSON() {
1162
+ return {
1163
+ type: 'uniqueBy',
1164
+ arrayField: String(this.arrayField),
1165
+ keySelector: typeof this.keySelector === 'string' ? this.keySelector : '[Function]',
1166
+ metadata: this.metadata,
1167
+ };
1168
+ }
1169
+ static fromJSON(json) {
1170
+ return new UniqueBySpecification(json.arrayField, json.keySelector, json.metadata);
1171
+ }
1172
+ toDSL() {
1173
+ const selector = typeof this.keySelector === 'string'
1174
+ ? `"${this.keySelector}"`
1175
+ : '[Function]';
1176
+ return `uniqueBy(${String(this.arrayField)}, ${selector})`;
1177
+ }
1178
+ clone() {
1179
+ return new UniqueBySpecification(this.arrayField, this.keySelector, this.metadata);
1180
+ }
1181
+ getArrayField() {
1182
+ return this.arrayField;
1183
+ }
1184
+ getKeySelector() {
1185
+ return this.keySelector;
1186
+ }
1187
+ /**
1188
+ * Gets duplicate items found in the array
1189
+ */
1190
+ getDuplicates(obj) {
1191
+ const arrayValue = obj[this.arrayField];
1192
+ if (!Array.isArray(arrayValue)) {
1193
+ return [];
1194
+ }
1195
+ const keyMap = new Map();
1196
+ arrayValue.forEach((item, index) => {
1197
+ let key;
1198
+ if (typeof this.keySelector === 'string') {
1199
+ key = item?.[this.keySelector];
1200
+ }
1201
+ else {
1202
+ key = this.keySelector(item);
1203
+ }
1204
+ const keyStr = JSON.stringify(key);
1205
+ if (!keyMap.has(keyStr)) {
1206
+ keyMap.set(keyStr, []);
1207
+ }
1208
+ keyMap.get(keyStr).push({ index, item });
1209
+ });
1210
+ // Return only keys that have duplicates
1211
+ return Array.from(keyMap.entries())
1212
+ .filter(([_, items]) => items.length > 1)
1213
+ .map(([keyStr, items]) => ({
1214
+ key: JSON.parse(keyStr),
1215
+ items,
1216
+ }));
1217
+ }
1218
+ }
1219
+ /**
1220
+ * Validates that an array has a minimum number of elements
1221
+ */
1222
+ class MinLengthSpecification extends Specification {
1223
+ arrayField;
1224
+ minLength;
1225
+ constructor(arrayField, minLength, metadata) {
1226
+ super(metadata);
1227
+ this.arrayField = arrayField;
1228
+ this.minLength = minLength;
1229
+ }
1230
+ isSatisfiedBy(obj) {
1231
+ const arrayValue = obj[this.arrayField];
1232
+ if (!Array.isArray(arrayValue)) {
1233
+ return false;
1234
+ }
1235
+ return arrayValue.length >= this.minLength;
1236
+ }
1237
+ toJSON() {
1238
+ return {
1239
+ type: 'minLength',
1240
+ arrayField: String(this.arrayField),
1241
+ minLength: this.minLength,
1242
+ metadata: this.metadata,
1243
+ };
1244
+ }
1245
+ static fromJSON(json) {
1246
+ return new MinLengthSpecification(json.arrayField, json.minLength, json.metadata);
1247
+ }
1248
+ toDSL() {
1249
+ return `minLength(${String(this.arrayField)}, ${this.minLength})`;
1250
+ }
1251
+ clone() {
1252
+ return new MinLengthSpecification(this.arrayField, this.minLength, this.metadata);
1253
+ }
1254
+ getArrayField() {
1255
+ return this.arrayField;
1256
+ }
1257
+ getMinLength() {
1258
+ return this.minLength;
1259
+ }
1260
+ }
1261
+ /**
1262
+ * Validates that an array has a maximum number of elements
1263
+ */
1264
+ class MaxLengthSpecification extends Specification {
1265
+ arrayField;
1266
+ maxLength;
1267
+ constructor(arrayField, maxLength, metadata) {
1268
+ super(metadata);
1269
+ this.arrayField = arrayField;
1270
+ this.maxLength = maxLength;
1271
+ }
1272
+ isSatisfiedBy(obj) {
1273
+ const arrayValue = obj[this.arrayField];
1274
+ if (!Array.isArray(arrayValue)) {
1275
+ return true; // If not array, max length doesn't apply
1276
+ }
1277
+ return arrayValue.length <= this.maxLength;
1278
+ }
1279
+ toJSON() {
1280
+ return {
1281
+ type: 'maxLength',
1282
+ arrayField: String(this.arrayField),
1283
+ maxLength: this.maxLength,
1284
+ metadata: this.metadata,
1285
+ };
1286
+ }
1287
+ static fromJSON(json) {
1288
+ return new MaxLengthSpecification(json.arrayField, json.maxLength, json.metadata);
1289
+ }
1290
+ toDSL() {
1291
+ return `maxLength(${String(this.arrayField)}, ${this.maxLength})`;
1292
+ }
1293
+ clone() {
1294
+ return new MaxLengthSpecification(this.arrayField, this.maxLength, this.metadata);
1295
+ }
1296
+ getArrayField() {
1297
+ return this.arrayField;
1298
+ }
1299
+ getMaxLength() {
1300
+ return this.maxLength;
1301
+ }
1302
+ }
1303
+
1304
+ /**
1305
+ * Only applies the inner specification if the field is defined (not null, undefined, or empty)
1306
+ */
1307
+ class IfDefinedSpecification extends Specification {
1308
+ field;
1309
+ innerSpecification;
1310
+ constructor(field, innerSpecification, metadata) {
1311
+ super(metadata);
1312
+ this.field = field;
1313
+ this.innerSpecification = innerSpecification;
1314
+ }
1315
+ isSatisfiedBy(obj) {
1316
+ const fieldValue = obj[this.field];
1317
+ // If field is not defined, validation passes
1318
+ if (this.isUndefined(fieldValue)) {
1319
+ return true;
1320
+ }
1321
+ // If field is defined, apply the inner specification
1322
+ return this.innerSpecification.isSatisfiedBy(obj);
1323
+ }
1324
+ isUndefined(value) {
1325
+ return value === null || value === undefined || value === '';
1326
+ }
1327
+ toJSON() {
1328
+ return {
1329
+ type: 'ifDefined',
1330
+ field: String(this.field),
1331
+ specification: this.innerSpecification.toJSON(),
1332
+ metadata: this.metadata,
1333
+ };
1334
+ }
1335
+ static fromJSON(json) {
1336
+ throw new Error('Use SpecificationFactory.fromJSON() for proper reconstruction');
1337
+ }
1338
+ toDSL() {
1339
+ return `ifDefined(${String(this.field)}, ${this.innerSpecification.toDSL()})`;
1340
+ }
1341
+ clone() {
1342
+ return new IfDefinedSpecification(this.field, this.innerSpecification.clone(), this.metadata);
1343
+ }
1344
+ getField() {
1345
+ return this.field;
1346
+ }
1347
+ getInnerSpecification() {
1348
+ return this.innerSpecification;
1349
+ }
1350
+ }
1351
+ /**
1352
+ * Only applies the inner specification if the field value is not null
1353
+ */
1354
+ class IfNotNullSpecification extends Specification {
1355
+ field;
1356
+ innerSpecification;
1357
+ constructor(field, innerSpecification, metadata) {
1358
+ super(metadata);
1359
+ this.field = field;
1360
+ this.innerSpecification = innerSpecification;
1361
+ }
1362
+ isSatisfiedBy(obj) {
1363
+ const fieldValue = obj[this.field];
1364
+ // If field is null, validation passes
1365
+ if (fieldValue === null) {
1366
+ return true;
1367
+ }
1368
+ // If field is not null, apply the inner specification
1369
+ return this.innerSpecification.isSatisfiedBy(obj);
1370
+ }
1371
+ toJSON() {
1372
+ return {
1373
+ type: 'ifNotNull',
1374
+ field: String(this.field),
1375
+ specification: this.innerSpecification.toJSON(),
1376
+ metadata: this.metadata,
1377
+ };
1378
+ }
1379
+ static fromJSON(json) {
1380
+ throw new Error('Use SpecificationFactory.fromJSON() for proper reconstruction');
1381
+ }
1382
+ toDSL() {
1383
+ return `ifNotNull(${String(this.field)}, ${this.innerSpecification.toDSL()})`;
1384
+ }
1385
+ clone() {
1386
+ return new IfNotNullSpecification(this.field, this.innerSpecification.clone(), this.metadata);
1387
+ }
1388
+ getField() {
1389
+ return this.field;
1390
+ }
1391
+ getInnerSpecification() {
1392
+ return this.innerSpecification;
1393
+ }
1394
+ }
1395
+ /**
1396
+ * Only applies the inner specification if the field exists as a property on the object
1397
+ */
1398
+ class IfExistsSpecification extends Specification {
1399
+ field;
1400
+ innerSpecification;
1401
+ constructor(field, innerSpecification, metadata) {
1402
+ super(metadata);
1403
+ this.field = field;
1404
+ this.innerSpecification = innerSpecification;
1405
+ }
1406
+ isSatisfiedBy(obj) {
1407
+ // If field doesn't exist as a property, validation passes
1408
+ if (!(this.field in obj)) {
1409
+ return true;
1410
+ }
1411
+ // If field exists, apply the inner specification
1412
+ return this.innerSpecification.isSatisfiedBy(obj);
1413
+ }
1414
+ toJSON() {
1415
+ return {
1416
+ type: 'ifExists',
1417
+ field: String(this.field),
1418
+ specification: this.innerSpecification.toJSON(),
1419
+ metadata: this.metadata,
1420
+ };
1421
+ }
1422
+ static fromJSON(json) {
1423
+ throw new Error('Use SpecificationFactory.fromJSON() for proper reconstruction');
1424
+ }
1425
+ toDSL() {
1426
+ return `ifExists(${String(this.field)}, ${this.innerSpecification.toDSL()})`;
1427
+ }
1428
+ clone() {
1429
+ return new IfExistsSpecification(this.field, this.innerSpecification.clone(), this.metadata);
1430
+ }
1431
+ getField() {
1432
+ return this.field;
1433
+ }
1434
+ getInnerSpecification() {
1435
+ return this.innerSpecification;
1436
+ }
1437
+ }
1438
+ /**
1439
+ * Provides a default value for a field if it's undefined, then applies the specification
1440
+ */
1441
+ class WithDefaultSpecification extends Specification {
1442
+ field;
1443
+ defaultValue;
1444
+ innerSpecification;
1445
+ constructor(field, defaultValue, innerSpecification, metadata) {
1446
+ super(metadata);
1447
+ this.field = field;
1448
+ this.defaultValue = defaultValue;
1449
+ this.innerSpecification = innerSpecification;
1450
+ }
1451
+ isSatisfiedBy(obj) {
1452
+ // Create a copy of the object with default value if field is undefined
1453
+ const objWithDefault = { ...obj };
1454
+ if (this.isUndefined(obj[this.field])) {
1455
+ objWithDefault[this.field] = this.defaultValue;
1456
+ }
1457
+ return this.innerSpecification.isSatisfiedBy(objWithDefault);
1458
+ }
1459
+ isUndefined(value) {
1460
+ return value === null || value === undefined;
1461
+ }
1462
+ toJSON() {
1463
+ return {
1464
+ type: 'withDefault',
1465
+ field: String(this.field),
1466
+ defaultValue: this.defaultValue,
1467
+ specification: this.innerSpecification.toJSON(),
1468
+ metadata: this.metadata,
1469
+ };
1470
+ }
1471
+ static fromJSON(json) {
1472
+ throw new Error('Use SpecificationFactory.fromJSON() for proper reconstruction');
1473
+ }
1474
+ toDSL() {
1475
+ const defaultStr = JSON.stringify(this.defaultValue);
1476
+ return `withDefault(${String(this.field)}, ${defaultStr}, ${this.innerSpecification.toDSL()})`;
1477
+ }
1478
+ clone() {
1479
+ return new WithDefaultSpecification(this.field, this.defaultValue, this.innerSpecification.clone(), this.metadata);
1480
+ }
1481
+ getField() {
1482
+ return this.field;
1483
+ }
1484
+ getDefaultValue() {
1485
+ return this.defaultValue;
1486
+ }
1487
+ getInnerSpecification() {
1488
+ return this.innerSpecification;
1489
+ }
1490
+ }
1491
+
1492
+ /**
1493
+ * Form specification that manages validation rules for multiple fields
1494
+ */
1495
+ class FormSpecification extends Specification {
1496
+ fieldRules = new Map();
1497
+ globalValidations = [];
1498
+ constructor(metadata) {
1499
+ super(metadata);
1500
+ }
1501
+ /**
1502
+ * Adds validation rules for a specific field
1503
+ */
1504
+ addFieldRules(field, rules) {
1505
+ this.fieldRules.set(field, rules);
1506
+ return this;
1507
+ }
1508
+ /**
1509
+ * Adds a global validation rule that applies to the entire form
1510
+ */
1511
+ addGlobalValidation(specification) {
1512
+ this.globalValidations.push(specification);
1513
+ return this;
1514
+ }
1515
+ /**
1516
+ * Sets required condition for a field
1517
+ */
1518
+ setRequired(field, condition) {
1519
+ const existing = this.fieldRules.get(field) || {};
1520
+ existing.required = condition;
1521
+ this.fieldRules.set(field, existing);
1522
+ return this;
1523
+ }
1524
+ /**
1525
+ * Sets visibility condition for a field
1526
+ */
1527
+ setVisible(field, condition) {
1528
+ const existing = this.fieldRules.get(field) || {};
1529
+ existing.visible = condition;
1530
+ this.fieldRules.set(field, existing);
1531
+ return this;
1532
+ }
1533
+ /**
1534
+ * Sets disabled condition for a field
1535
+ */
1536
+ setDisabled(field, condition) {
1537
+ const existing = this.fieldRules.get(field) || {};
1538
+ existing.disabled = condition;
1539
+ this.fieldRules.set(field, existing);
1540
+ return this;
1541
+ }
1542
+ /**
1543
+ * Sets readonly condition for a field
1544
+ */
1545
+ setReadonly(field, condition) {
1546
+ const existing = this.fieldRules.get(field) || {};
1547
+ existing.readonly = condition;
1548
+ this.fieldRules.set(field, existing);
1549
+ return this;
1550
+ }
1551
+ /**
1552
+ * Basic satisfaction check - validates all rules
1553
+ */
1554
+ isSatisfiedBy(obj) {
1555
+ const result = this.validateForm(obj);
1556
+ return result.isValid;
1557
+ }
1558
+ /**
1559
+ * Comprehensive form validation with detailed results
1560
+ */
1561
+ validateForm(obj) {
1562
+ const result = {
1563
+ isValid: true,
1564
+ fields: {},
1565
+ globalErrors: [],
1566
+ warnings: [],
1567
+ };
1568
+ // Validate each field
1569
+ for (const [field, rules] of this.fieldRules.entries()) {
1570
+ const fieldResult = this.validateField(field, rules, obj);
1571
+ result.fields[String(field)] = fieldResult;
1572
+ if (!fieldResult.isValid) {
1573
+ result.isValid = false;
1574
+ }
1575
+ }
1576
+ // Validate global rules
1577
+ for (const globalSpec of this.globalValidations) {
1578
+ try {
1579
+ if (!globalSpec.isSatisfiedBy(obj)) {
1580
+ const metadata = globalSpec.getMetadata();
1581
+ const message = metadata?.message || 'Global validation failed';
1582
+ result.globalErrors.push(message);
1583
+ result.isValid = false;
1584
+ }
1585
+ }
1586
+ catch (error) {
1587
+ result.globalErrors.push(`Global validation error: ${error}`);
1588
+ result.isValid = false;
1589
+ }
1590
+ }
1591
+ return result;
1592
+ }
1593
+ validateField(field, rules, obj) {
1594
+ const fieldResult = {
1595
+ field: String(field),
1596
+ isValid: true,
1597
+ errors: [],
1598
+ warnings: [],
1599
+ metadata: rules.metadata,
1600
+ };
1601
+ // Check if field should be visible
1602
+ if (!this.isFieldVisible(field, rules, obj)) {
1603
+ // If field is not visible, skip validation
1604
+ return fieldResult;
1605
+ }
1606
+ // Check required validation
1607
+ if (rules.required) {
1608
+ const isRequired = typeof rules.required === 'boolean'
1609
+ ? rules.required
1610
+ : rules.required.isSatisfiedBy(obj);
1611
+ if (isRequired) {
1612
+ const fieldValue = obj[field];
1613
+ if (fieldValue === null ||
1614
+ fieldValue === undefined ||
1615
+ fieldValue === '') {
1616
+ fieldResult.errors.push(rules.metadata?.message || `${String(field)} is required`);
1617
+ fieldResult.isValid = false;
1618
+ }
1619
+ }
1620
+ }
1621
+ // Run validation specifications
1622
+ if (rules.validation) {
1623
+ for (const spec of rules.validation) {
1624
+ try {
1625
+ if (!spec.isSatisfiedBy(obj)) {
1626
+ const metadata = spec.getMetadata();
1627
+ const message = metadata?.message || `${String(field)} validation failed`;
1628
+ fieldResult.errors.push(message);
1629
+ fieldResult.isValid = false;
1630
+ }
1631
+ }
1632
+ catch (error) {
1633
+ fieldResult.errors.push(`Validation error for ${String(field)}: ${error}`);
1634
+ fieldResult.isValid = false;
1635
+ }
1636
+ }
1637
+ }
1638
+ return fieldResult;
1639
+ }
1640
+ /**
1641
+ * Checks if a field should be visible
1642
+ */
1643
+ isFieldVisible(field, rules, obj) {
1644
+ if (!rules?.visible) {
1645
+ return true; // Default to visible
1646
+ }
1647
+ return typeof rules.visible === 'boolean'
1648
+ ? rules.visible
1649
+ : rules.visible.isSatisfiedBy(obj);
1650
+ }
1651
+ /**
1652
+ * Checks if a field should be disabled
1653
+ */
1654
+ isFieldDisabled(field, obj) {
1655
+ const rules = this.fieldRules.get(field);
1656
+ if (!rules?.disabled) {
1657
+ return false; // Default to enabled
1658
+ }
1659
+ return typeof rules.disabled === 'boolean'
1660
+ ? rules.disabled
1661
+ : rules.disabled.isSatisfiedBy(obj);
1662
+ }
1663
+ /**
1664
+ * Checks if a field should be readonly
1665
+ */
1666
+ isFieldReadonly(field, obj) {
1667
+ const rules = this.fieldRules.get(field);
1668
+ if (!rules?.readonly) {
1669
+ return false; // Default to editable
1670
+ }
1671
+ return typeof rules.readonly === 'boolean'
1672
+ ? rules.readonly
1673
+ : rules.readonly.isSatisfiedBy(obj);
1674
+ }
1675
+ /**
1676
+ * Gets all configured fields
1677
+ */
1678
+ getFields() {
1679
+ return Array.from(this.fieldRules.keys());
1680
+ }
1681
+ /**
1682
+ * Gets rules for a specific field
1683
+ */
1684
+ getFieldRules(field) {
1685
+ return this.fieldRules.get(field);
1686
+ }
1687
+ toJSON() {
1688
+ const fieldsJson = {};
1689
+ for (const [field, rules] of this.fieldRules.entries()) {
1690
+ fieldsJson[String(field)] = {
1691
+ validation: rules.validation?.map((spec) => spec.toJSON()),
1692
+ required: typeof rules.required === 'object'
1693
+ ? rules.required.toJSON()
1694
+ : rules.required,
1695
+ visible: typeof rules.visible === 'object'
1696
+ ? rules.visible.toJSON()
1697
+ : rules.visible,
1698
+ disabled: typeof rules.disabled === 'object'
1699
+ ? rules.disabled.toJSON()
1700
+ : rules.disabled,
1701
+ readonly: typeof rules.readonly === 'object'
1702
+ ? rules.readonly.toJSON()
1703
+ : rules.readonly,
1704
+ metadata: rules.metadata,
1705
+ };
1706
+ }
1707
+ return {
1708
+ type: 'form',
1709
+ fields: fieldsJson,
1710
+ globalValidations: this.globalValidations.map((spec) => spec.toJSON()),
1711
+ metadata: this.metadata,
1712
+ };
1713
+ }
1714
+ static fromJSON(json) {
1715
+ throw new Error('FormSpecification.fromJSON not yet implemented - requires SpecificationFactory');
1716
+ }
1717
+ toDSL() {
1718
+ const parts = [];
1719
+ // Export field rules
1720
+ for (const [field, rules] of this.fieldRules.entries()) {
1721
+ if (rules.required && typeof rules.required === 'object') {
1722
+ parts.push(`requiredIf(${String(field)}, ${rules.required.toDSL()})`);
1723
+ }
1724
+ if (rules.visible && typeof rules.visible === 'object') {
1725
+ parts.push(`visibleIf(${String(field)}, ${rules.visible.toDSL()})`);
1726
+ }
1727
+ if (rules.disabled && typeof rules.disabled === 'object') {
1728
+ parts.push(`disabledIf(${String(field)}, ${rules.disabled.toDSL()})`);
1729
+ }
1730
+ if (rules.readonly && typeof rules.readonly === 'object') {
1731
+ parts.push(`readonlyIf(${String(field)}, ${rules.readonly.toDSL()})`);
1732
+ }
1733
+ if (rules.validation) {
1734
+ for (const spec of rules.validation) {
1735
+ parts.push(spec.toDSL());
1736
+ }
1737
+ }
1738
+ }
1739
+ // Export global validations
1740
+ for (const spec of this.globalValidations) {
1741
+ parts.push(spec.toDSL());
1742
+ }
1743
+ return parts.join(' && ');
1744
+ }
1745
+ clone() {
1746
+ const cloned = new FormSpecification(this.metadata);
1747
+ // Clone field rules
1748
+ for (const [field, rules] of this.fieldRules.entries()) {
1749
+ const clonedRules = {
1750
+ validation: rules.validation?.map((spec) => spec.clone()),
1751
+ required: typeof rules.required === 'object'
1752
+ ? rules.required.clone()
1753
+ : rules.required,
1754
+ visible: typeof rules.visible === 'object'
1755
+ ? rules.visible.clone()
1756
+ : rules.visible,
1757
+ disabled: typeof rules.disabled === 'object'
1758
+ ? rules.disabled.clone()
1759
+ : rules.disabled,
1760
+ readonly: typeof rules.readonly === 'object'
1761
+ ? rules.readonly.clone()
1762
+ : rules.readonly,
1763
+ metadata: rules.metadata,
1764
+ };
1765
+ cloned.fieldRules.set(field, clonedRules);
1766
+ }
1767
+ // Clone global validations
1768
+ cloned.globalValidations = this.globalValidations.map((spec) => spec.clone());
1769
+ return cloned;
1770
+ }
1771
+ }
1772
+
1773
+ var TokenType;
1774
+ (function (TokenType) {
1775
+ // Literals
1776
+ TokenType["IDENTIFIER"] = "IDENTIFIER";
1777
+ TokenType["STRING"] = "STRING";
1778
+ TokenType["NUMBER"] = "NUMBER";
1779
+ TokenType["BOOLEAN"] = "BOOLEAN";
1780
+ TokenType["NULL"] = "NULL";
1781
+ // Operators
1782
+ TokenType["AND"] = "AND";
1783
+ TokenType["OR"] = "OR";
1784
+ TokenType["NOT"] = "NOT";
1785
+ TokenType["XOR"] = "XOR";
1786
+ TokenType["IMPLIES"] = "IMPLIES";
1787
+ // Comparison operators
1788
+ TokenType["EQUALS"] = "EQUALS";
1789
+ TokenType["NOT_EQUALS"] = "NOT_EQUALS";
1790
+ TokenType["LESS_THAN"] = "LESS_THAN";
1791
+ TokenType["LESS_THAN_OR_EQUAL"] = "LESS_THAN_OR_EQUAL";
1792
+ TokenType["GREATER_THAN"] = "GREATER_THAN";
1793
+ TokenType["GREATER_THAN_OR_EQUAL"] = "GREATER_THAN_OR_EQUAL";
1794
+ TokenType["IN"] = "IN";
1795
+ // Function tokens
1796
+ TokenType["CONTAINS"] = "CONTAINS";
1797
+ TokenType["STARTS_WITH"] = "STARTS_WITH";
1798
+ TokenType["ENDS_WITH"] = "ENDS_WITH";
1799
+ TokenType["AT_LEAST"] = "AT_LEAST";
1800
+ TokenType["EXACTLY"] = "EXACTLY";
1801
+ // Punctuation
1802
+ TokenType["LEFT_PAREN"] = "LEFT_PAREN";
1803
+ TokenType["RIGHT_PAREN"] = "RIGHT_PAREN";
1804
+ TokenType["LEFT_BRACKET"] = "LEFT_BRACKET";
1805
+ TokenType["RIGHT_BRACKET"] = "RIGHT_BRACKET";
1806
+ TokenType["COMMA"] = "COMMA";
1807
+ TokenType["DOT"] = "DOT";
1808
+ // Special
1809
+ TokenType["FIELD_REFERENCE"] = "FIELD_REFERENCE";
1810
+ TokenType["EOF"] = "EOF";
1811
+ TokenType["WHITESPACE"] = "WHITESPACE";
1812
+ })(TokenType || (TokenType = {}));
1813
+ const OPERATOR_KEYWORDS = {
1814
+ 'and': TokenType.AND,
1815
+ '&&': TokenType.AND,
1816
+ 'or': TokenType.OR,
1817
+ '||': TokenType.OR,
1818
+ 'not': TokenType.NOT,
1819
+ '!': TokenType.NOT,
1820
+ 'xor': TokenType.XOR,
1821
+ 'implies': TokenType.IMPLIES,
1822
+ 'in': TokenType.IN,
1823
+ 'contains': TokenType.CONTAINS,
1824
+ 'startsWith': TokenType.STARTS_WITH,
1825
+ 'endsWith': TokenType.ENDS_WITH,
1826
+ 'atLeast': TokenType.AT_LEAST,
1827
+ 'exactly': TokenType.EXACTLY,
1828
+ 'true': TokenType.BOOLEAN,
1829
+ 'false': TokenType.BOOLEAN,
1830
+ 'null': TokenType.NULL
1831
+ };
1832
+ const COMPARISON_OPERATORS = {
1833
+ '==': TokenType.EQUALS,
1834
+ '!=': TokenType.NOT_EQUALS,
1835
+ '<': TokenType.LESS_THAN,
1836
+ '<=': TokenType.LESS_THAN_OR_EQUAL,
1837
+ '>': TokenType.GREATER_THAN,
1838
+ '>=': TokenType.GREATER_THAN_OR_EQUAL
1839
+ };
1840
+
1841
+ class DslTokenizer {
1842
+ input;
1843
+ position = 0;
1844
+ line = 1;
1845
+ column = 1;
1846
+ constructor(input) {
1847
+ this.input = input;
1848
+ }
1849
+ tokenize() {
1850
+ const tokens = [];
1851
+ while (this.position < this.input.length) {
1852
+ const token = this.nextToken();
1853
+ if (token.type !== TokenType.WHITESPACE) {
1854
+ tokens.push(token);
1855
+ }
1856
+ }
1857
+ tokens.push({
1858
+ type: TokenType.EOF,
1859
+ value: '',
1860
+ position: this.position,
1861
+ line: this.line,
1862
+ column: this.column
1863
+ });
1864
+ return tokens;
1865
+ }
1866
+ nextToken() {
1867
+ this.skipWhitespace();
1868
+ if (this.position >= this.input.length) {
1869
+ return this.createToken(TokenType.EOF, '');
1870
+ }
1871
+ const char = this.input[this.position];
1872
+ // Field references ${...}
1873
+ if (char === '$' && this.peek() === '{') {
1874
+ return this.readFieldReference();
1875
+ }
1876
+ // String literals
1877
+ if (char === '"' || char === "'") {
1878
+ return this.readString(char);
1879
+ }
1880
+ // Numbers
1881
+ if (this.isDigit(char) || (char === '-' && this.isDigit(this.peek()))) {
1882
+ return this.readNumber();
1883
+ }
1884
+ // Multi-character operators
1885
+ const twoChar = this.input.substr(this.position, 2);
1886
+ if (COMPARISON_OPERATORS[twoChar]) {
1887
+ const token = this.createToken(COMPARISON_OPERATORS[twoChar], twoChar);
1888
+ this.advance(2);
1889
+ return token;
1890
+ }
1891
+ // Single character operators and punctuation
1892
+ switch (char) {
1893
+ case '(':
1894
+ return this.createTokenAndAdvance(TokenType.LEFT_PAREN, char);
1895
+ case ')':
1896
+ return this.createTokenAndAdvance(TokenType.RIGHT_PAREN, char);
1897
+ case '[':
1898
+ return this.createTokenAndAdvance(TokenType.LEFT_BRACKET, char);
1899
+ case ']':
1900
+ return this.createTokenAndAdvance(TokenType.RIGHT_BRACKET, char);
1901
+ case ',':
1902
+ return this.createTokenAndAdvance(TokenType.COMMA, char);
1903
+ case '.':
1904
+ return this.createTokenAndAdvance(TokenType.DOT, char);
1905
+ case '!':
1906
+ if (this.peek() === '=') {
1907
+ const token = this.createToken(TokenType.NOT_EQUALS, '!=');
1908
+ this.advance(2);
1909
+ return token;
1910
+ }
1911
+ return this.createTokenAndAdvance(TokenType.NOT, char);
1912
+ case '<':
1913
+ if (this.peek() === '=') {
1914
+ const token = this.createToken(TokenType.LESS_THAN_OR_EQUAL, '<=');
1915
+ this.advance(2);
1916
+ return token;
1917
+ }
1918
+ return this.createTokenAndAdvance(TokenType.LESS_THAN, char);
1919
+ case '>':
1920
+ if (this.peek() === '=') {
1921
+ const token = this.createToken(TokenType.GREATER_THAN_OR_EQUAL, '>=');
1922
+ this.advance(2);
1923
+ return token;
1924
+ }
1925
+ return this.createTokenAndAdvance(TokenType.GREATER_THAN, char);
1926
+ case '=':
1927
+ if (this.peek() === '=') {
1928
+ const token = this.createToken(TokenType.EQUALS, '==');
1929
+ this.advance(2);
1930
+ return token;
1931
+ }
1932
+ break;
1933
+ case '&':
1934
+ if (this.peek() === '&') {
1935
+ const token = this.createToken(TokenType.AND, '&&');
1936
+ this.advance(2);
1937
+ return token;
1938
+ }
1939
+ break;
1940
+ case '|':
1941
+ if (this.peek() === '|') {
1942
+ const token = this.createToken(TokenType.OR, '||');
1943
+ this.advance(2);
1944
+ return token;
1945
+ }
1946
+ break;
1947
+ }
1948
+ // Identifiers and keywords
1949
+ if (this.isAlpha(char) || char === '_') {
1950
+ return this.readIdentifier();
1951
+ }
1952
+ throw new Error(`Unexpected character '${char}' at position ${this.position}`);
1953
+ }
1954
+ readFieldReference() {
1955
+ const start = this.position;
1956
+ this.advance(2); // Skip ${
1957
+ let value = '';
1958
+ while (this.position < this.input.length && this.input[this.position] !== '}') {
1959
+ value += this.input[this.position];
1960
+ this.advance();
1961
+ }
1962
+ if (this.position >= this.input.length) {
1963
+ throw new Error('Unterminated field reference');
1964
+ }
1965
+ this.advance(); // Skip }
1966
+ return this.createToken(TokenType.FIELD_REFERENCE, value, start);
1967
+ }
1968
+ readString(quote) {
1969
+ const start = this.position;
1970
+ this.advance(); // Skip opening quote
1971
+ let value = '';
1972
+ while (this.position < this.input.length && this.input[this.position] !== quote) {
1973
+ if (this.input[this.position] === '\\') {
1974
+ this.advance();
1975
+ if (this.position < this.input.length) {
1976
+ const escaped = this.input[this.position];
1977
+ switch (escaped) {
1978
+ case 'n':
1979
+ value += '\n';
1980
+ break;
1981
+ case 't':
1982
+ value += '\t';
1983
+ break;
1984
+ case 'r':
1985
+ value += '\r';
1986
+ break;
1987
+ case '\\':
1988
+ value += '\\';
1989
+ break;
1990
+ case '"':
1991
+ value += '"';
1992
+ break;
1993
+ case "'":
1994
+ value += "'";
1995
+ break;
1996
+ default:
1997
+ value += escaped;
1998
+ break;
1999
+ }
2000
+ this.advance();
2001
+ }
2002
+ }
2003
+ else {
2004
+ value += this.input[this.position];
2005
+ this.advance();
2006
+ }
2007
+ }
2008
+ if (this.position >= this.input.length) {
2009
+ throw new Error('Unterminated string literal');
2010
+ }
2011
+ this.advance(); // Skip closing quote
2012
+ return this.createToken(TokenType.STRING, value, start);
2013
+ }
2014
+ readNumber() {
2015
+ const start = this.position;
2016
+ let value = '';
2017
+ if (this.input[this.position] === '-') {
2018
+ value += this.input[this.position];
2019
+ this.advance();
2020
+ }
2021
+ while (this.position < this.input.length && this.isDigit(this.input[this.position])) {
2022
+ value += this.input[this.position];
2023
+ this.advance();
2024
+ }
2025
+ if (this.position < this.input.length && this.input[this.position] === '.') {
2026
+ value += this.input[this.position];
2027
+ this.advance();
2028
+ while (this.position < this.input.length && this.isDigit(this.input[this.position])) {
2029
+ value += this.input[this.position];
2030
+ this.advance();
2031
+ }
2032
+ }
2033
+ return this.createToken(TokenType.NUMBER, value, start);
2034
+ }
2035
+ readIdentifier() {
2036
+ const start = this.position;
2037
+ let value = '';
2038
+ while (this.position < this.input.length &&
2039
+ (this.isAlphaNumeric(this.input[this.position]) || this.input[this.position] === '_')) {
2040
+ value += this.input[this.position];
2041
+ this.advance();
2042
+ }
2043
+ const tokenType = OPERATOR_KEYWORDS[value] || TokenType.IDENTIFIER;
2044
+ return this.createToken(tokenType, value, start);
2045
+ }
2046
+ skipWhitespace() {
2047
+ while (this.position < this.input.length && this.isWhitespace(this.input[this.position])) {
2048
+ if (this.input[this.position] === '\n') {
2049
+ this.line++;
2050
+ this.column = 1;
2051
+ }
2052
+ else {
2053
+ this.column++;
2054
+ }
2055
+ this.position++;
2056
+ }
2057
+ }
2058
+ createToken(type, value, startPos) {
2059
+ return {
2060
+ type,
2061
+ value,
2062
+ position: startPos ?? this.position,
2063
+ line: this.line,
2064
+ column: this.column - value.length
2065
+ };
2066
+ }
2067
+ createTokenAndAdvance(type, value) {
2068
+ const token = this.createToken(type, value);
2069
+ this.advance();
2070
+ return token;
2071
+ }
2072
+ advance(count = 1) {
2073
+ for (let i = 0; i < count && this.position < this.input.length; i++) {
2074
+ if (this.input[this.position] === '\n') {
2075
+ this.line++;
2076
+ this.column = 1;
2077
+ }
2078
+ else {
2079
+ this.column++;
2080
+ }
2081
+ this.position++;
2082
+ }
2083
+ }
2084
+ peek(offset = 1) {
2085
+ const pos = this.position + offset;
2086
+ return pos < this.input.length ? this.input[pos] : '';
2087
+ }
2088
+ isDigit(char) {
2089
+ return char >= '0' && char <= '9';
2090
+ }
2091
+ isAlpha(char) {
2092
+ return (char >= 'a' && char <= 'z') || (char >= 'A' && char <= 'Z');
2093
+ }
2094
+ isAlphaNumeric(char) {
2095
+ return this.isAlpha(char) || this.isDigit(char);
2096
+ }
2097
+ isWhitespace(char) {
2098
+ return char === ' ' || char === '\t' || char === '\n' || char === '\r';
2099
+ }
2100
+ }
2101
+
2102
+ class DslParser {
2103
+ functionRegistry;
2104
+ tokens = [];
2105
+ current = 0;
2106
+ constructor(functionRegistry) {
2107
+ this.functionRegistry = functionRegistry;
2108
+ }
2109
+ parse(input) {
2110
+ const tokenizer = new DslTokenizer(input);
2111
+ this.tokens = tokenizer.tokenize();
2112
+ this.current = 0;
2113
+ const spec = this.parseExpression();
2114
+ if (!this.isAtEnd()) {
2115
+ throw new Error(`Unexpected token: ${this.peek().value}`);
2116
+ }
2117
+ return spec;
2118
+ }
2119
+ parseExpression() {
2120
+ return this.parseImplies();
2121
+ }
2122
+ parseImplies() {
2123
+ let expr = this.parseXor();
2124
+ while (this.match(TokenType.IMPLIES)) {
2125
+ const right = this.parseXor();
2126
+ expr = new ImpliesSpecification(expr, right);
2127
+ }
2128
+ return expr;
2129
+ }
2130
+ parseXor() {
2131
+ let expr = this.parseOr();
2132
+ while (this.match(TokenType.XOR)) {
2133
+ const right = this.parseOr();
2134
+ if (expr instanceof XorSpecification) {
2135
+ expr = new XorSpecification([...expr.getSpecifications(), right]);
2136
+ }
2137
+ else {
2138
+ expr = new XorSpecification([expr, right]);
2139
+ }
2140
+ }
2141
+ return expr;
2142
+ }
2143
+ parseOr() {
2144
+ let expr = this.parseAnd();
2145
+ while (this.match(TokenType.OR)) {
2146
+ const right = this.parseAnd();
2147
+ if (expr instanceof OrSpecification) {
2148
+ expr = new OrSpecification([...expr.getSpecifications(), right]);
2149
+ }
2150
+ else {
2151
+ expr = new OrSpecification([expr, right]);
2152
+ }
2153
+ }
2154
+ return expr;
2155
+ }
2156
+ parseAnd() {
2157
+ let expr = this.parseUnary();
2158
+ while (this.match(TokenType.AND)) {
2159
+ const right = this.parseUnary();
2160
+ if (expr instanceof AndSpecification) {
2161
+ expr = new AndSpecification([...expr.getSpecifications(), right]);
2162
+ }
2163
+ else {
2164
+ expr = new AndSpecification([expr, right]);
2165
+ }
2166
+ }
2167
+ return expr;
2168
+ }
2169
+ parseUnary() {
2170
+ if (this.match(TokenType.NOT)) {
2171
+ const expr = this.parseUnary();
2172
+ return new NotSpecification(expr);
2173
+ }
2174
+ return this.parseComparison();
2175
+ }
2176
+ parseComparison() {
2177
+ let left = this.parsePrimary();
2178
+ if (this.matchComparison()) {
2179
+ const operator = this.previous().type;
2180
+ // Parse RHS allowing literals, arrays and identifiers (as values)
2181
+ const rightValue = this.parseArgument();
2182
+ // Field to field comparison not implemented
2183
+ if (left instanceof FieldSpecification && rightValue instanceof FieldSpecification) {
2184
+ throw new Error('Field to field comparison not yet implemented in parser');
2185
+ }
2186
+ if (left instanceof FieldSpecification) {
2187
+ const field = left.getField();
2188
+ const compOp = this.tokenTypeToComparisonOperator(operator);
2189
+ return new FieldSpecification(field, compOp, rightValue);
2190
+ }
2191
+ throw new Error('Invalid comparison expression');
2192
+ }
2193
+ return left;
2194
+ }
2195
+ parsePrimary() {
2196
+ if (this.match(TokenType.LEFT_PAREN)) {
2197
+ const expr = this.parseExpression();
2198
+ this.consume(TokenType.RIGHT_PAREN, "Expected ')' after expression");
2199
+ return expr;
2200
+ }
2201
+ if (this.match(TokenType.FIELD_REFERENCE)) {
2202
+ const fieldName = this.previous().value;
2203
+ return new FieldSpecification(fieldName, ComparisonOperator.EQUALS, true);
2204
+ }
2205
+ if (this.match(TokenType.IDENTIFIER)) {
2206
+ const identifier = this.previous().value;
2207
+ // Check if this is a function call
2208
+ if (this.match(TokenType.LEFT_PAREN)) {
2209
+ return this.parseFunctionCall(identifier);
2210
+ }
2211
+ // Otherwise treat as field reference
2212
+ return new FieldSpecification(identifier, ComparisonOperator.EQUALS, true);
2213
+ }
2214
+ throw new Error(`Unexpected token: ${this.peek().value}`);
2215
+ }
2216
+ parseFunctionCall(functionName) {
2217
+ const args = [];
2218
+ if (!this.check(TokenType.RIGHT_PAREN)) {
2219
+ do {
2220
+ args.push(this.parseArgument());
2221
+ } while (this.match(TokenType.COMMA));
2222
+ }
2223
+ this.consume(TokenType.RIGHT_PAREN, "Expected ')' after function arguments");
2224
+ // Handle special built-in functions
2225
+ switch (functionName) {
2226
+ case 'atLeast':
2227
+ if (args.length !== 2) {
2228
+ throw new Error('atLeast requires exactly 2 arguments');
2229
+ }
2230
+ const min = args[0];
2231
+ const specs = args[1];
2232
+ if (!Array.isArray(specs)) {
2233
+ throw new Error('atLeast second argument must be an array of specifications');
2234
+ }
2235
+ return new AtLeastSpecification(min, specs);
2236
+ case 'exactly':
2237
+ if (args.length !== 2) {
2238
+ throw new Error('exactly requires exactly 2 arguments');
2239
+ }
2240
+ const exact = args[0];
2241
+ const exactSpecs = args[1];
2242
+ if (!Array.isArray(exactSpecs)) {
2243
+ throw new Error('exactly second argument must be an array of specifications');
2244
+ }
2245
+ return new ExactlySpecification(exact, exactSpecs);
2246
+ case 'contains':
2247
+ case 'startsWith':
2248
+ case 'endsWith':
2249
+ if (args.length !== 2) {
2250
+ throw new Error(`${functionName} requires exactly 2 arguments`);
2251
+ }
2252
+ const field = args[0];
2253
+ const value = args[1];
2254
+ const op = this.functionNameToOperator(functionName);
2255
+ return new FieldSpecification(field, op, value);
2256
+ default:
2257
+ return new FunctionSpecification(functionName, args, this.functionRegistry);
2258
+ }
2259
+ }
2260
+ parseArgument() {
2261
+ if (this.match(TokenType.STRING)) {
2262
+ return this.previous().value;
2263
+ }
2264
+ if (this.match(TokenType.NUMBER)) {
2265
+ const value = this.previous().value;
2266
+ return value.includes('.') ? parseFloat(value) : parseInt(value, 10);
2267
+ }
2268
+ if (this.match(TokenType.BOOLEAN)) {
2269
+ return this.previous().value === 'true';
2270
+ }
2271
+ if (this.match(TokenType.NULL)) {
2272
+ return null;
2273
+ }
2274
+ if (this.match(TokenType.FIELD_REFERENCE)) {
2275
+ return this.previous().value;
2276
+ }
2277
+ if (this.match(TokenType.IDENTIFIER)) {
2278
+ return this.previous().value;
2279
+ }
2280
+ if (this.match(TokenType.LEFT_BRACKET)) {
2281
+ const elements = [];
2282
+ if (!this.check(TokenType.RIGHT_BRACKET)) {
2283
+ do {
2284
+ elements.push(this.parseArgument());
2285
+ } while (this.match(TokenType.COMMA));
2286
+ }
2287
+ this.consume(TokenType.RIGHT_BRACKET, "Expected ']' after array elements");
2288
+ return elements;
2289
+ }
2290
+ throw new Error(`Unexpected token in argument: ${this.peek().value}`);
2291
+ }
2292
+ matchComparison() {
2293
+ return this.match(TokenType.EQUALS, TokenType.NOT_EQUALS, TokenType.LESS_THAN, TokenType.LESS_THAN_OR_EQUAL, TokenType.GREATER_THAN, TokenType.GREATER_THAN_OR_EQUAL, TokenType.IN);
2294
+ }
2295
+ tokenTypeToComparisonOperator(tokenType) {
2296
+ switch (tokenType) {
2297
+ case TokenType.EQUALS:
2298
+ return ComparisonOperator.EQUALS;
2299
+ case TokenType.NOT_EQUALS:
2300
+ return ComparisonOperator.NOT_EQUALS;
2301
+ case TokenType.LESS_THAN:
2302
+ return ComparisonOperator.LESS_THAN;
2303
+ case TokenType.LESS_THAN_OR_EQUAL:
2304
+ return ComparisonOperator.LESS_THAN_OR_EQUAL;
2305
+ case TokenType.GREATER_THAN:
2306
+ return ComparisonOperator.GREATER_THAN;
2307
+ case TokenType.GREATER_THAN_OR_EQUAL:
2308
+ return ComparisonOperator.GREATER_THAN_OR_EQUAL;
2309
+ case TokenType.IN:
2310
+ return ComparisonOperator.IN;
2311
+ default:
2312
+ throw new Error(`Unknown comparison operator: ${tokenType}`);
2313
+ }
2314
+ }
2315
+ functionNameToOperator(functionName) {
2316
+ switch (functionName) {
2317
+ case 'contains':
2318
+ return ComparisonOperator.CONTAINS;
2319
+ case 'startsWith':
2320
+ return ComparisonOperator.STARTS_WITH;
2321
+ case 'endsWith':
2322
+ return ComparisonOperator.ENDS_WITH;
2323
+ default:
2324
+ throw new Error(`Unknown function: ${functionName}`);
2325
+ }
2326
+ }
2327
+ extractValue(spec) {
2328
+ // This is a simplified extraction - in a real implementation
2329
+ // we'd need to handle more complex cases
2330
+ if (spec instanceof FieldSpecification) {
2331
+ return spec.getValue();
2332
+ }
2333
+ return spec;
2334
+ }
2335
+ match(...types) {
2336
+ for (const type of types) {
2337
+ if (this.check(type)) {
2338
+ this.advance();
2339
+ return true;
2340
+ }
2341
+ }
2342
+ return false;
2343
+ }
2344
+ check(type) {
2345
+ if (this.isAtEnd())
2346
+ return false;
2347
+ return this.peek().type === type;
2348
+ }
2349
+ advance() {
2350
+ if (!this.isAtEnd())
2351
+ this.current++;
2352
+ return this.previous();
2353
+ }
2354
+ isAtEnd() {
2355
+ return this.peek().type === TokenType.EOF;
2356
+ }
2357
+ peek() {
2358
+ return this.tokens[this.current];
2359
+ }
2360
+ previous() {
2361
+ return this.tokens[this.current - 1];
2362
+ }
2363
+ consume(type, message) {
2364
+ if (this.check(type))
2365
+ return this.advance();
2366
+ throw new Error(`${message}. Got: ${this.peek().value}`);
2367
+ }
2368
+ }
2369
+
2370
+ class DslExporter {
2371
+ options;
2372
+ constructor(options = {}) {
2373
+ this.options = {
2374
+ prettyPrint: options.prettyPrint ?? false,
2375
+ indentSize: options.indentSize ?? 2,
2376
+ maxLineLength: options.maxLineLength ?? 80,
2377
+ useParentheses: options.useParentheses ?? 'auto',
2378
+ includeMetadata: options.includeMetadata ?? false,
2379
+ metadataPosition: options.metadataPosition ?? 'before',
2380
+ };
2381
+ }
2382
+ export(specification) {
2383
+ const dsl = this.exportSpecification(specification, 0);
2384
+ if (this.options.prettyPrint) {
2385
+ return this.formatPretty(dsl);
2386
+ }
2387
+ return dsl;
2388
+ }
2389
+ /**
2390
+ * Exports specification with metadata comments
2391
+ */
2392
+ exportWithMetadata(specification) {
2393
+ const originalIncludeMetadata = this.options.includeMetadata;
2394
+ this.options.includeMetadata = true;
2395
+ const result = this.export(specification);
2396
+ this.options.includeMetadata = originalIncludeMetadata;
2397
+ return result;
2398
+ }
2399
+ exportSpecification(spec, depth) {
2400
+ const baseDsl = this.getSpecificationDsl(spec, depth);
2401
+ if (!this.options.includeMetadata) {
2402
+ return baseDsl;
2403
+ }
2404
+ return this.addMetadataComments(spec, baseDsl, depth);
2405
+ }
2406
+ getSpecificationDsl(spec, depth) {
2407
+ if (spec instanceof FieldSpecification) {
2408
+ return this.exportFieldSpecification(spec);
2409
+ }
2410
+ if (spec instanceof AndSpecification) {
2411
+ return this.exportAndSpecification(spec, depth);
2412
+ }
2413
+ if (spec instanceof OrSpecification) {
2414
+ return this.exportOrSpecification(spec, depth);
2415
+ }
2416
+ if (spec instanceof NotSpecification) {
2417
+ return this.exportNotSpecification(spec, depth);
2418
+ }
2419
+ if (spec instanceof XorSpecification) {
2420
+ return this.exportXorSpecification(spec, depth);
2421
+ }
2422
+ if (spec instanceof ImpliesSpecification) {
2423
+ return this.exportImpliesSpecification(spec, depth);
2424
+ }
2425
+ if (spec instanceof FunctionSpecification) {
2426
+ return this.exportFunctionSpecification(spec);
2427
+ }
2428
+ if (spec instanceof AtLeastSpecification) {
2429
+ return this.exportAtLeastSpecification(spec, depth);
2430
+ }
2431
+ if (spec instanceof ExactlySpecification) {
2432
+ return this.exportExactlySpecification(spec, depth);
2433
+ }
2434
+ if (spec instanceof FieldToFieldSpecification) {
2435
+ return this.exportFieldToFieldSpecification(spec);
2436
+ }
2437
+ if (spec instanceof ContextualSpecification) {
2438
+ return this.exportContextualSpecification(spec);
2439
+ }
2440
+ // Fallback to toDSL method
2441
+ return spec.toDSL();
2442
+ }
2443
+ addMetadataComments(spec, dsl, depth) {
2444
+ const metadata = spec.getMetadata();
2445
+ if (!metadata) {
2446
+ return dsl;
2447
+ }
2448
+ const indent = ' '.repeat(depth * this.options.indentSize);
2449
+ const comments = [];
2450
+ // Add metadata comments
2451
+ if (metadata.code) {
2452
+ comments.push(`${indent}// code: ${metadata.code}`);
2453
+ }
2454
+ if (metadata.message) {
2455
+ comments.push(`${indent}// message: ${metadata.message}`);
2456
+ }
2457
+ if (metadata.tag) {
2458
+ comments.push(`${indent}// tag: ${metadata.tag}`);
2459
+ }
2460
+ if (metadata.uiConfig) {
2461
+ const uiConfigStr = JSON.stringify(metadata.uiConfig);
2462
+ comments.push(`${indent}// uiConfig: ${uiConfigStr}`);
2463
+ }
2464
+ // Add any other metadata properties
2465
+ const standardKeys = ['code', 'message', 'tag', 'uiConfig'];
2466
+ for (const [key, value] of Object.entries(metadata)) {
2467
+ if (!standardKeys.includes(key) && value !== undefined) {
2468
+ comments.push(`${indent}// ${key}: ${JSON.stringify(value)}`);
2469
+ }
2470
+ }
2471
+ if (comments.length === 0) {
2472
+ return dsl;
2473
+ }
2474
+ switch (this.options.metadataPosition) {
2475
+ case 'before':
2476
+ return `${comments.join('\n')}\n${indent}${dsl}`;
2477
+ case 'after':
2478
+ return `${indent}${dsl}\n${comments.join('\n')}`;
2479
+ case 'inline':
2480
+ const firstComment = comments[0] ? ` ${comments[0].trim()}` : '';
2481
+ return `${indent}${dsl}${firstComment}`;
2482
+ default:
2483
+ return dsl;
2484
+ }
2485
+ }
2486
+ exportFieldSpecification(spec) {
2487
+ return spec.toDSL();
2488
+ }
2489
+ exportAndSpecification(spec, depth) {
2490
+ const specifications = spec.getSpecifications();
2491
+ const parts = specifications.map((s) => {
2492
+ const dsl = this.exportSpecification(s, depth + 1);
2493
+ return this.needsParentheses(s, 'and') ? `(${dsl})` : dsl;
2494
+ });
2495
+ if (this.options.prettyPrint && this.shouldBreakLine(parts.join(' && '))) {
2496
+ const indent = ' '.repeat(depth * this.options.indentSize);
2497
+ const nextIndent = ' '.repeat((depth + 1) * this.options.indentSize);
2498
+ return parts.join(` &&\n${nextIndent}`);
2499
+ }
2500
+ return parts.join(' && ');
2501
+ }
2502
+ exportOrSpecification(spec, depth) {
2503
+ const specifications = spec.getSpecifications();
2504
+ const parts = specifications.map((s) => {
2505
+ const dsl = this.exportSpecification(s, depth + 1);
2506
+ return this.needsParentheses(s, 'or') ? `(${dsl})` : dsl;
2507
+ });
2508
+ if (this.options.prettyPrint && this.shouldBreakLine(parts.join(' || '))) {
2509
+ const nextIndent = ' '.repeat((depth + 1) * this.options.indentSize);
2510
+ return parts.join(` ||\n${nextIndent}`);
2511
+ }
2512
+ return parts.join(' || ');
2513
+ }
2514
+ exportNotSpecification(spec, depth) {
2515
+ const innerSpec = spec.getSpecification();
2516
+ const innerDsl = this.exportSpecification(innerSpec, depth);
2517
+ if (this.needsParentheses(innerSpec, 'not')) {
2518
+ return `!(${innerDsl})`;
2519
+ }
2520
+ return `!${innerDsl}`;
2521
+ }
2522
+ exportXorSpecification(spec, depth) {
2523
+ const specifications = spec.getSpecifications();
2524
+ const parts = specifications.map((s) => {
2525
+ const dsl = this.exportSpecification(s, depth + 1);
2526
+ return this.needsParentheses(s, 'xor') ? `(${dsl})` : dsl;
2527
+ });
2528
+ if (this.options.prettyPrint && this.shouldBreakLine(parts.join(' xor '))) {
2529
+ const nextIndent = ' '.repeat((depth + 1) * this.options.indentSize);
2530
+ return parts.join(` xor\n${nextIndent}`);
2531
+ }
2532
+ return parts.join(' xor ');
2533
+ }
2534
+ exportImpliesSpecification(spec, depth) {
2535
+ const antecedent = this.exportSpecification(spec.getAntecedent(), depth + 1);
2536
+ const consequent = this.exportSpecification(spec.getConsequent(), depth + 1);
2537
+ const leftPart = this.needsParentheses(spec.getAntecedent(), 'implies')
2538
+ ? `(${antecedent})`
2539
+ : antecedent;
2540
+ const rightPart = this.needsParentheses(spec.getConsequent(), 'implies')
2541
+ ? `(${consequent})`
2542
+ : consequent;
2543
+ if (this.options.prettyPrint &&
2544
+ this.shouldBreakLine(`${leftPart} implies ${rightPart}`)) {
2545
+ const nextIndent = ' '.repeat((depth + 1) * this.options.indentSize);
2546
+ return `${leftPart} implies\n${nextIndent}${rightPart}`;
2547
+ }
2548
+ return `${leftPart} implies ${rightPart}`;
2549
+ }
2550
+ exportFunctionSpecification(spec) {
2551
+ return spec.toDSL();
2552
+ }
2553
+ exportAtLeastSpecification(spec, depth) {
2554
+ const minimum = spec.getMinimum();
2555
+ const specifications = spec.getSpecifications();
2556
+ const specDsls = specifications.map((s) => this.exportSpecification(s, depth + 1));
2557
+ if (this.options.prettyPrint && specDsls.length > 2) {
2558
+ const nextIndent = ' '.repeat((depth + 1) * this.options.indentSize);
2559
+ const specsStr = specDsls.join(`,\n${nextIndent}`);
2560
+ return `atLeast(${minimum}, [\n${nextIndent}${specsStr}\n${' '.repeat(depth * this.options.indentSize)}])`;
2561
+ }
2562
+ return `atLeast(${minimum}, [${specDsls.join(', ')}])`;
2563
+ }
2564
+ exportExactlySpecification(spec, depth) {
2565
+ const exact = spec.getExact();
2566
+ const specifications = spec.getSpecifications();
2567
+ const specDsls = specifications.map((s) => this.exportSpecification(s, depth + 1));
2568
+ if (this.options.prettyPrint && specDsls.length > 2) {
2569
+ const nextIndent = ' '.repeat((depth + 1) * this.options.indentSize);
2570
+ const specsStr = specDsls.join(`,\n${nextIndent}`);
2571
+ return `exactly(${exact}, [\n${nextIndent}${specsStr}\n${' '.repeat(depth * this.options.indentSize)}])`;
2572
+ }
2573
+ return `exactly(${exact}, [${specDsls.join(', ')}])`;
2574
+ }
2575
+ exportFieldToFieldSpecification(spec) {
2576
+ return spec.toDSL();
2577
+ }
2578
+ exportContextualSpecification(spec) {
2579
+ return spec.toDSL();
2580
+ }
2581
+ needsParentheses(spec, parentContext) {
2582
+ if (this.options.useParentheses === 'explicit') {
2583
+ return true;
2584
+ }
2585
+ if (this.options.useParentheses === 'minimal') {
2586
+ return false;
2587
+ }
2588
+ // Auto mode - determine based on operator precedence
2589
+ const precedence = this.getOperatorPrecedence(spec);
2590
+ const parentPrecedence = this.getContextPrecedence(parentContext);
2591
+ return precedence < parentPrecedence;
2592
+ }
2593
+ getOperatorPrecedence(spec) {
2594
+ if (spec instanceof NotSpecification)
2595
+ return 5;
2596
+ if (spec instanceof AndSpecification)
2597
+ return 4;
2598
+ if (spec instanceof OrSpecification)
2599
+ return 3;
2600
+ if (spec instanceof XorSpecification)
2601
+ return 2;
2602
+ if (spec instanceof ImpliesSpecification)
2603
+ return 1;
2604
+ return 6; // Highest precedence for primitives
2605
+ }
2606
+ getContextPrecedence(context) {
2607
+ switch (context) {
2608
+ case 'not':
2609
+ return 5;
2610
+ case 'and':
2611
+ return 4;
2612
+ case 'or':
2613
+ return 3;
2614
+ case 'xor':
2615
+ return 2;
2616
+ case 'implies':
2617
+ return 1;
2618
+ default:
2619
+ return 0;
2620
+ }
2621
+ }
2622
+ shouldBreakLine(line) {
2623
+ return line.length > this.options.maxLineLength;
2624
+ }
2625
+ formatPretty(dsl) {
2626
+ // Additional pretty-printing logic could go here
2627
+ // For now, just return the DSL as-is since we handle formatting
2628
+ // in the individual export methods
2629
+ return dsl;
2630
+ }
2631
+ }
2632
+
2633
+ /**
2634
+ * Types of validation issues that can be found in DSL
2635
+ */
2636
+ var ValidationIssueType;
2637
+ (function (ValidationIssueType) {
2638
+ ValidationIssueType["SYNTAX_ERROR"] = "SyntaxError";
2639
+ ValidationIssueType["UNKNOWN_FUNCTION"] = "UnknownFunction";
2640
+ ValidationIssueType["UNKNOWN_OPERATOR"] = "UnknownOperator";
2641
+ ValidationIssueType["UNBALANCED_PARENTHESES"] = "UnbalancedParentheses";
2642
+ ValidationIssueType["UNBALANCED_BRACKETS"] = "UnbalancedBrackets";
2643
+ ValidationIssueType["INVALID_TOKEN"] = "InvalidToken";
2644
+ ValidationIssueType["UNEXPECTED_TOKEN"] = "UnexpectedToken";
2645
+ ValidationIssueType["MISSING_ARGUMENT"] = "MissingArgument";
2646
+ ValidationIssueType["TOO_MANY_ARGUMENTS"] = "TooManyArguments";
2647
+ ValidationIssueType["INVALID_FIELD_REFERENCE"] = "InvalidFieldReference";
2648
+ ValidationIssueType["EMPTY_EXPRESSION"] = "EmptyExpression";
2649
+ ValidationIssueType["UNTERMINATED_STRING"] = "UnterminatedString";
2650
+ ValidationIssueType["INVALID_NUMBER"] = "InvalidNumber";
2651
+ ValidationIssueType["DEPRECATED_SYNTAX"] = "DeprecatedSyntax";
2652
+ ValidationIssueType["PERFORMANCE_WARNING"] = "PerformanceWarning";
2653
+ })(ValidationIssueType || (ValidationIssueType = {}));
2654
+ /**
2655
+ * Severity levels for validation issues
2656
+ */
2657
+ var ValidationSeverity;
2658
+ (function (ValidationSeverity) {
2659
+ ValidationSeverity["ERROR"] = "error";
2660
+ ValidationSeverity["WARNING"] = "warning";
2661
+ ValidationSeverity["INFO"] = "info";
2662
+ ValidationSeverity["HINT"] = "hint";
2663
+ })(ValidationSeverity || (ValidationSeverity = {}));
2664
+
2665
+ /**
2666
+ * Validates DSL expressions and provides detailed error reporting
2667
+ */
2668
+ class DslValidator {
2669
+ config;
2670
+ BUILT_IN_FUNCTIONS = [
2671
+ 'contains', 'startsWith', 'endsWith', 'atLeast', 'exactly',
2672
+ 'forEach', 'uniqueBy', 'minLength', 'maxLength',
2673
+ 'requiredIf', 'visibleIf', 'disabledIf', 'readonlyIf',
2674
+ 'ifDefined', 'ifNotNull', 'ifExists', 'withDefault'
2675
+ ];
2676
+ constructor(config = {}) {
2677
+ this.config = {
2678
+ knownFunctions: config.knownFunctions || [],
2679
+ functionRegistry: config.functionRegistry,
2680
+ knownFields: config.knownFields || [],
2681
+ maxComplexity: config.maxComplexity || 50,
2682
+ enablePerformanceWarnings: config.enablePerformanceWarnings ?? true,
2683
+ enableDeprecationWarnings: config.enableDeprecationWarnings ?? true
2684
+ };
2685
+ }
2686
+ /**
2687
+ * Validates a DSL expression and returns all issues found
2688
+ */
2689
+ validate(input) {
2690
+ const issues = [];
2691
+ try {
2692
+ // Basic validation
2693
+ this.validateBasicStructure(input, issues);
2694
+ // Tokenize and validate tokens
2695
+ const tokenizer = new DslTokenizer(input);
2696
+ const tokens = tokenizer.tokenize();
2697
+ this.validateTokens(tokens, input, issues);
2698
+ this.validateSyntax(tokens, input, issues);
2699
+ this.validateSemantics(tokens, input, issues);
2700
+ this.validateComplexity(tokens, issues);
2701
+ }
2702
+ catch (error) {
2703
+ issues.push({
2704
+ type: ValidationIssueType.SYNTAX_ERROR,
2705
+ severity: ValidationSeverity.ERROR,
2706
+ message: `Unexpected error during validation: ${error}`,
2707
+ position: { start: 0, end: input.length, line: 1, column: 1 }
2708
+ });
2709
+ }
2710
+ return issues;
2711
+ }
2712
+ validateBasicStructure(input, issues) {
2713
+ // Check for empty expression
2714
+ if (input.trim().length === 0) {
2715
+ issues.push({
2716
+ type: ValidationIssueType.EMPTY_EXPRESSION,
2717
+ severity: ValidationSeverity.ERROR,
2718
+ message: 'Expression cannot be empty',
2719
+ position: { start: 0, end: 0, line: 1, column: 1 },
2720
+ suggestion: 'Add a valid expression'
2721
+ });
2722
+ return;
2723
+ }
2724
+ // Check for unbalanced parentheses
2725
+ this.validateBalancedDelimiters(input, '(', ')', ValidationIssueType.UNBALANCED_PARENTHESES, issues);
2726
+ // Check for unbalanced brackets
2727
+ this.validateBalancedDelimiters(input, '[', ']', ValidationIssueType.UNBALANCED_BRACKETS, issues);
2728
+ // Check for unterminated strings
2729
+ this.validateStringLiterals(input, issues);
2730
+ }
2731
+ validateBalancedDelimiters(input, open, close, issueType, issues) {
2732
+ let count = 0;
2733
+ let openPositions = [];
2734
+ for (let i = 0; i < input.length; i++) {
2735
+ if (input[i] === open) {
2736
+ count++;
2737
+ openPositions.push(i);
2738
+ }
2739
+ else if (input[i] === close) {
2740
+ count--;
2741
+ if (count < 0) {
2742
+ const { line, column } = this.getLineColumn(input, i);
2743
+ issues.push({
2744
+ type: issueType,
2745
+ severity: ValidationSeverity.ERROR,
2746
+ message: `Unexpected closing ${close}`,
2747
+ position: { start: i, end: i + 1, line, column },
2748
+ suggestion: `Remove the extra ${close} or add a matching ${open}`
2749
+ });
2750
+ }
2751
+ else {
2752
+ openPositions.pop();
2753
+ }
2754
+ }
2755
+ }
2756
+ // Report unclosed delimiters
2757
+ for (const pos of openPositions) {
2758
+ const { line, column } = this.getLineColumn(input, pos);
2759
+ issues.push({
2760
+ type: issueType,
2761
+ severity: ValidationSeverity.ERROR,
2762
+ message: `Unclosed ${open}`,
2763
+ position: { start: pos, end: pos + 1, line, column },
2764
+ suggestion: `Add a closing ${close}`
2765
+ });
2766
+ }
2767
+ }
2768
+ validateStringLiterals(input, issues) {
2769
+ const quotes = ['"', "'"];
2770
+ for (const quote of quotes) {
2771
+ let inString = false;
2772
+ let startPos = -1;
2773
+ for (let i = 0; i < input.length; i++) {
2774
+ if (input[i] === quote) {
2775
+ if (!inString) {
2776
+ inString = true;
2777
+ startPos = i;
2778
+ }
2779
+ else {
2780
+ // Check if it's escaped
2781
+ let escaped = false;
2782
+ let backslashCount = 0;
2783
+ for (let j = i - 1; j >= 0 && input[j] === '\\'; j--) {
2784
+ backslashCount++;
2785
+ }
2786
+ escaped = backslashCount % 2 === 1;
2787
+ if (!escaped) {
2788
+ inString = false;
2789
+ startPos = -1;
2790
+ }
2791
+ }
2792
+ }
2793
+ }
2794
+ if (inString) {
2795
+ const { line, column } = this.getLineColumn(input, startPos);
2796
+ issues.push({
2797
+ type: ValidationIssueType.UNTERMINATED_STRING,
2798
+ severity: ValidationSeverity.ERROR,
2799
+ message: `Unterminated string literal starting with ${quote}`,
2800
+ position: { start: startPos, end: input.length, line, column },
2801
+ suggestion: `Add a closing ${quote}`
2802
+ });
2803
+ }
2804
+ }
2805
+ }
2806
+ validateTokens(tokens, input, issues) {
2807
+ for (const token of tokens) {
2808
+ if (token.type === TokenType.EOF)
2809
+ continue;
2810
+ // Validate numbers
2811
+ if (token.type === TokenType.NUMBER) {
2812
+ if (isNaN(Number(token.value))) {
2813
+ issues.push({
2814
+ type: ValidationIssueType.INVALID_NUMBER,
2815
+ severity: ValidationSeverity.ERROR,
2816
+ message: `Invalid number format: ${token.value}`,
2817
+ position: {
2818
+ start: token.position,
2819
+ end: token.position + token.value.length,
2820
+ line: token.line,
2821
+ column: token.column
2822
+ },
2823
+ suggestion: 'Use a valid number format (e.g., 42, 3.14, -5)'
2824
+ });
2825
+ }
2826
+ }
2827
+ // Validate field references
2828
+ if (token.type === TokenType.FIELD_REFERENCE || token.type === TokenType.IDENTIFIER) {
2829
+ if (this.config.knownFields.length > 0) {
2830
+ if (!this.config.knownFields.includes(token.value)) {
2831
+ issues.push({
2832
+ type: ValidationIssueType.INVALID_FIELD_REFERENCE,
2833
+ severity: ValidationSeverity.WARNING,
2834
+ message: `Unknown field: ${token.value}`,
2835
+ position: {
2836
+ start: token.position,
2837
+ end: token.position + token.value.length,
2838
+ line: token.line,
2839
+ column: token.column
2840
+ },
2841
+ suggestion: this.suggestSimilarField(token.value),
2842
+ help: `Available fields: ${this.config.knownFields.join(', ')}`
2843
+ });
2844
+ }
2845
+ }
2846
+ }
2847
+ }
2848
+ }
2849
+ validateSyntax(tokens, input, issues) {
2850
+ for (let i = 0; i < tokens.length; i++) {
2851
+ const token = tokens[i];
2852
+ const nextToken = tokens[i + 1];
2853
+ const prevToken = tokens[i - 1];
2854
+ // Check for consecutive operators
2855
+ if (this.isOperatorToken(token) && this.isOperatorToken(nextToken)) {
2856
+ issues.push({
2857
+ type: ValidationIssueType.UNEXPECTED_TOKEN,
2858
+ severity: ValidationSeverity.ERROR,
2859
+ message: `Unexpected ${nextToken.type} after ${token.type}`,
2860
+ position: {
2861
+ start: nextToken.position,
2862
+ end: nextToken.position + nextToken.value.length,
2863
+ line: nextToken.line,
2864
+ column: nextToken.column
2865
+ },
2866
+ suggestion: 'Add an operand between operators'
2867
+ });
2868
+ }
2869
+ // Check for operators at the beginning/end
2870
+ if (i === 0 && this.isBinaryOperatorToken(token)) {
2871
+ issues.push({
2872
+ type: ValidationIssueType.UNEXPECTED_TOKEN,
2873
+ severity: ValidationSeverity.ERROR,
2874
+ message: `Expression cannot start with ${token.type}`,
2875
+ position: {
2876
+ start: token.position,
2877
+ end: token.position + token.value.length,
2878
+ line: token.line,
2879
+ column: token.column
2880
+ },
2881
+ suggestion: 'Add an operand before the operator'
2882
+ });
2883
+ }
2884
+ if (i === tokens.length - 2 && this.isBinaryOperatorToken(token) && nextToken.type === TokenType.EOF) {
2885
+ issues.push({
2886
+ type: ValidationIssueType.UNEXPECTED_TOKEN,
2887
+ severity: ValidationSeverity.ERROR,
2888
+ message: `Expression cannot end with ${token.type}`,
2889
+ position: {
2890
+ start: token.position,
2891
+ end: token.position + token.value.length,
2892
+ line: token.line,
2893
+ column: token.column
2894
+ },
2895
+ suggestion: 'Add an operand after the operator'
2896
+ });
2897
+ }
2898
+ }
2899
+ }
2900
+ validateSemantics(tokens, input, issues) {
2901
+ // Check function calls
2902
+ for (let i = 0; i < tokens.length; i++) {
2903
+ const token = tokens[i];
2904
+ const nextToken = tokens[i + 1];
2905
+ if (token.type === TokenType.IDENTIFIER && nextToken?.type === TokenType.LEFT_PAREN) {
2906
+ const functionName = token.value;
2907
+ // Check if function is known
2908
+ const allKnownFunctions = [
2909
+ ...this.BUILT_IN_FUNCTIONS,
2910
+ ...this.config.knownFunctions
2911
+ ];
2912
+ if (this.config.functionRegistry) {
2913
+ allKnownFunctions.push(...Array.from(this.config.functionRegistry.getAll().keys()));
2914
+ }
2915
+ if (!allKnownFunctions.includes(functionName)) {
2916
+ issues.push({
2917
+ type: ValidationIssueType.UNKNOWN_FUNCTION,
2918
+ severity: ValidationSeverity.ERROR,
2919
+ message: `Unknown function: ${functionName}`,
2920
+ position: {
2921
+ start: token.position,
2922
+ end: token.position + token.value.length,
2923
+ line: token.line,
2924
+ column: token.column
2925
+ },
2926
+ suggestion: this.suggestSimilarFunction(functionName, allKnownFunctions),
2927
+ help: `Available functions: ${allKnownFunctions.join(', ')}`
2928
+ });
2929
+ }
2930
+ // Validate function arguments (basic check)
2931
+ this.validateFunctionArguments(functionName, i, tokens, issues);
2932
+ }
2933
+ }
2934
+ }
2935
+ validateFunctionArguments(functionName, startIndex, tokens, issues) {
2936
+ // Find the argument list
2937
+ let parenCount = 0;
2938
+ let argCount = 0;
2939
+ let inArgs = false;
2940
+ for (let i = startIndex + 1; i < tokens.length; i++) {
2941
+ const token = tokens[i];
2942
+ if (token.type === TokenType.LEFT_PAREN) {
2943
+ parenCount++;
2944
+ if (parenCount === 1) {
2945
+ inArgs = true;
2946
+ }
2947
+ }
2948
+ else if (token.type === TokenType.RIGHT_PAREN) {
2949
+ parenCount--;
2950
+ if (parenCount === 0) {
2951
+ break;
2952
+ }
2953
+ }
2954
+ else if (token.type === TokenType.COMMA && parenCount === 1) {
2955
+ argCount++;
2956
+ }
2957
+ else if (inArgs && parenCount === 1 && token.type !== TokenType.COMMA) {
2958
+ // We have at least one argument
2959
+ if (argCount === 0) {
2960
+ argCount = 1;
2961
+ }
2962
+ }
2963
+ }
2964
+ // Check argument count for known functions
2965
+ const expectedArgs = this.getExpectedArgumentCount(functionName);
2966
+ if (expectedArgs !== null && argCount !== expectedArgs) {
2967
+ const functionToken = tokens[startIndex];
2968
+ const severity = argCount < expectedArgs ? ValidationSeverity.ERROR : ValidationSeverity.WARNING;
2969
+ const message = argCount < expectedArgs
2970
+ ? `Function ${functionName} expects ${expectedArgs} arguments, got ${argCount}`
2971
+ : `Function ${functionName} expects ${expectedArgs} arguments, got ${argCount}`;
2972
+ issues.push({
2973
+ type: argCount < expectedArgs ? ValidationIssueType.MISSING_ARGUMENT : ValidationIssueType.TOO_MANY_ARGUMENTS,
2974
+ severity,
2975
+ message,
2976
+ position: {
2977
+ start: functionToken.position,
2978
+ end: functionToken.position + functionToken.value.length,
2979
+ line: functionToken.line,
2980
+ column: functionToken.column
2981
+ },
2982
+ suggestion: `Use exactly ${expectedArgs} arguments`
2983
+ });
2984
+ }
2985
+ }
2986
+ validateComplexity(tokens, issues) {
2987
+ if (!this.config.enablePerformanceWarnings)
2988
+ return;
2989
+ const operatorCount = tokens.filter(token => this.isOperatorToken(token)).length;
2990
+ if (operatorCount > this.config.maxComplexity) {
2991
+ issues.push({
2992
+ type: ValidationIssueType.PERFORMANCE_WARNING,
2993
+ severity: ValidationSeverity.WARNING,
2994
+ message: `Expression complexity is high (${operatorCount} operators). Consider breaking into smaller expressions.`,
2995
+ position: { start: 0, end: 0, line: 1, column: 1 },
2996
+ help: 'Complex expressions can impact performance and readability'
2997
+ });
2998
+ }
2999
+ }
3000
+ getExpectedArgumentCount(functionName) {
3001
+ const argCounts = {
3002
+ 'contains': 2,
3003
+ 'startsWith': 2,
3004
+ 'endsWith': 2,
3005
+ 'atLeast': 2,
3006
+ 'exactly': 2,
3007
+ 'forEach': 2,
3008
+ 'uniqueBy': 2,
3009
+ 'minLength': 2,
3010
+ 'maxLength': 2,
3011
+ 'requiredIf': 2,
3012
+ 'visibleIf': 2,
3013
+ 'disabledIf': 2,
3014
+ 'readonlyIf': 2,
3015
+ 'ifDefined': 2,
3016
+ 'ifNotNull': 2,
3017
+ 'ifExists': 2,
3018
+ 'withDefault': 3
3019
+ };
3020
+ return argCounts[functionName] ?? null;
3021
+ }
3022
+ isOperatorToken(token) {
3023
+ return [
3024
+ TokenType.AND, TokenType.OR, TokenType.NOT, TokenType.XOR, TokenType.IMPLIES,
3025
+ TokenType.EQUALS, TokenType.NOT_EQUALS, TokenType.LESS_THAN,
3026
+ TokenType.LESS_THAN_OR_EQUAL, TokenType.GREATER_THAN, TokenType.GREATER_THAN_OR_EQUAL,
3027
+ TokenType.IN
3028
+ ].includes(token.type);
3029
+ }
3030
+ isBinaryOperatorToken(token) {
3031
+ return [
3032
+ TokenType.AND, TokenType.OR, TokenType.XOR, TokenType.IMPLIES,
3033
+ TokenType.EQUALS, TokenType.NOT_EQUALS, TokenType.LESS_THAN,
3034
+ TokenType.LESS_THAN_OR_EQUAL, TokenType.GREATER_THAN, TokenType.GREATER_THAN_OR_EQUAL,
3035
+ TokenType.IN
3036
+ ].includes(token.type);
3037
+ }
3038
+ suggestSimilarField(fieldName) {
3039
+ return this.findSimilar(fieldName, this.config.knownFields);
3040
+ }
3041
+ suggestSimilarFunction(functionName, knownFunctions) {
3042
+ return this.findSimilar(functionName, knownFunctions);
3043
+ }
3044
+ findSimilar(target, candidates) {
3045
+ if (candidates.length === 0)
3046
+ return undefined;
3047
+ const similarities = candidates.map(candidate => ({
3048
+ candidate,
3049
+ distance: this.levenshteinDistance(target.toLowerCase(), candidate.toLowerCase())
3050
+ }));
3051
+ similarities.sort((a, b) => a.distance - b.distance);
3052
+ // Only suggest if the distance is reasonable
3053
+ if (similarities[0].distance <= Math.max(2, target.length * 0.4)) {
3054
+ return `Did you mean "${similarities[0].candidate}"?`;
3055
+ }
3056
+ return undefined;
3057
+ }
3058
+ levenshteinDistance(a, b) {
3059
+ const matrix = [];
3060
+ for (let i = 0; i <= b.length; i++) {
3061
+ matrix[i] = [i];
3062
+ }
3063
+ for (let j = 0; j <= a.length; j++) {
3064
+ matrix[0][j] = j;
3065
+ }
3066
+ for (let i = 1; i <= b.length; i++) {
3067
+ for (let j = 1; j <= a.length; j++) {
3068
+ if (b.charAt(i - 1) === a.charAt(j - 1)) {
3069
+ matrix[i][j] = matrix[i - 1][j - 1];
3070
+ }
3071
+ else {
3072
+ matrix[i][j] = Math.min(matrix[i - 1][j - 1] + 1, matrix[i][j - 1] + 1, matrix[i - 1][j] + 1);
3073
+ }
3074
+ }
3075
+ }
3076
+ return matrix[b.length][a.length];
3077
+ }
3078
+ getLineColumn(input, position) {
3079
+ let line = 1;
3080
+ let column = 1;
3081
+ for (let i = 0; i < position && i < input.length; i++) {
3082
+ if (input[i] === '\n') {
3083
+ line++;
3084
+ column = 1;
3085
+ }
3086
+ else {
3087
+ column++;
3088
+ }
3089
+ }
3090
+ return { line, column };
3091
+ }
3092
+ }
3093
+
3094
+ class SpecificationFactory {
3095
+ static fromJSON(json) {
3096
+ // Robustez: validar entrada e inferir quando possível
3097
+ if (json == null) {
3098
+ throw new Error('Invalid specification JSON: null/undefined');
3099
+ }
3100
+ if (Array.isArray(json)) {
3101
+ const specs = json.map((specJson) => SpecificationFactory.fromJSON(specJson));
3102
+ return new AndSpecification(specs);
3103
+ }
3104
+ if (typeof json !== 'object') {
3105
+ throw new Error(`Invalid specification JSON: expected object/array, got ${typeof json}`);
3106
+ }
3107
+ // Inferência quando 'type' está ausente
3108
+ if (!json.type) {
3109
+ if (Array.isArray(json.specs)) {
3110
+ const specs = json.specs.map((specJson) => SpecificationFactory.fromJSON(specJson));
3111
+ return new AndSpecification(specs);
3112
+ }
3113
+ if ('field' in json && 'operator' in json) {
3114
+ return FieldSpecification.fromJSON({ type: 'field', ...json });
3115
+ }
3116
+ if ('antecedent' in json && 'consequent' in json) {
3117
+ const antecedent = SpecificationFactory.fromJSON(json.antecedent);
3118
+ const consequent = SpecificationFactory.fromJSON(json.consequent);
3119
+ return new ImpliesSpecification(antecedent, consequent);
3120
+ }
3121
+ if ('spec' in json) {
3122
+ const notSpec = SpecificationFactory.fromJSON(json.spec);
3123
+ return new NotSpecification(notSpec);
3124
+ }
3125
+ if ('itemSpecification' in json && 'arrayField' in json) {
3126
+ const itemSpec = SpecificationFactory.fromJSON(json.itemSpecification);
3127
+ return new ForEachSpecification(json.arrayField, itemSpec, json.metadata);
3128
+ }
3129
+ // Se não for possível inferir, manter erro claro
3130
+ throw new Error('Unknown specification type: undefined');
3131
+ }
3132
+ switch (json.type) {
3133
+ case 'field':
3134
+ return FieldSpecification.fromJSON(json);
3135
+ case 'and':
3136
+ const andSpecs = json.specs.map((specJson) => SpecificationFactory.fromJSON(specJson));
3137
+ return new AndSpecification(andSpecs);
3138
+ case 'or':
3139
+ const orSpecs = json.specs.map((specJson) => SpecificationFactory.fromJSON(specJson));
3140
+ return new OrSpecification(orSpecs);
3141
+ case 'not':
3142
+ const notSpec = SpecificationFactory.fromJSON(json.spec);
3143
+ return new NotSpecification(notSpec);
3144
+ case 'xor':
3145
+ const xorSpecs = json.specs.map((specJson) => SpecificationFactory.fromJSON(specJson));
3146
+ return new XorSpecification(xorSpecs);
3147
+ case 'implies':
3148
+ const antecedent = SpecificationFactory.fromJSON(json.antecedent);
3149
+ const consequent = SpecificationFactory.fromJSON(json.consequent);
3150
+ return new ImpliesSpecification(antecedent, consequent);
3151
+ case 'function':
3152
+ return FunctionSpecification.fromJSON(json);
3153
+ case 'atLeast':
3154
+ const atLeastSpecs = json.specs.map((specJson) => SpecificationFactory.fromJSON(specJson));
3155
+ return new AtLeastSpecification(json.minimum, atLeastSpecs);
3156
+ case 'exactly':
3157
+ const exactlySpecs = json.specs.map((specJson) => SpecificationFactory.fromJSON(specJson));
3158
+ return new ExactlySpecification(json.exact, exactlySpecs);
3159
+ case 'fieldToField':
3160
+ return FieldToFieldSpecification.fromJSON(json);
3161
+ case 'contextual':
3162
+ return ContextualSpecification.fromJSON(json);
3163
+ case 'requiredIf':
3164
+ const requiredCondition = SpecificationFactory.fromJSON(json.condition);
3165
+ return new RequiredIfSpecification(json.field, requiredCondition, json.metadata);
3166
+ case 'visibleIf':
3167
+ const visibleCondition = SpecificationFactory.fromJSON(json.condition);
3168
+ return new VisibleIfSpecification(json.field, visibleCondition, json.metadata);
3169
+ case 'disabledIf':
3170
+ const disabledCondition = SpecificationFactory.fromJSON(json.condition);
3171
+ return new DisabledIfSpecification(json.field, disabledCondition, json.metadata);
3172
+ case 'readonlyIf':
3173
+ const readonlyCondition = SpecificationFactory.fromJSON(json.condition);
3174
+ return new ReadonlyIfSpecification(json.field, readonlyCondition, json.metadata);
3175
+ case 'forEach':
3176
+ const itemSpec = SpecificationFactory.fromJSON(json.itemSpecification);
3177
+ return new ForEachSpecification(json.arrayField, itemSpec, json.metadata);
3178
+ case 'uniqueBy':
3179
+ return UniqueBySpecification.fromJSON(json);
3180
+ case 'minLength':
3181
+ return MinLengthSpecification.fromJSON(json);
3182
+ case 'maxLength':
3183
+ return MaxLengthSpecification.fromJSON(json);
3184
+ case 'ifDefined':
3185
+ const ifDefinedSpec = SpecificationFactory.fromJSON(json.specification);
3186
+ return new IfDefinedSpecification(json.field, ifDefinedSpec, json.metadata);
3187
+ case 'ifNotNull':
3188
+ const ifNotNullSpec = SpecificationFactory.fromJSON(json.specification);
3189
+ return new IfNotNullSpecification(json.field, ifNotNullSpec, json.metadata);
3190
+ case 'ifExists':
3191
+ const ifExistsSpec = SpecificationFactory.fromJSON(json.specification);
3192
+ return new IfExistsSpecification(json.field, ifExistsSpec, json.metadata);
3193
+ case 'withDefault':
3194
+ const withDefaultSpec = SpecificationFactory.fromJSON(json.specification);
3195
+ return new WithDefaultSpecification(json.field, json.defaultValue, withDefaultSpec, json.metadata);
3196
+ case 'form':
3197
+ // Form specifications require special reconstruction
3198
+ throw new Error('FormSpecification.fromJSON not yet implemented');
3199
+ default:
3200
+ throw new Error(`Unknown specification type: ${json.type}`);
3201
+ }
3202
+ }
3203
+ static field(field, operator, value) {
3204
+ return new FieldSpecification(field, operator, value);
3205
+ }
3206
+ static and(...specs) {
3207
+ return new AndSpecification(specs);
3208
+ }
3209
+ static or(...specs) {
3210
+ return new OrSpecification(specs);
3211
+ }
3212
+ static not(spec) {
3213
+ return new NotSpecification(spec);
3214
+ }
3215
+ static xor(...specs) {
3216
+ return new XorSpecification(specs);
3217
+ }
3218
+ static implies(antecedent, consequent) {
3219
+ return new ImpliesSpecification(antecedent, consequent);
3220
+ }
3221
+ static func(name, args, registry) {
3222
+ return new FunctionSpecification(name, args, registry);
3223
+ }
3224
+ static atLeast(minimum, specs) {
3225
+ return new AtLeastSpecification(minimum, specs);
3226
+ }
3227
+ static exactly(exact, specs) {
3228
+ return new ExactlySpecification(exact, specs);
3229
+ }
3230
+ static fieldToField(fieldA, operator, fieldB, transformA, transformB, registry) {
3231
+ return new FieldToFieldSpecification(fieldA, operator, fieldB, transformA, transformB, registry);
3232
+ }
3233
+ static contextual(template, provider) {
3234
+ return new ContextualSpecification(template, provider);
3235
+ }
3236
+ // Convenience methods for common operators
3237
+ static equals(field, value) {
3238
+ return new FieldSpecification(field, ComparisonOperator.EQUALS, value);
3239
+ }
3240
+ static notEquals(field, value) {
3241
+ return new FieldSpecification(field, ComparisonOperator.NOT_EQUALS, value);
3242
+ }
3243
+ static greaterThan(field, value) {
3244
+ return new FieldSpecification(field, ComparisonOperator.GREATER_THAN, value);
3245
+ }
3246
+ static lessThan(field, value) {
3247
+ return new FieldSpecification(field, ComparisonOperator.LESS_THAN, value);
3248
+ }
3249
+ static contains(field, value) {
3250
+ return new FieldSpecification(field, ComparisonOperator.CONTAINS, value);
3251
+ }
3252
+ static startsWith(field, value) {
3253
+ return new FieldSpecification(field, ComparisonOperator.STARTS_WITH, value);
3254
+ }
3255
+ static endsWith(field, value) {
3256
+ return new FieldSpecification(field, ComparisonOperator.ENDS_WITH, value);
3257
+ }
3258
+ static isIn(field, values) {
3259
+ return new FieldSpecification(field, ComparisonOperator.IN, values);
3260
+ }
3261
+ // Phase 2: Conditional validators
3262
+ static requiredIf(field, condition, metadata) {
3263
+ return new RequiredIfSpecification(field, condition, metadata);
3264
+ }
3265
+ static visibleIf(field, condition, metadata) {
3266
+ return new VisibleIfSpecification(field, condition, metadata);
3267
+ }
3268
+ static disabledIf(field, condition, metadata) {
3269
+ return new DisabledIfSpecification(field, condition, metadata);
3270
+ }
3271
+ static readonlyIf(field, condition, metadata) {
3272
+ return new ReadonlyIfSpecification(field, condition, metadata);
3273
+ }
3274
+ // Phase 2: Collection specifications
3275
+ static forEach(arrayField, itemSpecification, metadata) {
3276
+ return new ForEachSpecification(arrayField, itemSpecification, metadata);
3277
+ }
3278
+ static uniqueBy(arrayField, keySelector, metadata) {
3279
+ return new UniqueBySpecification(arrayField, keySelector, metadata);
3280
+ }
3281
+ static minLength(arrayField, minLength, metadata) {
3282
+ return new MinLengthSpecification(arrayField, minLength, metadata);
3283
+ }
3284
+ static maxLength(arrayField, maxLength, metadata) {
3285
+ return new MaxLengthSpecification(arrayField, maxLength, metadata);
3286
+ }
3287
+ // Phase 2: Optional field handling
3288
+ static ifDefined(field, specification, metadata) {
3289
+ return new IfDefinedSpecification(field, specification, metadata);
3290
+ }
3291
+ static ifNotNull(field, specification, metadata) {
3292
+ return new IfNotNullSpecification(field, specification, metadata);
3293
+ }
3294
+ static ifExists(field, specification, metadata) {
3295
+ return new IfExistsSpecification(field, specification, metadata);
3296
+ }
3297
+ static withDefault(field, defaultValue, specification, metadata) {
3298
+ return new WithDefaultSpecification(field, defaultValue, specification, metadata);
3299
+ }
3300
+ // Phase 2: Form specifications
3301
+ static form(metadata) {
3302
+ return new FormSpecification(metadata);
3303
+ }
3304
+ // Phase 2: Enhanced field specifications with metadata
3305
+ static fieldWithMetadata(field, operator, value, metadata) {
3306
+ return new FieldSpecification(field, operator, value, metadata);
3307
+ }
3308
+ // Convenience methods with metadata support
3309
+ static equalsWithMetadata(field, value, metadata) {
3310
+ return new FieldSpecification(field, ComparisonOperator.EQUALS, value, metadata);
3311
+ }
3312
+ static greaterThanWithMetadata(field, value, metadata) {
3313
+ return new FieldSpecification(field, ComparisonOperator.GREATER_THAN, value, metadata);
3314
+ }
3315
+ }
3316
+
3317
+ class SpecificationUtils {
3318
+ /**
3319
+ * Simplifies a specification by removing redundant nesting and applying logical rules
3320
+ */
3321
+ static simplify(spec) {
3322
+ if (spec instanceof AndSpecification) {
3323
+ return this.simplifyAnd(spec);
3324
+ }
3325
+ if (spec instanceof OrSpecification) {
3326
+ return this.simplifyOr(spec);
3327
+ }
3328
+ if (spec instanceof NotSpecification) {
3329
+ return this.simplifyNot(spec);
3330
+ }
3331
+ return spec;
3332
+ }
3333
+ static simplifyAnd(spec) {
3334
+ const specs = spec.getSpecifications();
3335
+ const flattened = [];
3336
+ // Flatten nested AND specifications
3337
+ for (const s of specs) {
3338
+ const simplified = this.simplify(s);
3339
+ if (simplified instanceof AndSpecification) {
3340
+ flattened.push(...simplified.getSpecifications());
3341
+ }
3342
+ else {
3343
+ flattened.push(simplified);
3344
+ }
3345
+ }
3346
+ // Remove duplicates and contradictions
3347
+ const unique = this.removeDuplicates(flattened);
3348
+ if (unique.length === 0) {
3349
+ throw new Error('Empty AND specification');
3350
+ }
3351
+ if (unique.length === 1) {
3352
+ return unique[0];
3353
+ }
3354
+ return new AndSpecification(unique);
3355
+ }
3356
+ static simplifyOr(spec) {
3357
+ const specs = spec.getSpecifications();
3358
+ const flattened = [];
3359
+ // Flatten nested OR specifications
3360
+ for (const s of specs) {
3361
+ const simplified = this.simplify(s);
3362
+ if (simplified instanceof OrSpecification) {
3363
+ flattened.push(...simplified.getSpecifications());
3364
+ }
3365
+ else {
3366
+ flattened.push(simplified);
3367
+ }
3368
+ }
3369
+ // Remove duplicates
3370
+ const unique = this.removeDuplicates(flattened);
3371
+ if (unique.length === 0) {
3372
+ throw new Error('Empty OR specification');
3373
+ }
3374
+ if (unique.length === 1) {
3375
+ return unique[0];
3376
+ }
3377
+ return new OrSpecification(unique);
3378
+ }
3379
+ static simplifyNot(spec) {
3380
+ const inner = spec.getSpecification();
3381
+ // Double negation elimination: !!A = A
3382
+ if (inner instanceof NotSpecification) {
3383
+ return this.simplify(inner.getSpecification());
3384
+ }
3385
+ const simplified = this.simplify(inner);
3386
+ if (simplified === inner) {
3387
+ return spec;
3388
+ }
3389
+ return new NotSpecification(simplified);
3390
+ }
3391
+ static removeDuplicates(specs) {
3392
+ const unique = [];
3393
+ const seen = new Set();
3394
+ for (const spec of specs) {
3395
+ const key = JSON.stringify(spec.toJSON());
3396
+ if (!seen.has(key)) {
3397
+ seen.add(key);
3398
+ unique.push(spec);
3399
+ }
3400
+ }
3401
+ return unique;
3402
+ }
3403
+ /**
3404
+ * Checks if two specifications are logically equivalent
3405
+ */
3406
+ static areEquivalent(spec1, spec2) {
3407
+ const json1 = JSON.stringify(this.simplify(spec1).toJSON());
3408
+ const json2 = JSON.stringify(this.simplify(spec2).toJSON());
3409
+ return json1 === json2;
3410
+ }
3411
+ /**
3412
+ * Gets all field references used in a specification
3413
+ */
3414
+ static getReferencedFields(spec) {
3415
+ const fields = new Set();
3416
+ this.collectFields(spec, fields);
3417
+ return fields;
3418
+ }
3419
+ static collectFields(spec, fields) {
3420
+ if (spec instanceof FieldSpecification) {
3421
+ fields.add(spec.getField());
3422
+ }
3423
+ else if (spec instanceof AndSpecification ||
3424
+ spec instanceof OrSpecification) {
3425
+ spec.getSpecifications().forEach((s) => this.collectFields(s, fields));
3426
+ }
3427
+ else if (spec instanceof NotSpecification) {
3428
+ this.collectFields(spec.getSpecification(), fields);
3429
+ }
3430
+ // Add more cases as needed for other specification types
3431
+ }
3432
+ /**
3433
+ * Validates a specification for common issues
3434
+ */
3435
+ static validate(spec) {
3436
+ const errors = [];
3437
+ const warnings = [];
3438
+ try {
3439
+ this.validateSpecification(spec, errors, warnings);
3440
+ }
3441
+ catch (error) {
3442
+ errors.push(`Validation error: ${error}`);
3443
+ }
3444
+ return {
3445
+ isValid: errors.length === 0,
3446
+ errors,
3447
+ warnings,
3448
+ };
3449
+ }
3450
+ static validateSpecification(spec, errors, warnings) {
3451
+ if (spec instanceof AndSpecification || spec instanceof OrSpecification) {
3452
+ const specs = spec.getSpecifications();
3453
+ if (specs.length === 0) {
3454
+ errors.push(`${spec.constructor.name} must have at least one specification`);
3455
+ }
3456
+ specs.forEach((s) => this.validateSpecification(s, errors, warnings));
3457
+ }
3458
+ else if (spec instanceof NotSpecification) {
3459
+ this.validateSpecification(spec.getSpecification(), errors, warnings);
3460
+ }
3461
+ // Add more validation rules as needed
3462
+ }
3463
+ /**
3464
+ * Estimates the complexity of a specification (useful for performance analysis)
3465
+ */
3466
+ static getComplexity(spec) {
3467
+ if (spec instanceof FieldSpecification) {
3468
+ return 1;
3469
+ }
3470
+ if (spec instanceof AndSpecification || spec instanceof OrSpecification) {
3471
+ return spec
3472
+ .getSpecifications()
3473
+ .reduce((total, s) => total + this.getComplexity(s), 1);
3474
+ }
3475
+ if (spec instanceof NotSpecification) {
3476
+ return 1 + this.getComplexity(spec.getSpecification());
3477
+ }
3478
+ return 1; // Base complexity for unknown types
3479
+ }
3480
+ }
3481
+
3482
+ // Utility functions for specifications
3483
+
3484
+ // Main entry point for @praxisui/specification library
3485
+
3486
+ /*
3487
+ * Public API Surface of @praxisui/specification
3488
+ */
3489
+
3490
+ /**
3491
+ * Generated bundle index. Do not edit.
3492
+ */
3493
+
3494
+ export { AndSpecification, AtLeastSpecification, COMPARISON_OPERATORS, ComparisonOperator, CompositeContextProvider, ConditionalSpecification, ConditionalType, ContextualSpecification, DateContextProvider, DefaultContextProvider, DisabledIfSpecification, DslExporter, DslParser, DslTokenizer, DslValidator, ExactlySpecification, FieldSpecification, FieldToFieldSpecification, ForEachSpecification, FormSpecification, FunctionRegistry, FunctionSpecification, IfDefinedSpecification, IfExistsSpecification, IfNotNullSpecification, ImpliesSpecification, MaxLengthSpecification, MinLengthSpecification, NotSpecification, OPERATOR_KEYWORDS, OPERATOR_SYMBOLS, OrSpecification, ReadonlyIfSpecification, RequiredIfSpecification, SpecificationFactory, SpecificationUtils, TokenType, TransformRegistry, UniqueBySpecification, ValidationIssueType, ValidationSeverity, VisibleIfSpecification, WithDefaultSpecification, XorSpecification };
3495
+ //# sourceMappingURL=praxisui-specification.mjs.map