@reactive-agents/health 0.7.6
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 +70 -0
- package/dist/index.js +105 -0
- package/dist/index.js.map +1 -0
- package/package.json +44 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { Context, Effect } from 'effect';
|
|
2
|
+
import * as effect_Cause from 'effect/Cause';
|
|
3
|
+
import * as effect_Types from 'effect/Types';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Health check result from a registered probe.
|
|
7
|
+
*/
|
|
8
|
+
interface HealthCheckResult {
|
|
9
|
+
readonly name: string;
|
|
10
|
+
readonly healthy: boolean;
|
|
11
|
+
readonly message?: string;
|
|
12
|
+
readonly durationMs: number;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Full health response returned from /health and /ready endpoints.
|
|
16
|
+
*/
|
|
17
|
+
interface HealthResponse {
|
|
18
|
+
readonly status: "healthy" | "degraded" | "unhealthy";
|
|
19
|
+
readonly uptime: number;
|
|
20
|
+
readonly agent?: string;
|
|
21
|
+
readonly checks: readonly HealthCheckResult[];
|
|
22
|
+
readonly timestamp: string;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Configuration for the health server.
|
|
26
|
+
*/
|
|
27
|
+
interface HealthConfig {
|
|
28
|
+
/** Port to bind the health HTTP server. Default: 3000 */
|
|
29
|
+
readonly port: number;
|
|
30
|
+
/** Agent name to include in health responses. */
|
|
31
|
+
readonly agentName?: string;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Health service interface — manages an HTTP health server and registered probes.
|
|
35
|
+
*/
|
|
36
|
+
interface HealthService {
|
|
37
|
+
/** Start the HTTP health server on the configured port. */
|
|
38
|
+
readonly start: () => Effect.Effect<void, never>;
|
|
39
|
+
/** Stop the HTTP health server gracefully. */
|
|
40
|
+
readonly stop: () => Effect.Effect<void, never>;
|
|
41
|
+
/** Register a named health check probe. */
|
|
42
|
+
readonly registerCheck: (name: string, check: () => Effect.Effect<boolean, never>) => Effect.Effect<void, never>;
|
|
43
|
+
/** Run all registered checks and return the aggregate result. */
|
|
44
|
+
readonly check: () => Effect.Effect<HealthResponse, never>;
|
|
45
|
+
}
|
|
46
|
+
declare const Health_base: Context.TagClass<Health, "HealthService", HealthService>;
|
|
47
|
+
/**
|
|
48
|
+
* Effect-TS Context Tag for HealthService.
|
|
49
|
+
*/
|
|
50
|
+
declare class Health extends Health_base {
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
declare const HealthServerError_base: new <A extends Record<string, any> = {}>(args: effect_Types.Equals<A, {}> extends true ? void : { readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }) => effect_Cause.YieldableError & {
|
|
54
|
+
readonly _tag: "HealthServerError";
|
|
55
|
+
} & Readonly<A>;
|
|
56
|
+
declare class HealthServerError extends HealthServerError_base<{
|
|
57
|
+
readonly message: string;
|
|
58
|
+
readonly cause?: unknown;
|
|
59
|
+
}> {
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Create a HealthService that runs a Bun.serve HTTP server
|
|
64
|
+
* with /health, /ready, and /metrics endpoints.
|
|
65
|
+
*/
|
|
66
|
+
declare const makeHealthService: (config: HealthConfig) => Effect.Effect<HealthService & {
|
|
67
|
+
readonly _port: number;
|
|
68
|
+
}>;
|
|
69
|
+
|
|
70
|
+
export { Health, type HealthCheckResult, type HealthConfig, type HealthResponse, HealthServerError, type HealthService, makeHealthService };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
// src/types.ts
|
|
2
|
+
import { Context } from "effect";
|
|
3
|
+
var Health = class extends Context.Tag("HealthService")() {
|
|
4
|
+
};
|
|
5
|
+
|
|
6
|
+
// src/errors.ts
|
|
7
|
+
import { Data } from "effect";
|
|
8
|
+
var HealthServerError = class extends Data.TaggedError("HealthServerError") {
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
// src/service.ts
|
|
12
|
+
import { Effect as Effect2, Ref } from "effect";
|
|
13
|
+
var makeHealthService = (config) => Effect2.gen(function* () {
|
|
14
|
+
const checksRef = yield* Ref.make([]);
|
|
15
|
+
const startedAt = Date.now();
|
|
16
|
+
let server = null;
|
|
17
|
+
let boundPort = config.port;
|
|
18
|
+
const runChecks = () => Effect2.gen(function* () {
|
|
19
|
+
const checks = yield* Ref.get(checksRef);
|
|
20
|
+
const results = [];
|
|
21
|
+
for (const c of checks) {
|
|
22
|
+
const start = Date.now();
|
|
23
|
+
const healthy = yield* c.check();
|
|
24
|
+
results.push({
|
|
25
|
+
name: c.name,
|
|
26
|
+
healthy,
|
|
27
|
+
durationMs: Date.now() - start
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
return results;
|
|
31
|
+
});
|
|
32
|
+
const buildResponse = (checks) => {
|
|
33
|
+
const allHealthy = checks.every((c) => c.healthy);
|
|
34
|
+
const anyHealthy = checks.some((c) => c.healthy);
|
|
35
|
+
return {
|
|
36
|
+
status: checks.length === 0 || allHealthy ? "healthy" : anyHealthy ? "degraded" : "unhealthy",
|
|
37
|
+
uptime: Math.floor((Date.now() - startedAt) / 1e3),
|
|
38
|
+
agent: config.agentName,
|
|
39
|
+
checks,
|
|
40
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
41
|
+
};
|
|
42
|
+
};
|
|
43
|
+
const metricsText = () => {
|
|
44
|
+
const uptime = ((Date.now() - startedAt) / 1e3).toFixed(1);
|
|
45
|
+
return [
|
|
46
|
+
`# HELP raxd_uptime_seconds Agent uptime in seconds`,
|
|
47
|
+
`# TYPE raxd_uptime_seconds gauge`,
|
|
48
|
+
`raxd_uptime_seconds ${uptime}`,
|
|
49
|
+
`# HELP raxd_health_status Health status (1=healthy, 0=unhealthy)`,
|
|
50
|
+
`# TYPE raxd_health_status gauge`,
|
|
51
|
+
`raxd_health_status 1`
|
|
52
|
+
].join("\n") + "\n";
|
|
53
|
+
};
|
|
54
|
+
const handleRequest = async (req) => {
|
|
55
|
+
const url = new URL(req.url);
|
|
56
|
+
if (url.pathname === "/health") {
|
|
57
|
+
const checks = await Effect2.runPromise(runChecks());
|
|
58
|
+
const body = buildResponse(checks);
|
|
59
|
+
return Response.json(body, { status: 200 });
|
|
60
|
+
}
|
|
61
|
+
if (url.pathname === "/ready") {
|
|
62
|
+
const checks = await Effect2.runPromise(runChecks());
|
|
63
|
+
const body = buildResponse(checks);
|
|
64
|
+
const status = body.status === "unhealthy" ? 503 : 200;
|
|
65
|
+
return Response.json(body, { status });
|
|
66
|
+
}
|
|
67
|
+
if (url.pathname === "/metrics") {
|
|
68
|
+
return new Response(metricsText(), {
|
|
69
|
+
status: 200,
|
|
70
|
+
headers: { "content-type": "text/plain; charset=utf-8" }
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
return Response.json({ error: "Not found" }, { status: 404 });
|
|
74
|
+
};
|
|
75
|
+
const service = {
|
|
76
|
+
get _port() {
|
|
77
|
+
return boundPort;
|
|
78
|
+
},
|
|
79
|
+
start: () => Effect2.sync(() => {
|
|
80
|
+
server = Bun.serve({
|
|
81
|
+
port: config.port,
|
|
82
|
+
fetch: handleRequest
|
|
83
|
+
});
|
|
84
|
+
boundPort = server.port ?? config.port;
|
|
85
|
+
}),
|
|
86
|
+
stop: () => Effect2.sync(() => {
|
|
87
|
+
if (server) {
|
|
88
|
+
server.stop(true);
|
|
89
|
+
server = null;
|
|
90
|
+
}
|
|
91
|
+
}),
|
|
92
|
+
registerCheck: (name, check) => Ref.update(checksRef, (checks) => [...checks, { name, check }]),
|
|
93
|
+
check: () => Effect2.gen(function* () {
|
|
94
|
+
const checks = yield* runChecks();
|
|
95
|
+
return buildResponse(checks);
|
|
96
|
+
})
|
|
97
|
+
};
|
|
98
|
+
return service;
|
|
99
|
+
});
|
|
100
|
+
export {
|
|
101
|
+
Health,
|
|
102
|
+
HealthServerError,
|
|
103
|
+
makeHealthService
|
|
104
|
+
};
|
|
105
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/types.ts","../src/errors.ts","../src/service.ts"],"sourcesContent":["// packages/health/src/types.ts\nimport { Context, Effect } from \"effect\";\n\n/**\n * Health check result from a registered probe.\n */\nexport interface HealthCheckResult {\n readonly name: string;\n readonly healthy: boolean;\n readonly message?: string;\n readonly durationMs: number;\n}\n\n/**\n * Full health response returned from /health and /ready endpoints.\n */\nexport interface HealthResponse {\n readonly status: \"healthy\" | \"degraded\" | \"unhealthy\";\n readonly uptime: number;\n readonly agent?: string;\n readonly checks: readonly HealthCheckResult[];\n readonly timestamp: string;\n}\n\n/**\n * Configuration for the health server.\n */\nexport interface HealthConfig {\n /** Port to bind the health HTTP server. Default: 3000 */\n readonly port: number;\n /** Agent name to include in health responses. */\n readonly agentName?: string;\n}\n\n/**\n * Health service interface — manages an HTTP health server and registered probes.\n */\nexport interface HealthService {\n /** Start the HTTP health server on the configured port. */\n readonly start: () => Effect.Effect<void, never>;\n /** Stop the HTTP health server gracefully. */\n readonly stop: () => Effect.Effect<void, never>;\n /** Register a named health check probe. */\n readonly registerCheck: (\n name: string,\n check: () => Effect.Effect<boolean, never>,\n ) => Effect.Effect<void, never>;\n /** Run all registered checks and return the aggregate result. */\n readonly check: () => Effect.Effect<HealthResponse, never>;\n}\n\n/**\n * Effect-TS Context Tag for HealthService.\n */\nexport class Health extends Context.Tag(\"HealthService\")<\n Health,\n HealthService\n>() {}\n","// packages/health/src/errors.ts\nimport { Data } from \"effect\";\n\nexport class HealthServerError extends Data.TaggedError(\"HealthServerError\")<{\n readonly message: string;\n readonly cause?: unknown;\n}> {}\n","// packages/health/src/service.ts\nimport { Effect, Ref } from \"effect\";\nimport type {\n HealthConfig,\n HealthService,\n HealthResponse,\n HealthCheckResult,\n} from \"./types.js\";\n\ntype HealthCheck = {\n readonly name: string;\n readonly check: () => Effect.Effect<boolean, never>;\n};\n\n/**\n * Create a HealthService that runs a Bun.serve HTTP server\n * with /health, /ready, and /metrics endpoints.\n */\nexport const makeHealthService = (\n config: HealthConfig,\n): Effect.Effect<HealthService & { readonly _port: number }> =>\n Effect.gen(function* () {\n const checksRef = yield* Ref.make<HealthCheck[]>([]);\n const startedAt = Date.now();\n let server: ReturnType<typeof Bun.serve> | null = null;\n let boundPort = config.port;\n\n const runChecks = (): Effect.Effect<HealthCheckResult[], never> =>\n Effect.gen(function* () {\n const checks = yield* Ref.get(checksRef);\n const results: HealthCheckResult[] = [];\n for (const c of checks) {\n const start = Date.now();\n const healthy = yield* c.check();\n results.push({\n name: c.name,\n healthy,\n durationMs: Date.now() - start,\n });\n }\n return results;\n });\n\n const buildResponse = (checks: HealthCheckResult[]): HealthResponse => {\n const allHealthy = checks.every((c) => c.healthy);\n const anyHealthy = checks.some((c) => c.healthy);\n return {\n status:\n checks.length === 0 || allHealthy\n ? \"healthy\"\n : anyHealthy\n ? \"degraded\"\n : \"unhealthy\",\n uptime: Math.floor((Date.now() - startedAt) / 1000),\n agent: config.agentName,\n checks,\n timestamp: new Date().toISOString(),\n };\n };\n\n const metricsText = (): string => {\n const uptime = ((Date.now() - startedAt) / 1000).toFixed(1);\n return (\n [\n `# HELP raxd_uptime_seconds Agent uptime in seconds`,\n `# TYPE raxd_uptime_seconds gauge`,\n `raxd_uptime_seconds ${uptime}`,\n `# HELP raxd_health_status Health status (1=healthy, 0=unhealthy)`,\n `# TYPE raxd_health_status gauge`,\n `raxd_health_status 1`,\n ].join(\"\\n\") + \"\\n\"\n );\n };\n\n const handleRequest = async (req: Request): Promise<Response> => {\n const url = new URL(req.url);\n\n if (url.pathname === \"/health\") {\n const checks = await Effect.runPromise(runChecks());\n const body = buildResponse(checks);\n return Response.json(body, { status: 200 });\n }\n\n if (url.pathname === \"/ready\") {\n const checks = await Effect.runPromise(runChecks());\n const body = buildResponse(checks);\n const status = body.status === \"unhealthy\" ? 503 : 200;\n return Response.json(body, { status });\n }\n\n if (url.pathname === \"/metrics\") {\n return new Response(metricsText(), {\n status: 200,\n headers: { \"content-type\": \"text/plain; charset=utf-8\" },\n });\n }\n\n return Response.json({ error: \"Not found\" }, { status: 404 });\n };\n\n const service: HealthService & { readonly _port: number } = {\n get _port() {\n return boundPort;\n },\n\n start: () =>\n Effect.sync(() => {\n server = Bun.serve({\n port: config.port,\n fetch: handleRequest,\n });\n boundPort = server.port ?? config.port;\n }),\n\n stop: () =>\n Effect.sync(() => {\n if (server) {\n server.stop(true);\n server = null;\n }\n }),\n\n registerCheck: (name, check) =>\n Ref.update(checksRef, (checks) => [...checks, { name, check }]),\n\n check: () =>\n Effect.gen(function* () {\n const checks = yield* runChecks();\n return buildResponse(checks);\n }),\n };\n\n return service;\n });\n"],"mappings":";AACA,SAAS,eAAuB;AAqDzB,IAAM,SAAN,cAAqB,QAAQ,IAAI,eAAe,EAGrD,EAAE;AAAC;;;ACxDL,SAAS,YAAY;AAEd,IAAM,oBAAN,cAAgC,KAAK,YAAY,mBAAmB,EAGxE;AAAC;;;ACLJ,SAAS,UAAAA,SAAQ,WAAW;AAiBrB,IAAM,oBAAoB,CAC/B,WAEAA,QAAO,IAAI,aAAa;AACtB,QAAM,YAAY,OAAO,IAAI,KAAoB,CAAC,CAAC;AACnD,QAAM,YAAY,KAAK,IAAI;AAC3B,MAAI,SAA8C;AAClD,MAAI,YAAY,OAAO;AAEvB,QAAM,YAAY,MAChBA,QAAO,IAAI,aAAa;AACtB,UAAM,SAAS,OAAO,IAAI,IAAI,SAAS;AACvC,UAAM,UAA+B,CAAC;AACtC,eAAW,KAAK,QAAQ;AACtB,YAAM,QAAQ,KAAK,IAAI;AACvB,YAAM,UAAU,OAAO,EAAE,MAAM;AAC/B,cAAQ,KAAK;AAAA,QACX,MAAM,EAAE;AAAA,QACR;AAAA,QACA,YAAY,KAAK,IAAI,IAAI;AAAA,MAC3B,CAAC;AAAA,IACH;AACA,WAAO;AAAA,EACT,CAAC;AAEH,QAAM,gBAAgB,CAAC,WAAgD;AACrE,UAAM,aAAa,OAAO,MAAM,CAAC,MAAM,EAAE,OAAO;AAChD,UAAM,aAAa,OAAO,KAAK,CAAC,MAAM,EAAE,OAAO;AAC/C,WAAO;AAAA,MACL,QACE,OAAO,WAAW,KAAK,aACnB,YACA,aACE,aACA;AAAA,MACR,QAAQ,KAAK,OAAO,KAAK,IAAI,IAAI,aAAa,GAAI;AAAA,MAClD,OAAO,OAAO;AAAA,MACd;AAAA,MACA,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,IACpC;AAAA,EACF;AAEA,QAAM,cAAc,MAAc;AAChC,UAAM,WAAW,KAAK,IAAI,IAAI,aAAa,KAAM,QAAQ,CAAC;AAC1D,WACE;AAAA,MACE;AAAA,MACA;AAAA,MACA,uBAAuB,MAAM;AAAA,MAC7B;AAAA,MACA;AAAA,MACA;AAAA,IACF,EAAE,KAAK,IAAI,IAAI;AAAA,EAEnB;AAEA,QAAM,gBAAgB,OAAO,QAAoC;AAC/D,UAAM,MAAM,IAAI,IAAI,IAAI,GAAG;AAE3B,QAAI,IAAI,aAAa,WAAW;AAC9B,YAAM,SAAS,MAAMA,QAAO,WAAW,UAAU,CAAC;AAClD,YAAM,OAAO,cAAc,MAAM;AACjC,aAAO,SAAS,KAAK,MAAM,EAAE,QAAQ,IAAI,CAAC;AAAA,IAC5C;AAEA,QAAI,IAAI,aAAa,UAAU;AAC7B,YAAM,SAAS,MAAMA,QAAO,WAAW,UAAU,CAAC;AAClD,YAAM,OAAO,cAAc,MAAM;AACjC,YAAM,SAAS,KAAK,WAAW,cAAc,MAAM;AACnD,aAAO,SAAS,KAAK,MAAM,EAAE,OAAO,CAAC;AAAA,IACvC;AAEA,QAAI,IAAI,aAAa,YAAY;AAC/B,aAAO,IAAI,SAAS,YAAY,GAAG;AAAA,QACjC,QAAQ;AAAA,QACR,SAAS,EAAE,gBAAgB,4BAA4B;AAAA,MACzD,CAAC;AAAA,IACH;AAEA,WAAO,SAAS,KAAK,EAAE,OAAO,YAAY,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EAC9D;AAEA,QAAM,UAAsD;AAAA,IAC1D,IAAI,QAAQ;AACV,aAAO;AAAA,IACT;AAAA,IAEA,OAAO,MACLA,QAAO,KAAK,MAAM;AAChB,eAAS,IAAI,MAAM;AAAA,QACjB,MAAM,OAAO;AAAA,QACb,OAAO;AAAA,MACT,CAAC;AACD,kBAAY,OAAO,QAAQ,OAAO;AAAA,IACpC,CAAC;AAAA,IAEH,MAAM,MACJA,QAAO,KAAK,MAAM;AAChB,UAAI,QAAQ;AACV,eAAO,KAAK,IAAI;AAChB,iBAAS;AAAA,MACX;AAAA,IACF,CAAC;AAAA,IAEH,eAAe,CAAC,MAAM,UACpB,IAAI,OAAO,WAAW,CAAC,WAAW,CAAC,GAAG,QAAQ,EAAE,MAAM,MAAM,CAAC,CAAC;AAAA,IAEhE,OAAO,MACLA,QAAO,IAAI,aAAa;AACtB,YAAM,SAAS,OAAO,UAAU;AAChC,aAAO,cAAc,MAAM;AAAA,IAC7B,CAAC;AAAA,EACL;AAEA,SAAO;AACT,CAAC;","names":["Effect"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@reactive-agents/health",
|
|
3
|
+
"version": "0.7.6",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"types": "./dist/index.d.ts",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"build": "tsup --config ../../tsup.config.base.ts",
|
|
9
|
+
"typecheck": "tsc --noEmit",
|
|
10
|
+
"test": "bun test"
|
|
11
|
+
},
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"effect": "^3.10.0"
|
|
14
|
+
},
|
|
15
|
+
"devDependencies": {
|
|
16
|
+
"typescript": "^5.7.0",
|
|
17
|
+
"bun-types": "latest"
|
|
18
|
+
},
|
|
19
|
+
"exports": {
|
|
20
|
+
".": {
|
|
21
|
+
"types": "./dist/index.d.ts",
|
|
22
|
+
"import": "./dist/index.js",
|
|
23
|
+
"default": "./dist/index.js"
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
"files": [
|
|
27
|
+
"dist",
|
|
28
|
+
"README.md",
|
|
29
|
+
"LICENSE"
|
|
30
|
+
],
|
|
31
|
+
"license": "MIT",
|
|
32
|
+
"repository": {
|
|
33
|
+
"type": "git",
|
|
34
|
+
"url": "https://github.com/tylerjrbuell/reactive-agents-ts.git",
|
|
35
|
+
"directory": "packages/health"
|
|
36
|
+
},
|
|
37
|
+
"homepage": "https://docs.reactiveagents.dev/",
|
|
38
|
+
"bugs": {
|
|
39
|
+
"url": "https://github.com/tylerjrbuell/reactive-agents-ts/issues"
|
|
40
|
+
},
|
|
41
|
+
"publishConfig": {
|
|
42
|
+
"access": "public"
|
|
43
|
+
}
|
|
44
|
+
}
|