Files
WRNexusJS/packages/core/src/platform.ts
T
2026-07-27 12:42:18 +05:30

196 lines
6.0 KiB
TypeScript

import { randomUUID } from "node:crypto";
export interface ProblemDetails {
type: string;
title: string;
status: number;
detail?: string;
instance?: string;
code?: string;
[key: string]: unknown;
}
export interface ProblemDetailsInput {
type?: string;
title: string;
status: number;
detail?: string;
instance?: string;
code?: string;
[key: string]: unknown;
}
export function problem(details: ProblemDetailsInput, headers?: HeadersInit): Response {
const body: ProblemDetails = {
...details,
type: details.type ?? "about:blank",
title: details.title,
status: details.status,
};
return Response.json(body, {
status: body.status,
headers: {
"content-type": "application/problem+json; charset=utf-8",
...Object.fromEntries(new Headers(headers)),
},
});
}
export type ServiceToken<T> = string | symbol | { readonly key: symbol; readonly __type?: T };
export function serviceToken<T>(description: string): ServiceToken<T> {
return { key: Symbol(description) };
}
function tokenKey<T>(token: ServiceToken<T>): string | symbol {
return typeof token === "object" ? token.key : token;
}
export class ServiceContainer {
readonly #values = new Map<string | symbol, unknown>();
constructor(private readonly parent?: ServiceContainer) {}
set<T>(token: ServiceToken<T>, value: T): this {
this.#values.set(tokenKey(token), value);
return this;
}
has<T>(token: ServiceToken<T>): boolean {
return this.#values.has(tokenKey(token)) || !!this.parent?.has(token);
}
get<T>(token: ServiceToken<T>): T {
const key = tokenKey(token);
if (this.#values.has(key)) return this.#values.get(key) as T;
if (this.parent) return this.parent.get(token);
throw new Error(`WRN-SERVICE-NOT-FOUND: ${typeof key === "symbol" ? key.description : key}`);
}
tryGet<T>(token: ServiceToken<T>): T | undefined {
try {
return this.get(token);
} catch {
return undefined;
}
}
scope(): ServiceContainer {
return new ServiceContainer(this);
}
}
export type LifecyclePhase = "starting" | "started" | "stopping" | "stopped";
export type LifecycleHandler = (signal: AbortSignal) => void | Promise<void>;
export class ApplicationLifecycle {
readonly #handlers = new Map<LifecyclePhase, LifecycleHandler[]>();
#controller = new AbortController();
on(phase: LifecyclePhase, handler: LifecycleHandler): () => void {
const handlers = this.#handlers.get(phase) ?? [];
handlers.push(handler);
this.#handlers.set(phase, handlers);
return () => {
const index = handlers.indexOf(handler);
if (index >= 0) handlers.splice(index, 1);
};
}
async run(phase: LifecyclePhase): Promise<void> {
if (phase === "stopping") this.#controller.abort("application stopping");
const handlers = this.#handlers.get(phase) ?? [];
const sequence =
phase === "stopping" || phase === "stopped" ? [...handlers].reverse() : handlers;
for (const handler of sequence) await handler(this.#controller.signal);
}
get signal(): AbortSignal {
return this.#controller.signal;
}
}
export interface HealthCheckResult {
status: "up" | "down" | "degraded";
message?: string;
details?: unknown;
durationMs?: number;
}
export type HealthCheck = () => HealthCheckResult | Promise<HealthCheckResult>;
export class HealthRegistry {
readonly #checks = new Map<string, HealthCheck>();
register(name: string, check: HealthCheck): () => void {
this.#checks.set(name, check);
return () => this.#checks.delete(name);
}
async check(): Promise<{
status: "up" | "down" | "degraded";
checks: Record<string, HealthCheckResult>;
}> {
const checks: Record<string, HealthCheckResult> = {};
for (const [name, check] of this.#checks) {
const start = performance.now();
try {
checks[name] = {
...(await check()),
durationMs: Math.round((performance.now() - start) * 100) / 100,
};
} catch (error) {
checks[name] = {
status: "down",
message: error instanceof Error ? error.message : String(error),
durationMs: Math.round((performance.now() - start) * 100) / 100,
};
}
}
const values = Object.values(checks);
const status = values.some((item) => item.status === "down")
? "down"
: values.some((item) => item.status === "degraded")
? "degraded"
: "up";
return { status, checks };
}
}
export function requestId(headers: Headers, preferred?: string): string {
const existing =
preferred ?? headers.get("x-request-id") ?? headers.get("traceparent")?.split("-")[1];
return existing && /^[A-Za-z0-9._:-]{8,128}$/.test(existing) ? existing : randomUUID();
}
export interface IdempotencyRecord<T = unknown> {
key: string;
value: T;
expiresAt: number;
}
export interface IdempotencyStore<T = unknown> {
get(key: string): Promise<IdempotencyRecord<T> | null>;
set(record: IdempotencyRecord<T>): Promise<void>;
delete(key: string): Promise<void>;
}
export function memoryIdempotencyStore<T = unknown>(
now: () => number = Date.now,
): IdempotencyStore<T> {
const records = new Map<string, IdempotencyRecord<T>>();
return {
async get(key) {
const value = records.get(key);
if (!value) return null;
if (value.expiresAt <= now()) {
records.delete(key);
return null;
}
return value;
},
async set(record) {
records.set(record.key, record);
},
async delete(key) {
records.delete(key);
},
};
}
export async function withIdempotency<T>(
store: IdempotencyStore<T>,
key: string,
execute: () => Promise<T>,
ttlMs = 24 * 60 * 60 * 1000,
): Promise<{ value: T; replayed: boolean }> {
if (!key.trim()) throw new TypeError("idempotency key cannot be empty");
const existing = await store.get(key);
if (existing) return { value: existing.value, replayed: true };
const value = await execute();
await store.set({ key, value, expiresAt: Date.now() + ttlMs });
return { value, replayed: false };
}