drools-builder 0.1.2 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -77,6 +77,7 @@ Plain metamodel objects are accepted everywhere a builder is expected.
77
77
  const file = createFile('fraud-rules')
78
78
  .import('com.example.Account')
79
79
  .import('com.example.FraudAlert')
80
+ .global('com.example.AlertService', 'alertService')
80
81
  .addRule(rule)
81
82
  .addRule(createRule('Another Rule').when(...).then(...))
82
83
  .build()
package/dist/index.cjs CHANGED
@@ -430,6 +430,7 @@ function resolveRule(input) {
430
430
  var DroolsFileBuilder = class {
431
431
  constructor(name) {
432
432
  this._imports = [];
433
+ this._globals = [];
433
434
  this._rules = [];
434
435
  this._name = name;
435
436
  }
@@ -444,6 +445,17 @@ var DroolsFileBuilder = class {
444
445
  this._imports.push(className);
445
446
  return this;
446
447
  }
448
+ /**
449
+ * Declare a global variable available to all rules in this file.
450
+ * Emits `global type name;` in the DRL header.
451
+ *
452
+ * @example
453
+ * .global('com.example.AlertService', 'alertService')
454
+ */
455
+ global(type, name) {
456
+ this._globals.push({ type, name });
457
+ return this;
458
+ }
447
459
  /**
448
460
  * Add a rule to the file.
449
461
  * Accepts a plain Rule object or a RuleBuilder (auto-resolved via .build()).
@@ -460,6 +472,7 @@ var DroolsFileBuilder = class {
460
472
  return {
461
473
  name: this._name,
462
474
  imports: [...this._imports],
475
+ globals: [...this._globals],
463
476
  rules: [...this._rules]
464
477
  };
465
478
  }
@@ -524,6 +537,144 @@ function rawConsequence(code) {
524
537
  return { kind: "RawConsequence", code };
525
538
  }
526
539
 
540
+ // src/rule-builder/parser/MetaToDRLTransformer.ts
541
+ function generateConstraint(c) {
542
+ switch (c.kind) {
543
+ case "FieldConstraint":
544
+ return `${c.binding ? `${c.binding} : ` : ""}${c.field} ${c.operator} ${c.value}`;
545
+ case "BindingConstraint":
546
+ return `${c.binding} : ${c.field}`;
547
+ case "RawConstraint":
548
+ return c.expression;
549
+ }
550
+ }
551
+ function generateCondition(cond, indent = " ") {
552
+ switch (cond.kind) {
553
+ case "FactPattern": {
554
+ const binding = cond.binding ? `${cond.binding} : ` : "";
555
+ const constraints = cond.constraints.map(generateConstraint).join(", ");
556
+ return `${binding}${cond.factType}( ${constraints} )`;
557
+ }
558
+ case "UnboundPattern": {
559
+ const constraints = cond.constraints.map(generateConstraint).join(", ");
560
+ return `${cond.factType}( ${constraints} )`;
561
+ }
562
+ case "And": {
563
+ const parts = cond.conditions.map((c) => generateCondition(c, indent + " "));
564
+ return `( ${parts.join(`
565
+ ${indent} and `)} )`;
566
+ }
567
+ case "Or": {
568
+ const parts = cond.conditions.map((c) => generateCondition(c, indent + " "));
569
+ return `( ${parts.join(`
570
+ ${indent} or `)} )`;
571
+ }
572
+ case "Not":
573
+ return `not( ${generateCondition(cond.condition, indent)} )`;
574
+ case "Exists":
575
+ return `exists( ${generateCondition(cond.condition, indent)} )`;
576
+ case "Forall":
577
+ return `forall( ${generateCondition(cond.condition, indent)} )`;
578
+ case "Accumulate": {
579
+ const source = generateCondition(cond.source, indent + " ");
580
+ const fns = cond.functions.map((f) => `${f.binding} : ${f.function}( ${f.argument} )`).join(", ");
581
+ const result = cond.resultConstraint ? `;
582
+ ${indent} ${cond.resultConstraint}` : "";
583
+ return `accumulate(
584
+ ${indent} ${source};
585
+ ${indent} ${fns}${result}
586
+ ${indent})`;
587
+ }
588
+ case "From":
589
+ return `${generateCondition(cond.pattern, indent)} from ${cond.expression}`;
590
+ case "Eval":
591
+ return `eval( ${cond.expression} )`;
592
+ case "RawCondition":
593
+ return cond.drl;
594
+ }
595
+ }
596
+ function generateWhenBlock(conditions, indent = " ") {
597
+ const flat = conditions.length === 1 && conditions[0].kind === "And" ? conditions[0].conditions : conditions;
598
+ return flat.map((c) => `${indent}${generateCondition(c, indent)}`).join("\n");
599
+ }
600
+ function generateConsequence(cons, indent = " ") {
601
+ switch (cons.kind) {
602
+ case "ModifyConsequence": {
603
+ const mods = cons.modifications.map((m) => `${m.method}( ${m.args.join(", ")} )`).join(`,
604
+ ${indent} `);
605
+ return `modify( ${cons.binding} ) {
606
+ ${indent} ${mods}
607
+ ${indent}}`;
608
+ }
609
+ case "InsertConsequence":
610
+ return `insert( ${cons.objectExpression} );`;
611
+ case "RetractConsequence":
612
+ return `retract( ${cons.binding} );`;
613
+ case "SetGlobalConsequence":
614
+ return `${cons.expression};`;
615
+ case "RawConsequence":
616
+ return `${cons.code};`;
617
+ case "ReturnConsequence":
618
+ return cons.expression ? `return ${cons.expression};` : "return;";
619
+ case "IfConsequence": {
620
+ const thenLines = cons.then.map((c) => `${indent} ${generateConsequence(c, indent + " ")}`).join("\n");
621
+ if (cons.else && cons.else.length > 0) {
622
+ const elseLines = cons.else.map((c) => `${indent} ${generateConsequence(c, indent + " ")}`).join("\n");
623
+ return `if (${cons.condition}) {
624
+ ${thenLines}
625
+ ${indent}} else {
626
+ ${elseLines}
627
+ ${indent}}`;
628
+ }
629
+ return `if (${cons.condition}) {
630
+ ${thenLines}
631
+ ${indent}}`;
632
+ }
633
+ }
634
+ }
635
+ function generateDeclaration(decl) {
636
+ const attrs = decl.attributes.map((a) => ` ${a.name} : ${a.type}`).join("\n");
637
+ return `declare ${decl.className}${attrs ? "\n" + attrs + "\n" : "\n"}end`;
638
+ }
639
+ function generateFunction(fn, indent = " ") {
640
+ const body = fn.body.map((c) => `${indent}${generateConsequence(c, indent)}`).join("\n");
641
+ return `function ${fn.returnType} ${fn.name}(${fn.params}) {
642
+ ${body}
643
+ }`;
644
+ }
645
+ function generateRule(rule) {
646
+ const lines = [`rule "${rule.name}"`];
647
+ if (rule.salience !== void 0) lines.push(` salience ${rule.salience}`);
648
+ if (rule.agendaGroup !== void 0) lines.push(` agenda-group "${rule.agendaGroup}"`);
649
+ if (rule.ruleFlowGroup !== void 0) lines.push(` ruleflow-group "${rule.ruleFlowGroup}"`);
650
+ if (rule.noLoop) lines.push(" no-loop true");
651
+ if (rule.lockOnActive) lines.push(" lock-on-active true");
652
+ lines.push(" when");
653
+ lines.push(generateWhenBlock(rule.conditions));
654
+ lines.push(" then");
655
+ lines.push(rule.consequences.map((c) => ` ${generateConsequence(c)}`).join("\n"));
656
+ lines.push("end");
657
+ return lines.join("\n");
658
+ }
659
+ var MetaToDRLTransformer = {
660
+ generate(file) {
661
+ const sections = [];
662
+ if (file.imports.length > 0)
663
+ sections.push(file.imports.map((i) => `import ${i};`).join("\n"));
664
+ if (file.globals.length > 0)
665
+ sections.push(file.globals.map((g) => `global ${g.type} ${g.name};`).join("\n"));
666
+ if (file.declarations && file.declarations.length > 0)
667
+ sections.push(file.declarations.map((d) => generateDeclaration(d)).join("\n\n"));
668
+ if (file.functions && file.functions.length > 0)
669
+ sections.push(file.functions.map((fn) => generateFunction(fn)).join("\n\n"));
670
+ sections.push(file.rules.map(generateRule).join("\n\n"));
671
+ return sections.join("\n\n");
672
+ },
673
+ generateRule(rule) {
674
+ return generateRule(rule);
675
+ }
676
+ };
677
+
527
678
  // src/rule-builder/parser/DRLToMetaTransformer.ts
528
679
  function indexAtDepth0(text, needle) {
529
680
  let depth = 0;
@@ -624,26 +775,63 @@ function parseImports(drl) {
624
775
  while ((m = re.exec(drl)) !== null) imports.push(m[1].trim());
625
776
  return imports;
626
777
  }
778
+ function parseGlobals(drl) {
779
+ const globals = [];
780
+ const re = /^\s*global\s+(\S+)\s+(\S+?)\s*;?$/gm;
781
+ let m;
782
+ while ((m = re.exec(drl)) !== null) globals.push({ type: m[1], name: m[2] });
783
+ return globals;
784
+ }
627
785
  function extractRuleBlocks(drl) {
628
786
  const blocks = [];
629
- const re = /\brule\s+"[^"]*"[\s\S]*?\bend\b/g;
787
+ const re = /\brule\s+(?:"[^"]*"|'[^']*')[\s\S]*?\bend\b/g;
630
788
  let m;
631
789
  while ((m = re.exec(drl)) !== null) blocks.push(m[0]);
632
790
  return blocks;
633
791
  }
792
+ function parseFunctions(drl) {
793
+ const results = [];
794
+ const re = /\bfunction\s+([\w<>?,\s\[\]]+?)\s+(\w+)\s*\(([^)]*)\)\s*\{([^{}]*(?:\{[^{}]*\}[^{}]*)*)\}/g;
795
+ let m;
796
+ while ((m = re.exec(drl)) !== null) {
797
+ results.push({
798
+ returnType: m[1].trim(),
799
+ name: m[2].trim(),
800
+ params: m[3].trim(),
801
+ body: parseConsequences(m[4].trim())
802
+ });
803
+ }
804
+ return results;
805
+ }
806
+ function parseDeclarations(drl) {
807
+ const results = [];
808
+ const re = /\bdeclare\s+(\w+)\s*([\s\S]*?)\bend\b/g;
809
+ let m;
810
+ while ((m = re.exec(drl)) !== null) {
811
+ const className = m[1].trim();
812
+ const attributes = [];
813
+ const attrRe = /^\s*(\w+)\s*:\s*(\S+)/gm;
814
+ let a;
815
+ while ((a = attrRe.exec(m[2])) !== null) {
816
+ attributes.push({ name: a[1].trim(), type: a[2].trim() });
817
+ }
818
+ results.push({ className, attributes });
819
+ }
820
+ return results;
821
+ }
634
822
  function parseRuleName(block) {
635
- const m = block.match(/\brule\s+"([^"]+)"/);
636
- return m ? m[1] : "unknown";
823
+ const m = block.match(/\brule\s+(?:"([^"]+)"|'([^']+)')/);
824
+ return m ? m[1] ?? m[2] : "unknown";
637
825
  }
638
826
  function parseRuleAttributes(block) {
639
827
  const attrs = {};
640
- const m = block.match(/\brule\s+"[^"]+"\s*([\s\S]*?)\bwhen\b/);
828
+ const m = block.match(/\brule\s+(?:"[^"]+"|'[^']+')\s*([\s\S]*?)\bwhen\b/);
641
829
  if (!m) return attrs;
642
830
  const attr = m[1];
643
831
  const salience = attr.match(/\bsalience\s+(-?\d+)/);
644
832
  if (salience) attrs.salience = parseInt(salience[1], 10);
645
- if (/\bno-loop\s+true\b/.test(attr)) attrs.noLoop = true;
646
- if (/\block-on-active\s+true\b/.test(attr)) attrs.lockOnActive = true;
833
+ if (!/\bno-loop\s+false\b/.test(attr) && /\bno-loop\b/.test(attr)) attrs.noLoop = true;
834
+ if (!/\block-on-active\s+false\b/.test(attr) && /\block-on-active\b/.test(attr)) attrs.lockOnActive = true;
647
835
  const ag = attr.match(/\bagenda-group\s+"([^"]+)"/);
648
836
  if (ag) attrs.agendaGroup = ag[1];
649
837
  const rfg = attr.match(/\bruleflow-group\s+"([^"]+)"/);
@@ -823,6 +1011,23 @@ function parseConsequences(then) {
823
1011
  function parseNextConsequence(text) {
824
1012
  const t = text.trim();
825
1013
  if (!t) return null;
1014
+ if (/^if\s*\(/.test(t)) {
1015
+ const condition = extractBalanced(t, "(", ")");
1016
+ const afterCond = t.slice(t.indexOf("(") + condition.length + 2).trim();
1017
+ const thenBlock = extractBalanced(afterCond, "{", "}");
1018
+ const thenConsequences = parseConsequences(thenBlock);
1019
+ let afterThen = afterCond.slice(afterCond.indexOf("{") + thenBlock.length + 2).trim();
1020
+ let elseConsequences;
1021
+ if (/^else\s*\{/.test(afterThen)) {
1022
+ const elseBlock = extractBalanced(afterThen, "{", "}");
1023
+ elseConsequences = parseConsequences(elseBlock);
1024
+ afterThen = afterThen.slice(afterThen.indexOf("{") + elseBlock.length + 2).trim();
1025
+ }
1026
+ return {
1027
+ consequence: { kind: "IfConsequence", condition: condition.trim(), then: thenConsequences, ...elseConsequences && { else: elseConsequences } },
1028
+ rest: afterThen
1029
+ };
1030
+ }
826
1031
  if (/^modify\s*\(/.test(t)) {
827
1032
  const m = t.match(/^modify\s*\(\s*(\$\w+)\s*\)/);
828
1033
  if (m) {
@@ -845,6 +1050,14 @@ function parseNextConsequence(text) {
845
1050
  const rest = t.slice(t.indexOf("(") + inner.length + 2).replace(/^\s*;/, "");
846
1051
  return { consequence: { kind: "RetractConsequence", binding: inner.trim() }, rest };
847
1052
  }
1053
+ if (/^return\b/.test(t)) {
1054
+ const semiIdx2 = indexAtDepth0(t, ";");
1055
+ const expression = t.slice("return".length, semiIdx2 !== -1 ? semiIdx2 : void 0).trim();
1056
+ return {
1057
+ consequence: { kind: "ReturnConsequence", expression },
1058
+ rest: semiIdx2 !== -1 ? t.slice(semiIdx2 + 1) : ""
1059
+ };
1060
+ }
848
1061
  const semiIdx = indexAtDepth0(t, ";");
849
1062
  if (semiIdx !== -1)
850
1063
  return { consequence: { kind: "RawConsequence", code: t.slice(0, semiIdx).trim() }, rest: t.slice(semiIdx + 1) };
@@ -863,7 +1076,10 @@ var DRLToMetaTransformer = {
863
1076
  const clean = stripComments(drl);
864
1077
  return {
865
1078
  name: "parsed",
866
- imports: parseImports(clean),
1079
+ imports: [...new Set(parseImports(clean))],
1080
+ globals: parseGlobals(clean),
1081
+ declarations: parseDeclarations(clean),
1082
+ functions: parseFunctions(clean),
867
1083
  rules: extractRuleBlocks(clean).map((block) => DRLToMetaTransformer.parseRule(block))
868
1084
  };
869
1085
  },
@@ -876,112 +1092,6 @@ var DRLToMetaTransformer = {
876
1092
  };
877
1093
  }
878
1094
  };
879
-
880
- // src/rule-builder/parser/MetaToDRLTransformer.ts
881
- function generateConstraint(c) {
882
- switch (c.kind) {
883
- case "FieldConstraint":
884
- return `${c.binding ? `${c.binding} : ` : ""}${c.field} ${c.operator} ${c.value}`;
885
- case "BindingConstraint":
886
- return `${c.binding} : ${c.field}`;
887
- case "RawConstraint":
888
- return c.expression;
889
- }
890
- }
891
- function generateCondition(cond, indent = " ") {
892
- switch (cond.kind) {
893
- case "FactPattern": {
894
- const binding = cond.binding ? `${cond.binding} : ` : "";
895
- const constraints = cond.constraints.map(generateConstraint).join(", ");
896
- return `${binding}${cond.factType}( ${constraints} )`;
897
- }
898
- case "UnboundPattern": {
899
- const constraints = cond.constraints.map(generateConstraint).join(", ");
900
- return `${cond.factType}( ${constraints} )`;
901
- }
902
- case "And": {
903
- const parts = cond.conditions.map((c) => generateCondition(c, indent + " "));
904
- return `( ${parts.join(`
905
- ${indent} and `)} )`;
906
- }
907
- case "Or": {
908
- const parts = cond.conditions.map((c) => generateCondition(c, indent + " "));
909
- return `( ${parts.join(`
910
- ${indent} or `)} )`;
911
- }
912
- case "Not":
913
- return `not( ${generateCondition(cond.condition, indent)} )`;
914
- case "Exists":
915
- return `exists( ${generateCondition(cond.condition, indent)} )`;
916
- case "Forall":
917
- return `forall( ${generateCondition(cond.condition, indent)} )`;
918
- case "Accumulate": {
919
- const source = generateCondition(cond.source, indent + " ");
920
- const fns = cond.functions.map((f) => `${f.binding} : ${f.function}( ${f.argument} )`).join(", ");
921
- const result = cond.resultConstraint ? `;
922
- ${indent} ${cond.resultConstraint}` : "";
923
- return `accumulate(
924
- ${indent} ${source};
925
- ${indent} ${fns}${result}
926
- ${indent})`;
927
- }
928
- case "From":
929
- return `${generateCondition(cond.pattern, indent)} from ${cond.expression}`;
930
- case "Eval":
931
- return `eval( ${cond.expression} )`;
932
- case "RawCondition":
933
- return cond.drl;
934
- }
935
- }
936
- function generateWhenBlock(conditions, indent = " ") {
937
- const flat = conditions.length === 1 && conditions[0].kind === "And" ? conditions[0].conditions : conditions;
938
- return flat.map((c) => `${indent}${generateCondition(c, indent)}`).join("\n");
939
- }
940
- function generateConsequence(cons, indent = " ") {
941
- switch (cons.kind) {
942
- case "ModifyConsequence": {
943
- const mods = cons.modifications.map((m) => `${m.method}( ${m.args.join(", ")} )`).join(`,
944
- ${indent} `);
945
- return `modify( ${cons.binding} ) {
946
- ${indent} ${mods}
947
- ${indent}}`;
948
- }
949
- case "InsertConsequence":
950
- return `insert( ${cons.objectExpression} );`;
951
- case "RetractConsequence":
952
- return `retract( ${cons.binding} );`;
953
- case "SetGlobalConsequence":
954
- return `${cons.expression};`;
955
- case "RawConsequence":
956
- return `${cons.code};`;
957
- }
958
- }
959
- function generateRule(rule) {
960
- const lines = [`rule "${rule.name}"`];
961
- if (rule.salience !== void 0) lines.push(` salience ${rule.salience}`);
962
- if (rule.agendaGroup !== void 0) lines.push(` agenda-group "${rule.agendaGroup}"`);
963
- if (rule.ruleFlowGroup !== void 0) lines.push(` ruleflow-group "${rule.ruleFlowGroup}"`);
964
- if (rule.noLoop) lines.push(" no-loop true");
965
- if (rule.lockOnActive) lines.push(" lock-on-active true");
966
- lines.push(" when");
967
- lines.push(generateWhenBlock(rule.conditions));
968
- lines.push(" then");
969
- lines.push(rule.consequences.map((c) => ` ${generateConsequence(c)}`).join("\n"));
970
- lines.push("end");
971
- return lines.join("\n");
972
- }
973
- var MetaToDRLTransformer = {
974
- generate(file) {
975
- const sections = [];
976
- if (file.imports.length > 0)
977
- sections.push(file.imports.map((i) => `import ${i};`).join("\n"));
978
- sections.push(file.rules.map(generateRule).join("\n\n"));
979
- return sections.join("\n\n");
980
- },
981
- generateRule(rule) {
982
- return generateRule(rule);
983
- }
984
- };
985
1095
  // Annotate the CommonJS export names for ESM import in node:
986
1096
  0 && (module.exports = {
987
1097
  AccumulateBuilder,