@fincity/kirun-js 3.1.4 → 3.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/__tests__/engine/dsl/GraphDebugTest.ts +316 -0
  2. package/__tests__/engine/runtime/expression/ExpressionParsingTest.ts +402 -14
  3. package/dist/index.js +15 -1
  4. package/dist/index.js.map +1 -1
  5. package/dist/module.js +15 -1
  6. package/dist/module.js.map +1 -1
  7. package/dist/types.d.ts +416 -0
  8. package/dist/types.d.ts.map +1 -1
  9. package/package.json +1 -1
  10. package/src/engine/dsl/DSLCompiler.ts +104 -0
  11. package/src/engine/dsl/index.ts +30 -0
  12. package/src/engine/dsl/lexer/DSLLexer.ts +518 -0
  13. package/src/engine/dsl/lexer/DSLToken.ts +74 -0
  14. package/src/engine/dsl/lexer/Keywords.ts +80 -0
  15. package/src/engine/dsl/lexer/LexerError.ts +37 -0
  16. package/src/engine/dsl/monaco/DSLFunctionProvider.ts +187 -0
  17. package/src/engine/dsl/parser/DSLParser.ts +1075 -0
  18. package/src/engine/dsl/parser/DSLParserError.ts +29 -0
  19. package/src/engine/dsl/parser/ast/ASTNode.ts +23 -0
  20. package/src/engine/dsl/parser/ast/ArgumentNode.ts +43 -0
  21. package/src/engine/dsl/parser/ast/ComplexValueNode.ts +22 -0
  22. package/src/engine/dsl/parser/ast/EventDeclNode.ts +27 -0
  23. package/src/engine/dsl/parser/ast/ExpressionNode.ts +33 -0
  24. package/src/engine/dsl/parser/ast/FunctionCallNode.ts +29 -0
  25. package/src/engine/dsl/parser/ast/FunctionDefNode.ts +37 -0
  26. package/src/engine/dsl/parser/ast/ParameterDeclNode.ts +25 -0
  27. package/src/engine/dsl/parser/ast/SchemaLiteralNode.ts +26 -0
  28. package/src/engine/dsl/parser/ast/SchemaNode.ts +23 -0
  29. package/src/engine/dsl/parser/ast/StatementNode.ts +41 -0
  30. package/src/engine/dsl/parser/ast/index.ts +14 -0
  31. package/src/engine/dsl/transformer/ASTToJSON.ts +378 -0
  32. package/src/engine/dsl/transformer/ExpressionHandler.ts +48 -0
  33. package/src/engine/dsl/transformer/JSONToText.ts +694 -0
  34. package/src/engine/dsl/transformer/SchemaTransformer.ts +110 -0
  35. package/src/engine/model/FunctionDefinition.ts +23 -0
  36. package/src/engine/runtime/expression/Expression.ts +5 -1
  37. package/src/engine/runtime/expression/ExpressionEvaluator.ts +152 -139
  38. package/src/engine/runtime/expression/ExpressionParser.ts +80 -27
  39. package/src/engine/util/duplicate.ts +3 -1
  40. package/src/index.ts +1 -0
@@ -387,18 +387,6 @@ describe('Original Expression Parsing Tests', () => {
387
387
  expect(expr.toString()).toContain('??');
388
388
  });
389
389
 
390
- test('Expression 3: Parent.perCount[Parent.Parent.__index].value. Percentage + \'%\'', () => {
391
- // This expression has:
392
- // 1. Dynamic array index with path expression
393
- // 2. Space before "Percentage" (after the dot)
394
- // 3. String concatenation with '%'
395
- const expression = "Parent.perCount[Parent.Parent.__index].value. Percentage + '%'";
396
-
397
- // Verify parsing succeeds (space before property name is handled)
398
- const expr = new Expression(expression);
399
- expect(expr).toBeDefined();
400
- });
401
-
402
390
  test('Expression 4: Page.dealData.size < Page.dealData.totalElements', () => {
403
391
  // This expression has extra space in the less-than comparison
404
392
  const expression = 'Page.dealData.size < Page.dealData.totalElements';
@@ -415,8 +403,8 @@ describe('Original Expression Parsing Tests', () => {
415
403
  const expressions = [
416
404
  'Steps.floorWeekOne.output.value * {{Page.secondsInDay}}',
417
405
  "Parent.projectInfo.projectType?? '-'",
418
- "Parent.perCount[Parent.Parent.__index].value. Percentage + '%'",
419
406
  'Page.dealData.size < Page.dealData.totalElements',
407
+ 'Store.pageDefinition.{{Store.urlDetails.pageName}}.properties.title.name.value ?? {{Store.pageDefinition.{{Store.urlDetails.pageName}}.properties.title.name.location.expression}}',
420
408
  ];
421
409
 
422
410
  for (const expression of expressions) {
@@ -557,7 +545,7 @@ describe('Original Expression Parsing Tests', () => {
557
545
 
558
546
  test('Expression 7: Nested dynamic bracket index', () => {
559
547
  const parentExtractor = new TestTokenValueExtractor('Parent.', { index: 1 });
560
- const pageExtractor = new TestTokenValueExtractor('Page.', {
548
+ const pageExtractor = new TestTokenValueExtractor('Page.', {
561
549
  matrix: [
562
550
  ['a', 'b', 'c'],
563
551
  ['d', 'e', 'f'],
@@ -575,4 +563,404 @@ describe('Original Expression Parsing Tests', () => {
575
563
 
576
564
  expect(result).toBe('e');
577
565
  });
566
+
567
+ test('Expression 8: Store.pageDefinition.{{Store.urlDetails.pageName}}.properties.title.name.value ?? {{Store.pageDefinition.{{Store.urlDetails.pageName}}.properties.title.name.location.expression}}', () => {
568
+ // Expression: dynamic page key from urlDetails.pageName, nullish coalesce value with location.expression
569
+ const expression =
570
+ 'Store.pageDefinition.{{Store.urlDetails.pageName}}.properties.title.name.value ?? {{Store.pageDefinition.{{Store.urlDetails.pageName}}.properties.title.name.location.expression}}';
571
+
572
+ // Verify parsing succeeds
573
+ const expr = new Expression(expression);
574
+ expect(expr).toBeDefined();
575
+ expect(expr.getOperations().isEmpty()).toBe(false);
576
+
577
+ // Store with urlDetails.pageName = "home" and pageDefinition.home.properties.title.name.value present
578
+ const storeWithValue = new TestTokenValueExtractor('Store.', {
579
+ application: {
580
+ properties: {
581
+ title: 'My Application Title',
582
+ },
583
+ },
584
+ urlDetails: { pageName: 'home' },
585
+ pageDefinition: {
586
+ home: {
587
+ properties: {
588
+ title: {
589
+ name: {
590
+ location: { expression: "'Application : ' + Store.application.properties.title" },
591
+ },
592
+ },
593
+ },
594
+ },
595
+ },
596
+ });
597
+ const valuesMapWithValue: Map<string, TokenValueExtractor> = new Map([
598
+ [storeWithValue.getPrefix(), storeWithValue],
599
+ ]);
600
+
601
+ const ev = new ExpressionEvaluator(expression);
602
+ expect(ev.evaluate(valuesMapWithValue)).toBe('Application : My Application Title');
603
+
604
+ // Store with value null - should return location.expression
605
+
606
+ const storeValueNull = new TestTokenValueExtractor('Store.', {
607
+ urlDetails: { pageName: 'home' },
608
+ pageDefinition: {
609
+ home: {
610
+ properties: {
611
+ title: {
612
+ name: {
613
+ value: null,
614
+ location: { expression: 'Expression Fallback Title' },
615
+ },
616
+ },
617
+ },
618
+ },
619
+ },
620
+ });
621
+ const valuesMapValueNull: Map<string, TokenValueExtractor> = new Map([
622
+ [storeValueNull.getPrefix(), storeValueNull],
623
+ ]);
624
+ expect(ev.evaluate(valuesMapValueNull)).toBe('Expression Fallback Title');
625
+
626
+ // Store with value missing (no .value key) - should return location.expression
627
+ // Use a fresh evaluator to avoid any cached expansion from previous evaluations
628
+ const storeValueMissing = new TestTokenValueExtractor('Store.', {
629
+ urlDetails: { pageName: 'home' },
630
+ pageDefinition: {
631
+ home: {
632
+ properties: {
633
+ title: {
634
+ name: {
635
+ location: { expression: 'Expression Fallback Title' },
636
+ },
637
+ },
638
+ },
639
+ },
640
+ },
641
+ });
642
+ const valuesMapValueMissing: Map<string, TokenValueExtractor> = new Map([
643
+ [storeValueMissing.getPrefix(), storeValueMissing],
644
+ ]);
645
+ const evFresh = new ExpressionEvaluator(expression);
646
+ expect(evFresh.evaluate(valuesMapValueMissing)).toBe('Expression Fallback Title');
647
+ });
648
+
649
+ test('Expression 9a: Simplified nested ternary to isolate issue', () => {
650
+ // Simplified version to isolate the issue
651
+ const expression = `Page.a != undefined ? 'A' : Page.b != undefined ? 'B' : Page.c != undefined ? 'C' : '-'`;
652
+
653
+ // Test with matching condition
654
+ const pageExtractor = new TestTokenValueExtractor('Page.', {
655
+ a: 'test'
656
+ });
657
+
658
+ const valuesMap = new Map([
659
+ [pageExtractor.getPrefix(), pageExtractor]
660
+ ]);
661
+
662
+ const ev = new ExpressionEvaluator(expression);
663
+ expect(ev.evaluate(valuesMap)).toBe('A');
664
+ });
665
+
666
+ test('Expression 9b: Nested ternary with dynamic keys', () => {
667
+ // Test with dynamic keys using {{}}
668
+ const expression = `Page.kycs.{{Parent.id}}.a != undefined ? 'A' : Page.kycs.{{Parent.id}}.b != undefined ? 'B' : '-'`;
669
+
670
+ const pageExtractor = new TestTokenValueExtractor('Page.', {
671
+ kycs: {
672
+ '123': {
673
+ a: 'test'
674
+ }
675
+ }
676
+ });
677
+
678
+ const parentExtractor = new TestTokenValueExtractor('Parent.', {
679
+ id: '123'
680
+ });
681
+
682
+ const valuesMap = new Map([
683
+ [pageExtractor.getPrefix(), pageExtractor],
684
+ [parentExtractor.getPrefix(), parentExtractor]
685
+ ]);
686
+
687
+ const ev = new ExpressionEvaluator(expression);
688
+ expect(ev.evaluate(valuesMap)).toBe('A');
689
+ });
690
+
691
+ test('Expression 9c1: Debug - Simplest failing case', () => {
692
+ // Simplest case that reproduces the issue
693
+ const expression = `true ? 'a' +' '+ 'b' : false ? 'c' +' '+ 'd' : '-'`;
694
+
695
+ const valuesMap = new Map();
696
+
697
+ const ev = new ExpressionEvaluator(expression);
698
+ const result = ev.evaluate(valuesMap);
699
+ expect(result).toBe('a b');
700
+ });
701
+
702
+ test('Expression 9c2: Debug - With property access', () => {
703
+ // Test with property access in ternary
704
+ const expression = `true ? Page.a +' '+ Page.b : false ? Page.c +' '+ Page.d : '-'`;
705
+
706
+ const pageExtractor = new TestTokenValueExtractor('Page.', {
707
+ a: 'Hello',
708
+ b: 'World'
709
+ });
710
+
711
+ const valuesMap = new Map([
712
+ [pageExtractor.getPrefix(), pageExtractor]
713
+ ]);
714
+
715
+ const ev = new ExpressionEvaluator(expression);
716
+ const result = ev.evaluate(valuesMap);
717
+ expect(result).toBe('Hello World');
718
+ });
719
+
720
+ test('Expression 9c3: Debug - With deeper property paths like original', () => {
721
+ // Test with deeper property paths like the original failing expression
722
+ const expression = `true ? Page.x.a.first +' '+ Page.x.a.last : '-'`;
723
+
724
+ const pageExtractor = new TestTokenValueExtractor('Page.', {
725
+ x: {
726
+ a: {
727
+ first: 'John',
728
+ last: 'Doe'
729
+ }
730
+ }
731
+ });
732
+
733
+ const valuesMap = new Map([
734
+ [pageExtractor.getPrefix(), pageExtractor]
735
+ ]);
736
+
737
+ const ev = new ExpressionEvaluator(expression);
738
+ const result = ev.evaluate(valuesMap);
739
+ expect(result).toBe('John Doe');
740
+ });
741
+
742
+ test('Expression 9c4: Debug - Nested ternary with deep paths', () => {
743
+ // Test nested ternary with deep property paths
744
+ const expression = `true ? Page.x.a.first +' '+ Page.x.a.last : false ? Page.x.b.first +' '+ Page.x.b.last : '-'`;
745
+
746
+ const pageExtractor = new TestTokenValueExtractor('Page.', {
747
+ x: {
748
+ a: {
749
+ first: 'John',
750
+ last: 'Doe'
751
+ }
752
+ }
753
+ });
754
+
755
+ const valuesMap = new Map([
756
+ [pageExtractor.getPrefix(), pageExtractor]
757
+ ]);
758
+
759
+ const ev = new ExpressionEvaluator(expression);
760
+ const result = ev.evaluate(valuesMap);
761
+ expect(result).toBe('John Doe');
762
+ });
763
+
764
+ test('Expression 9c5: Debug - With NUMERIC property path segment', () => {
765
+ // Test numeric segment in property path - should now work with the fix
766
+ const expression = `true ? Page.x.123.a.first +' '+ Page.x.123.a.last : '-'`;
767
+
768
+ const pageExtractor = new TestTokenValueExtractor('Page.', {
769
+ x: {
770
+ '123': {
771
+ a: {
772
+ first: 'John',
773
+ last: 'Doe'
774
+ }
775
+ }
776
+ }
777
+ });
778
+
779
+ const valuesMap = new Map([
780
+ [pageExtractor.getPrefix(), pageExtractor]
781
+ ]);
782
+
783
+ const ev = new ExpressionEvaluator(expression);
784
+ const result = ev.evaluate(valuesMap);
785
+ expect(result).toBe('John Doe');
786
+ });
787
+
788
+ test('Expression 9c6: Debug - With ObjectId-like property path segment', () => {
789
+ // Test with alphanumeric ObjectId-like value (e.g., MongoDB ObjectId)
790
+ const expression = `true ? Page.x.507f1f77bcf86cd799439011.a.first +' '+ Page.x.507f1f77bcf86cd799439011.a.last : '-'`;
791
+
792
+ const pageExtractor = new TestTokenValueExtractor('Page.', {
793
+ x: {
794
+ '507f1f77bcf86cd799439011': {
795
+ a: {
796
+ first: 'Jane',
797
+ last: 'Smith'
798
+ }
799
+ }
800
+ }
801
+ });
802
+
803
+ const valuesMap = new Map([
804
+ [pageExtractor.getPrefix(), pageExtractor]
805
+ ]);
806
+
807
+ const ev = new ExpressionEvaluator(expression);
808
+ const result = ev.evaluate(valuesMap);
809
+ expect(result).toBe('Jane Smith');
810
+ });
811
+
812
+ test('Expression 9c7: Debug - With underscore in property path segment', () => {
813
+ // Test with property names containing underscores and numbers
814
+ const expression = `true ? Page.x.123_abc.a.first +' '+ Page.x.123_abc.a.last : '-'`;
815
+
816
+ const pageExtractor = new TestTokenValueExtractor('Page.', {
817
+ x: {
818
+ '123_abc': {
819
+ a: {
820
+ first: 'Bob',
821
+ last: 'Johnson'
822
+ }
823
+ }
824
+ }
825
+ });
826
+
827
+ const valuesMap = new Map([
828
+ [pageExtractor.getPrefix(), pageExtractor]
829
+ ]);
830
+
831
+ const ev = new ExpressionEvaluator(expression);
832
+ const result = ev.evaluate(valuesMap);
833
+ expect(result).toBe('Bob Johnson');
834
+ });
835
+
836
+ test('Expression 9c: Debug - Parse expanded expression without {{}}', () => {
837
+ // Test the expanded expression directly (without {{}}) to see if parsing is the issue
838
+ const expandedExpression = `Page.kycs.123.a != undefined ? Page.kycs.123.a.first +' '+ Page.kycs.123.a.last : Page.kycs.123.b != undefined ? Page.kycs.123.b.first +' '+ Page.kycs.123.b.last : '-'`;
839
+
840
+ const pageExtractor = new TestTokenValueExtractor('Page.', {
841
+ kycs: {
842
+ '123': {
843
+ a: {
844
+ first: 'John',
845
+ last: 'Doe'
846
+ }
847
+ }
848
+ }
849
+ });
850
+
851
+ const valuesMap = new Map([
852
+ [pageExtractor.getPrefix(), pageExtractor]
853
+ ]);
854
+
855
+ const ev = new ExpressionEvaluator(expandedExpression);
856
+ const result = ev.evaluate(valuesMap);
857
+ expect(result).toBe('John Doe');
858
+ });
859
+
860
+ test('Expression 9c: Nested ternary with string concatenation', () => {
861
+ // Test with string concatenation in ternary branches
862
+ const expression = `Page.kycs.{{Parent.id}}.a != undefined ? Page.kycs.{{Parent.id}}.a.first +' '+ Page.kycs.{{Parent.id}}.a.last : Page.kycs.{{Parent.id}}.b != undefined ? Page.kycs.{{Parent.id}}.b.first +' '+ Page.kycs.{{Parent.id}}.b.last : '-'`;
863
+
864
+ const pageExtractor = new TestTokenValueExtractor('Page.', {
865
+ kycs: {
866
+ '123': {
867
+ a: {
868
+ first: 'John',
869
+ last: 'Doe'
870
+ }
871
+ }
872
+ }
873
+ });
874
+
875
+ const parentExtractor = new TestTokenValueExtractor('Parent.', {
876
+ id: '123'
877
+ });
878
+
879
+ const valuesMap = new Map([
880
+ [pageExtractor.getPrefix(), pageExtractor],
881
+ [parentExtractor.getPrefix(), parentExtractor]
882
+ ]);
883
+
884
+ const ev = new ExpressionEvaluator(expression);
885
+ expect(ev.evaluate(valuesMap)).toBe('John Doe');
886
+ });
887
+
888
+ test('Expression 9: Complex nested ternary with dynamic keys and string concatenation', () => {
889
+ // This is a complex multi-level ternary with dynamic object keys and string concatenation
890
+ const expression = `Page.kycs.{{Parent.kycAccountId}}.individual != undefined ? Page.kycs.{{Parent.kycAccountId}}.individual.basic.personalInformation.firstName +' '+ Page.kycs.{{Parent.kycAccountId}}.individual.basic.personalInformation.lastName : Page.kycs.{{Parent.kycAccountId}}.joint != undefined ? Page.kycs.{{Parent.kycAccountId}}.joint.mainApplicant.individual.basic.personalInformation.firstName +' '+ Page.kycs.{{Parent.kycAccountId}}.joint.mainApplicant.individual.basic.personalInformation.lastName : Page.kycs.{{Parent.kycAccountId}}.llp != undefined ? Page.kycs.{{Parent.kycAccountId}}.llp.authorizePerson.personalInformation.firstName +' '+ Page.kycs.{{Parent.kycAccountId}}.llp.authorizePerson.personalInformation.lastName : Page.kycs.{{Parent.kycAccountId}}.partnership != undefined ? Page.kycs.{{Parent.kycAccountId}}.partnership.authorizePerson.personalInformation.firstName +' '+ Page.kycs.{{Parent.kycAccountId}}.partnership.authorizePerson.personalInformation.lastName : Page.kycs.{{Parent.kycAccountId}}.private != undefined ? Page.kycs.{{Parent.kycAccountId}}.private.authorizePerson.personalInformation.firstName +' '+ Page.kycs.{{Parent.kycAccountId}}.private.authorizePerson.personalInformation.lastName : Page.kycs.{{Parent.kycAccountId}}.trust != undefined ? Page.kycs.{{Parent.kycAccountId}}.trust.authorizePerson.personalInformation.firstName +' '+ Page.kycs.{{Parent.kycAccountId}}.trust.authorizePerson.personalInformation.lastName : Page.kycs.{{Parent.kycAccountId}}.huf != undefined ? Page.kycs.{{Parent.kycAccountId}}.huf.kartaInformation.personalInformation.firstName +' '+ Page.kycs.{{Parent.kycAccountId}}.huf.kartaInformation.personalInformation.lastName : '-'`;
891
+
892
+ // Test with individual account type
893
+ const pageExtractorIndividual = new TestTokenValueExtractor('Page.', {
894
+ kycs: {
895
+ '123': {
896
+ individual: {
897
+ basic: {
898
+ personalInformation: {
899
+ firstName: 'John',
900
+ lastName: 'Doe'
901
+ }
902
+ }
903
+ }
904
+ }
905
+ }
906
+ });
907
+
908
+ const parentExtractorIndividual = new TestTokenValueExtractor('Parent.', {
909
+ kycAccountId: '123'
910
+ });
911
+
912
+ const valuesMapIndividual = new Map([
913
+ [pageExtractorIndividual.getPrefix(), pageExtractorIndividual],
914
+ [parentExtractorIndividual.getPrefix(), parentExtractorIndividual]
915
+ ]);
916
+
917
+ const ev = new ExpressionEvaluator(expression);
918
+ expect(ev.evaluate(valuesMapIndividual)).toBe('John Doe');
919
+
920
+ // Test with fallback (no matching account type)
921
+ const pageExtractorFallback = new TestTokenValueExtractor('Page.', {
922
+ kycs: {
923
+ '456': {}
924
+ }
925
+ });
926
+
927
+ const parentExtractorFallback = new TestTokenValueExtractor('Parent.', {
928
+ kycAccountId: '456'
929
+ });
930
+
931
+ const valuesMapFallback = new Map([
932
+ [pageExtractorFallback.getPrefix(), pageExtractorFallback],
933
+ [parentExtractorFallback.getPrefix(), parentExtractorFallback]
934
+ ]);
935
+
936
+ const evFallback = new ExpressionEvaluator(expression);
937
+ expect(evFallback.evaluate(valuesMapFallback)).toBe('-');
938
+ });
939
+
940
+ test('Expression trimming: Leading and trailing whitespace should be trimmed', () => {
941
+ // Test that expressions with leading/trailing whitespace are properly trimmed
942
+ const expr1 = new Expression(' Page.value ');
943
+ expect(expr1.getExpression()).toBe('Page.value');
944
+
945
+ const expr2 = new Expression(' true ? "yes" : "no" ');
946
+ expect(expr2.getExpression()).toBe('true ? "yes" : "no"');
947
+
948
+ const expr3 = new Expression('\t\nPage.x + 5\n\t');
949
+ expect(expr3.getExpression()).toBe('Page.x + 5');
950
+
951
+ // Test with actual evaluation to ensure trimming doesn't break functionality
952
+ const pageExtractor = new TestTokenValueExtractor('Page.', {
953
+ value: 'test',
954
+ x: 10
955
+ });
956
+ const valuesMap = new Map([
957
+ [pageExtractor.getPrefix(), pageExtractor]
958
+ ]);
959
+
960
+ const ev1 = new ExpressionEvaluator(' Page.value ');
961
+ expect(ev1.evaluate(valuesMap)).toBe('test');
962
+
963
+ const ev2 = new ExpressionEvaluator(' Page.x + 5 ');
964
+ expect(ev2.evaluate(valuesMap)).toBe(15);
965
+ });
578
966
  });