@criterionx/server 0.3.1 → 0.3.3
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/dist/index.d.ts +35 -1
- package/dist/index.js +75 -16
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -130,6 +130,39 @@ interface DecisionInfo {
|
|
|
130
130
|
description?: string;
|
|
131
131
|
meta?: Record<string, unknown>;
|
|
132
132
|
}
|
|
133
|
+
/**
|
|
134
|
+
* Error codes for structured error responses
|
|
135
|
+
*/
|
|
136
|
+
type ErrorCode = "DECISION_NOT_FOUND" | "INVALID_JSON" | "MISSING_INPUT" | "MISSING_PROFILE" | "VALIDATION_ERROR" | "EVALUATION_ERROR" | "INTERNAL_ERROR";
|
|
137
|
+
/**
|
|
138
|
+
* Structured error response
|
|
139
|
+
*/
|
|
140
|
+
interface ErrorResponse {
|
|
141
|
+
error: {
|
|
142
|
+
code: ErrorCode;
|
|
143
|
+
message: string;
|
|
144
|
+
details?: Record<string, unknown>;
|
|
145
|
+
};
|
|
146
|
+
requestId?: string;
|
|
147
|
+
timestamp: string;
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Health check status
|
|
151
|
+
*/
|
|
152
|
+
type HealthStatus = "healthy" | "degraded" | "unhealthy";
|
|
153
|
+
/**
|
|
154
|
+
* Health check response
|
|
155
|
+
*/
|
|
156
|
+
interface HealthResponse {
|
|
157
|
+
status: HealthStatus;
|
|
158
|
+
version: string;
|
|
159
|
+
uptime: number;
|
|
160
|
+
timestamp: string;
|
|
161
|
+
checks?: Record<string, {
|
|
162
|
+
status: HealthStatus;
|
|
163
|
+
message?: string;
|
|
164
|
+
}>;
|
|
165
|
+
}
|
|
133
166
|
|
|
134
167
|
/**
|
|
135
168
|
* Generate OpenAPI-compatible schema for a decision endpoint
|
|
@@ -269,6 +302,7 @@ declare class CriterionServer {
|
|
|
269
302
|
private metricsOptions;
|
|
270
303
|
private openApiOptions;
|
|
271
304
|
private openApiSpec;
|
|
305
|
+
private startTime;
|
|
272
306
|
constructor(options: ServerOptions);
|
|
273
307
|
private setupRoutes;
|
|
274
308
|
private generateDocsHtml;
|
|
@@ -290,4 +324,4 @@ declare class CriterionServer {
|
|
|
290
324
|
*/
|
|
291
325
|
declare function createServer(options: ServerOptions): CriterionServer;
|
|
292
326
|
|
|
293
|
-
export { type AfterEvaluateHook, type BeforeEvaluateHook, CriterionServer, type DecisionInfo, type EvaluateRequest, type HookContext, type Hooks, METRIC_EVALUATIONS_TOTAL, METRIC_EVALUATION_DURATION_SECONDS, METRIC_RULE_MATCHES_TOTAL, MetricsCollector, type MetricsOptions$1 as MetricsOptions, type OnErrorHook, type OpenAPIInfo, type OpenAPIOptions, type OpenAPISpec, type ServerOptions, createServer, generateEndpointSchema, generateOpenAPISpec, generateSwaggerUIHtml };
|
|
327
|
+
export { type AfterEvaluateHook, type BeforeEvaluateHook, CriterionServer, type DecisionInfo, type ErrorCode, type ErrorResponse, type EvaluateRequest, type HealthResponse, type HealthStatus, type HookContext, type Hooks, METRIC_EVALUATIONS_TOTAL, METRIC_EVALUATION_DURATION_SECONDS, METRIC_RULE_MATCHES_TOTAL, MetricsCollector, type MetricsOptions$1 as MetricsOptions, type OnErrorHook, type OpenAPIInfo, type OpenAPIOptions, type OpenAPISpec, type ServerOptions, createServer, generateEndpointSchema, generateOpenAPISpec, generateSwaggerUIHtml };
|
package/dist/index.js
CHANGED
|
@@ -457,9 +457,21 @@ import { Hono } from "hono";
|
|
|
457
457
|
import { cors } from "hono/cors";
|
|
458
458
|
import { serve } from "@hono/node-server";
|
|
459
459
|
import { Engine } from "@criterionx/core";
|
|
460
|
+
var SERVER_VERSION = "0.3.2";
|
|
460
461
|
function generateRequestId() {
|
|
461
462
|
return `req_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 9)}`;
|
|
462
463
|
}
|
|
464
|
+
function createErrorResponse(code, message, requestId, details) {
|
|
465
|
+
return {
|
|
466
|
+
error: {
|
|
467
|
+
code,
|
|
468
|
+
message,
|
|
469
|
+
...details && { details }
|
|
470
|
+
},
|
|
471
|
+
...requestId && { requestId },
|
|
472
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
473
|
+
};
|
|
474
|
+
}
|
|
463
475
|
var CriterionServer = class {
|
|
464
476
|
app;
|
|
465
477
|
engine;
|
|
@@ -471,6 +483,7 @@ var CriterionServer = class {
|
|
|
471
483
|
metricsOptions;
|
|
472
484
|
openApiOptions;
|
|
473
485
|
openApiSpec = null;
|
|
486
|
+
startTime;
|
|
474
487
|
constructor(options) {
|
|
475
488
|
this.app = new Hono();
|
|
476
489
|
this.engine = new Engine();
|
|
@@ -479,6 +492,7 @@ var CriterionServer = class {
|
|
|
479
492
|
this.hooks = options.hooks ?? {};
|
|
480
493
|
this.metricsOptions = options.metrics ?? {};
|
|
481
494
|
this.openApiOptions = options.openapi ?? {};
|
|
495
|
+
this.startTime = /* @__PURE__ */ new Date();
|
|
482
496
|
if (this.metricsOptions.enabled) {
|
|
483
497
|
this.metricsCollector = new MetricsCollector(this.metricsOptions);
|
|
484
498
|
}
|
|
@@ -505,11 +519,32 @@ var CriterionServer = class {
|
|
|
505
519
|
this.app.get("/", (c) => {
|
|
506
520
|
return c.json({
|
|
507
521
|
name: "Criterion Server",
|
|
508
|
-
version:
|
|
522
|
+
version: SERVER_VERSION,
|
|
509
523
|
decisions: this.decisions.size,
|
|
510
|
-
|
|
524
|
+
docs: "/docs",
|
|
525
|
+
health: "/health"
|
|
511
526
|
});
|
|
512
527
|
});
|
|
528
|
+
this.app.get("/health", (c) => {
|
|
529
|
+
const uptime = Math.floor((Date.now() - this.startTime.getTime()) / 1e3);
|
|
530
|
+
const response = {
|
|
531
|
+
status: "healthy",
|
|
532
|
+
version: SERVER_VERSION,
|
|
533
|
+
uptime,
|
|
534
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
535
|
+
checks: {
|
|
536
|
+
decisions: {
|
|
537
|
+
status: this.decisions.size > 0 ? "healthy" : "degraded",
|
|
538
|
+
message: `${this.decisions.size} decision(s) registered`
|
|
539
|
+
},
|
|
540
|
+
engine: {
|
|
541
|
+
status: "healthy",
|
|
542
|
+
message: "Engine operational"
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
};
|
|
546
|
+
return c.json(response);
|
|
547
|
+
});
|
|
513
548
|
if (this.metricsCollector) {
|
|
514
549
|
const endpoint = this.metricsOptions.endpoint ?? "/metrics";
|
|
515
550
|
this.app.get(endpoint, (c) => {
|
|
@@ -546,7 +581,10 @@ var CriterionServer = class {
|
|
|
546
581
|
const id = c.req.param("id");
|
|
547
582
|
const decision = this.decisions.get(id);
|
|
548
583
|
if (!decision) {
|
|
549
|
-
return c.json(
|
|
584
|
+
return c.json(
|
|
585
|
+
createErrorResponse("DECISION_NOT_FOUND", `Decision not found: ${id}`),
|
|
586
|
+
404
|
|
587
|
+
);
|
|
550
588
|
}
|
|
551
589
|
const schema = extractDecisionSchema(decision);
|
|
552
590
|
return c.json(schema);
|
|
@@ -555,34 +593,49 @@ var CriterionServer = class {
|
|
|
555
593
|
const id = c.req.param("id");
|
|
556
594
|
const decision = this.decisions.get(id);
|
|
557
595
|
if (!decision) {
|
|
558
|
-
return c.json(
|
|
596
|
+
return c.json(
|
|
597
|
+
createErrorResponse("DECISION_NOT_FOUND", `Decision not found: ${id}`),
|
|
598
|
+
404
|
|
599
|
+
);
|
|
559
600
|
}
|
|
560
601
|
const schema = generateEndpointSchema(decision);
|
|
561
602
|
return c.json(schema);
|
|
562
603
|
});
|
|
563
604
|
this.app.post("/decisions/:id", async (c) => {
|
|
564
605
|
const id = c.req.param("id");
|
|
606
|
+
const requestId = generateRequestId();
|
|
565
607
|
const decision = this.decisions.get(id);
|
|
566
608
|
if (!decision) {
|
|
567
|
-
return c.json(
|
|
609
|
+
return c.json(
|
|
610
|
+
createErrorResponse("DECISION_NOT_FOUND", `Decision not found: ${id}`, requestId),
|
|
611
|
+
404
|
|
612
|
+
);
|
|
568
613
|
}
|
|
569
614
|
let body;
|
|
570
615
|
try {
|
|
571
616
|
body = await c.req.json();
|
|
572
617
|
} catch {
|
|
573
|
-
return c.json(
|
|
618
|
+
return c.json(
|
|
619
|
+
createErrorResponse("INVALID_JSON", "Invalid JSON body", requestId),
|
|
620
|
+
400
|
|
621
|
+
);
|
|
574
622
|
}
|
|
575
623
|
if (body.input === void 0) {
|
|
576
|
-
return c.json(
|
|
624
|
+
return c.json(
|
|
625
|
+
createErrorResponse("MISSING_INPUT", "Missing 'input' in request body", requestId),
|
|
626
|
+
400
|
|
627
|
+
);
|
|
577
628
|
}
|
|
578
629
|
let profile = body.profile;
|
|
579
630
|
if (!profile) {
|
|
580
631
|
profile = this.profiles.get(id);
|
|
581
632
|
if (!profile) {
|
|
582
633
|
return c.json(
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
634
|
+
createErrorResponse(
|
|
635
|
+
"MISSING_PROFILE",
|
|
636
|
+
`No profile provided and no default profile for decision: ${id}`,
|
|
637
|
+
requestId
|
|
638
|
+
),
|
|
586
639
|
400
|
|
587
640
|
);
|
|
588
641
|
}
|
|
@@ -591,7 +644,7 @@ var CriterionServer = class {
|
|
|
591
644
|
decisionId: id,
|
|
592
645
|
input: body.input,
|
|
593
646
|
profile,
|
|
594
|
-
requestId
|
|
647
|
+
requestId,
|
|
595
648
|
timestamp: /* @__PURE__ */ new Date()
|
|
596
649
|
};
|
|
597
650
|
const startTime = performance.now();
|
|
@@ -635,13 +688,19 @@ var CriterionServer = class {
|
|
|
635
688
|
status: "ERROR"
|
|
636
689
|
});
|
|
637
690
|
}
|
|
691
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
638
692
|
if (this.hooks.onError) {
|
|
639
|
-
await this.hooks.onError(
|
|
640
|
-
ctx,
|
|
641
|
-
error instanceof Error ? error : new Error(String(error))
|
|
642
|
-
);
|
|
693
|
+
await this.hooks.onError(ctx, err);
|
|
643
694
|
}
|
|
644
|
-
|
|
695
|
+
return c.json(
|
|
696
|
+
createErrorResponse(
|
|
697
|
+
"EVALUATION_ERROR",
|
|
698
|
+
err.message,
|
|
699
|
+
requestId,
|
|
700
|
+
{ decisionId: id }
|
|
701
|
+
),
|
|
702
|
+
500
|
|
703
|
+
);
|
|
645
704
|
}
|
|
646
705
|
});
|
|
647
706
|
this.app.get("/docs", (c) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@criterionx/server",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.3",
|
|
4
4
|
"description": "HTTP server for Criterion decisions with auto-generated docs",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
"@hono/node-server": "^1.13.0",
|
|
43
43
|
"hono": "^4.6.0",
|
|
44
44
|
"zod-to-json-schema": "^3.24.0",
|
|
45
|
-
"@criterionx/core": "0.3.
|
|
45
|
+
"@criterionx/core": "0.3.3"
|
|
46
46
|
},
|
|
47
47
|
"peerDependencies": {
|
|
48
48
|
"zod": "^3.22.0"
|