Skip to content

Server API

API reference for @criterionx/server.

createServer

Creates a new Criterion HTTP server.

typescript
import { createServer } from "@criterionx/server";

const server = createServer(options);

Parameters

ParameterTypeDescription
optionsServerOptionsServer configuration

ServerOptions

typescript
interface ServerOptions {
  /** Decisions to expose via HTTP */
  decisions: Decision<any, any, any>[];

  /** Default profiles for decisions (keyed by decision ID) */
  profiles?: Record<string, unknown>;

  /** Enable CORS (default: true) */
  cors?: boolean;

  /** Middleware hooks for evaluation lifecycle */
  hooks?: Hooks;

  /** Prometheus metrics configuration */
  metrics?: MetricsOptions;

  /** OpenAPI spec generation configuration */
  openapi?: OpenAPIOptions;
}

Returns

Returns a CriterionServer instance.

Example

typescript
import { createServer } from "@criterionx/server";
import { myDecision } from "./decisions";

const server = createServer({
  decisions: [myDecision],
  profiles: {
    "my-decision": { threshold: 100 },
  },
});

server.listen(3000);

CriterionServer

The server instance returned by createServer.

server.listen(port?)

Starts the HTTP server.

typescript
server.listen(3000);
// Criterion Server starting on port 3000...
//   Decisions: 1
//   Docs: http://localhost:3000/docs
//   API: http://localhost:3000/decisions
ParameterTypeDefaultDescription
portnumber3000Port to listen on

server.handler

Access the underlying Hono app instance for custom middleware.

typescript
const server = createServer({ decisions: [...] });

// Add custom middleware
server.handler.use("*", async (c, next) => {
  const start = Date.now();
  await next();
  console.log(`${c.req.method} ${c.req.url} - ${Date.now() - start}ms`);
});

// Add custom routes
server.handler.get("/health", (c) => c.json({ status: "healthy" }));

server.metrics

Access the metrics collector (if enabled).

typescript
const server = createServer({
  decisions: [...],
  metrics: { enabled: true },
});

// Access metrics collector
const collector = server.metrics;
if (collector) {
  console.log(collector.toPrometheus());
}

Returns MetricsCollector | null. Returns null if metrics are not enabled.


HTTP Endpoints

GET /

Health check endpoint.

Response:

json
{
  "name": "Criterion Server",
  "version": "0.1.0",
  "decisions": 2
}

GET /docs

Interactive documentation UI (HTML).

Returns a Swagger-like interface for browsing and testing decisions.


GET /decisions

List all registered decisions.

Response:

json
{
  "decisions": [
    {
      "id": "transaction-risk",
      "version": "1.0.0",
      "description": "Evaluate transaction risk",
      "meta": { ... }
    }
  ]
}

GET /decisions/:id/schema

Get JSON Schema for a decision.

Response:

json
{
  "id": "transaction-risk",
  "version": "1.0.0",
  "inputSchema": {
    "type": "object",
    "properties": {
      "amount": { "type": "number" }
    },
    "required": ["amount"]
  },
  "outputSchema": {
    "type": "object",
    "properties": {
      "risk": { "type": "string", "enum": ["HIGH", "LOW"] }
    },
    "required": ["risk"]
  },
  "profileSchema": {
    "type": "object",
    "properties": {
      "threshold": { "type": "number" }
    },
    "required": ["threshold"]
  }
}

GET /decisions/:id/endpoint-schema

Get the full endpoint schema including request/response format.

Response:

json
{
  "id": "transaction-risk",
  "method": "POST",
  "path": "/decisions/transaction-risk",
  "requestSchema": {
    "type": "object",
    "properties": {
      "input": { ... },
      "profile": { ... }
    },
    "required": ["input"]
  },
  "responseSchema": {
    "type": "object",
    "properties": {
      "status": { ... },
      "data": { ... },
      "meta": { ... }
    }
  }
}

POST /decisions/:id

Evaluate a decision.

Request Body:

json
{
  "input": { ... },
  "profile": { ... }
}
FieldTypeRequiredDescription
inputobjectYesInput data matching the decision's inputSchema
profileobjectNoProfile to use (overrides default)

Success Response (200):

json
{
  "status": "OK",
  "data": { ... },
  "meta": {
    "decisionId": "transaction-risk",
    "decisionVersion": "1.0.0",
    "matchedRule": "rule-id",
    "explanation": "Why this rule matched",
    "evaluatedAt": "2024-12-29T12:00:00.000Z",
    "evaluatedRules": [
      { "ruleId": "rule-1", "matched": true, "explanation": "..." },
      { "ruleId": "rule-2", "matched": false }
    ]
  }
}

Error Response (400):

json
{
  "error": "Missing 'input' in request body"
}

Utility Functions

toJsonSchema

Convert a Zod schema to JSON Schema.

typescript
import { toJsonSchema } from "@criterionx/server";
import { z } from "zod";

const schema = z.object({
  name: z.string(),
  age: z.number(),
});

const jsonSchema = toJsonSchema(schema);
// {
//   type: "object",
//   properties: {
//     name: { type: "string" },
//     age: { type: "number" }
//   },
//   required: ["name", "age"]
// }

extractDecisionSchema

Extract all schemas from a decision as JSON Schema.

typescript
import { extractDecisionSchema } from "@criterionx/server";

const schema = extractDecisionSchema(myDecision);
// {
//   id: "my-decision",
//   version: "1.0.0",
//   inputSchema: { ... },
//   outputSchema: { ... },
//   profileSchema: { ... }
// }

TypeScript Types

ServerOptions

typescript
interface ServerOptions {
  decisions: Decision<any, any, any>[];
  profiles?: Record<string, unknown>;
  cors?: boolean;
}

EvaluateRequest

typescript
interface EvaluateRequest {
  input: unknown;
  profile?: unknown;
}

DecisionInfo

typescript
interface DecisionInfo {
  id: string;
  version: string;
  description?: string;
  meta?: Record<string, unknown>;
}

DecisionSchema

typescript
interface DecisionSchema {
  id: string;
  version: string;
  inputSchema: JsonSchema;
  outputSchema: JsonSchema;
  profileSchema: JsonSchema;
}

Hooks

typescript
interface Hooks {
  /** Called before decision evaluation */
  beforeEvaluate?: BeforeEvaluateHook;
  /** Called after successful evaluation */
  afterEvaluate?: AfterEvaluateHook;
  /** Called when an error occurs */
  onError?: OnErrorHook;
}

HookContext

typescript
interface HookContext {
  /** ID of the decision being evaluated */
  decisionId: string;
  /** Input data for the decision */
  input: unknown;
  /** Profile being used */
  profile: unknown;
  /** Unique request ID for tracing */
  requestId: string;
  /** Timestamp when evaluation started */
  timestamp: Date;
}

BeforeEvaluateHook

typescript
type BeforeEvaluateHook = (
  ctx: HookContext
) => Promise<Partial<HookContext> | void> | Partial<HookContext> | void;

Can modify context by returning a partial context object. Return undefined to keep original context. Throw to abort evaluation.

AfterEvaluateHook

typescript
type AfterEvaluateHook = (
  ctx: HookContext,
  result: Result<unknown>
) => Promise<void> | void;

Receives the evaluation result. Cannot modify the result. Use for logging, metrics, side effects.

OnErrorHook

typescript
type OnErrorHook = (
  ctx: HookContext,
  error: Error
) => Promise<void> | void;

Called when an error occurs during hook execution or evaluation.

MetricsOptions

typescript
interface MetricsOptions {
  /** Enable metrics collection (default: false) */
  enabled?: boolean;
  /** Endpoint path for metrics (default: /metrics) */
  endpoint?: string;
  /** Histogram buckets for latency in seconds */
  buckets?: number[];
}

MetricsCollector

The MetricsCollector class collects and exports Prometheus metrics.

typescript
import { MetricsCollector } from "@criterionx/server";

const collector = new MetricsCollector();

// Increment a counter
collector.increment("my_counter", { label: "value" });

// Observe a histogram value
collector.observe("my_histogram", { label: "value" }, 0.05);

// Export to Prometheus format
console.log(collector.toPrometheus());

// Reset all metrics
collector.reset();

Methods:

MethodDescription
increment(name, labels?, value?)Increment a counter
observe(name, labels, value)Record value in histogram
getCounter(name, labels?)Get current counter value
getHistogram(name, labels)Get histogram stats (sum, count)
toPrometheus()Export metrics in Prometheus format
reset()Reset all metrics

Metric Constants:

typescript
import {
  METRIC_EVALUATIONS_TOTAL,        // "criterion_evaluations_total"
  METRIC_EVALUATION_DURATION_SECONDS, // "criterion_evaluation_duration_seconds"
  METRIC_RULE_MATCHES_TOTAL,       // "criterion_rule_matches_total"
} from "@criterionx/server";

OpenAPIOptions

typescript
interface OpenAPIOptions {
  /** Enable OpenAPI spec generation (default: false) */
  enabled?: boolean;
  /** Endpoint path for OpenAPI spec (default: /openapi.json) */
  endpoint?: string;
  /** API info for OpenAPI spec */
  info?: Partial<OpenAPIInfo>;
  /** Enable Swagger UI (default: true when openapi is enabled) */
  swaggerUI?: boolean;
  /** Swagger UI endpoint (default: /swagger) */
  swaggerEndpoint?: string;
}

OpenAPIInfo

typescript
interface OpenAPIInfo {
  /** API title */
  title: string;
  /** API version */
  version: string;
  /** API description */
  description?: string;
  /** Contact information */
  contact?: {
    name?: string;
    url?: string;
    email?: string;
  };
  /** License information */
  license?: {
    name: string;
    url?: string;
  };
}

generateOpenAPISpec

Generate an OpenAPI 3.0 specification from decisions.

typescript
import { generateOpenAPISpec } from "@criterionx/server";

const spec = generateOpenAPISpec(decisions, {
  title: "My API",
  version: "1.0.0",
});

console.log(JSON.stringify(spec, null, 2));

generateSwaggerUIHtml

Generate HTML for Swagger UI.

typescript
import { generateSwaggerUIHtml } from "@criterionx/server";

const html = generateSwaggerUIHtml("/openapi.json");

Released under the MIT License.