Skip to content

@criterionx/generators ​

Code generation tools for Criterion decision engine. Define decisions declaratively and generate runtime code.

Installation ​

bash
npm install @criterionx/generators @criterionx/core

Overview ​

The generators package provides two main capabilities:

  1. Runtime Parsing - Parse declarative specs into runtime decisions
  2. Code Generation - Generate TypeScript code from specs

Decision Specification Format ​

Decisions are defined in a declarative JSON/YAML format:

typescript
const spec: DecisionSpec = {
  id: "eligibility",
  version: "1.0.0",
  description: "Check user eligibility",
  input: {
    age: { type: "number", min: 0 },
    creditScore: { type: "number", min: 300, max: 850 }
  },
  output: {
    eligible: { type: "boolean" },
    reason: { type: "string" }
  },
  profile: {
    minAge: { type: "number" },
    minScore: { type: "number" }
  },
  rules: [
    {
      id: "too-young",
      when: [{ field: "input.age", operator: "lt", value: "$profile.minAge" }],
      emit: { eligible: false, reason: "Minimum age not met" }
    },
    {
      id: "low-score",
      when: [{ field: "input.creditScore", operator: "lt", value: "$profile.minScore" }],
      emit: { eligible: false, reason: "Credit score too low" }
    },
    {
      id: "approved",
      when: "always",
      emit: { eligible: true, reason: "All requirements met" }
    }
  ]
};

Runtime Parsing ​

parseDecisionSpec ​

Parse a specification into a runtime Decision.

typescript
import { parseDecisionSpec } from '@criterionx/generators';
import { Engine } from '@criterionx/core';

const decision = parseDecisionSpec(spec);
const engine = new Engine();

const result = engine.run(decision,
  { age: 25, creditScore: 720 },
  { profile: { minAge: 18, minScore: 650 } }
);

parseDecisionSpecs ​

Parse multiple specifications.

typescript
import { parseDecisionSpecs } from '@criterionx/generators';

const decisions = parseDecisionSpecs([spec1, spec2, spec3]);
// Returns Map<string, Decision>

const pricing = decisions.get('pricing');

Code Generation ​

generateDecisionCode ​

Generate TypeScript code from a specification.

typescript
import { generateDecisionCode } from '@criterionx/generators';

const { code, decisionId, exportName } = generateDecisionCode(spec);

console.log(code);
// import { defineDecision } from "@criterionx/core";
// import { z } from "zod";
//
// export const eligibilityDecision = defineDecision({
//   id: "eligibility",
//   version: "1.0.0",
//   inputSchema: z.object({
//     age: z.number().min(0),
//     creditScore: z.number().min(300).max(850),
//   }),
//   ...
// });

Options ​

typescript
generateDecisionCode(spec, {
  includeComments?: boolean;  // Include JSDoc comments (default: true)
  includeImports?: boolean;   // Include import statements (default: true)
  exportName?: string;        // Custom export name
});

generateDecisionsFile ​

Generate a file with multiple decisions.

typescript
import { generateDecisionsFile } from '@criterionx/generators';

const code = generateDecisionsFile([spec1, spec2, spec3]);
// Single file with all decisions and shared imports

Schema Specification ​

Field Types ​

TypeZod EquivalentOptions
stringz.string()enum, optional, default
numberz.number()min, max, optional, default
booleanz.boolean()optional, default
datez.coerce.date()optional, default
arrayz.array()items, optional
objectz.object()properties, optional

Examples ​

typescript
// String with enum
{ type: "string", enum: ["active", "pending", "inactive"] }
// -> z.enum(["active", "pending", "inactive"])

// Number with range
{ type: "number", min: 0, max: 100 }
// -> z.number().min(0).max(100)

// Optional with default
{ type: "number", optional: true, default: 0 }
// -> z.number().optional().default(0)

// Array of strings
{ type: "array", items: "string" }
// -> z.array(z.string())

// Nested object
{
  type: "object",
  properties: {
    name: { type: "string" },
    age: { type: "number" }
  }
}
// -> z.object({ name: z.string(), age: z.number() })

Rule Conditions ​

Operators ​

OperatorDescriptionExample
eqEqual{ operator: "eq", value: "active" }
neqNot equal{ operator: "neq", value: null }
gtGreater than{ operator: "gt", value: 100 }
gteGreater or equal{ operator: "gte", value: "$profile.min" }
ltLess than{ operator: "lt", value: 0 }
lteLess or equal{ operator: "lte", value: "$profile.max" }
inValue in array{ operator: "in", value: "$profile.allowed" }
containsArray contains{ operator: "contains", value: "admin" }
matchesRegex match{ operator: "matches", value: "^[A-Z]+" }

Field References ​

Reference input or profile values with paths:

typescript
// Reference input
{ field: "input.age", operator: "gte", value: 18 }

// Reference profile in condition value
{ field: "input.score", operator: "gte", value: "$profile.minScore" }

Always Match ​

Use "always" for catch-all rules:

typescript
{
  id: "default",
  when: "always",
  emit: { result: "default" }
}

Emit Expressions ​

Static Values ​

typescript
emit: { eligible: true, message: "Approved" }

Field References ​

typescript
emit: {
  result: "$input.value",
  threshold: "$profile.minValue"
}

Expressions ​

typescript
emit: {
  total: "$input.quantity * $profile.unitPrice",
  discounted: "$input.quantity * $profile.unitPrice * (1 - $profile.discount)"
}

Rule Priority ​

Rules are evaluated in priority order (lower first):

typescript
rules: [
  { id: "high-priority", priority: 1, when: [...], emit: {...} },
  { id: "normal", priority: 5, when: [...], emit: {...} },
  { id: "fallback", priority: 10, when: "always", emit: {...} }
]

Use Cases ​

Configuration-Driven Decisions ​

typescript
// Load specs from database or config files
const specs = await loadDecisionSpecs();
const decisions = parseDecisionSpecs(specs);

// Use at runtime
const engine = new Engine();
const result = engine.run(
  decisions.get(decisionId)!,
  input,
  { profile }
);

Build-Time Code Generation ​

typescript
// generate-decisions.ts
import { generateDecisionsFile } from '@criterionx/generators';
import { writeFileSync } from 'fs';
import specs from './decision-specs.json';

const code = generateDecisionsFile(specs);
writeFileSync('src/generated/decisions.ts', code);

YAML Configuration ​

yaml
# decisions.yaml
- id: pricing
  version: "1.0.0"
  input:
    quantity:
      type: number
      min: 1
  output:
    total:
      type: number
  profile:
    unitPrice:
      type: number
  rules:
    - id: calculate
      when: always
      emit:
        total: "$input.quantity * $profile.unitPrice"
typescript
import { parseDecisionSpec } from '@criterionx/generators';
import { load } from 'js-yaml';

const specs = load(readFileSync('decisions.yaml', 'utf8'));
const decisions = specs.map(parseDecisionSpec);

Released under the MIT License.