UNPKG

@beignet/core

Version:

Core framework primitives for Beignet

568 lines (526 loc) 13.8 kB
import { type Redactor, redactValue } from "../ports/redaction.js"; /** * Logical provider watcher name used to toggle groups of instrumentation * events. */ export type ProviderInstrumentationWatcherName = string; /** * Common metadata attached to every provider instrumentation event. */ export interface BaseProviderInstrumentationEvent { /** * Optional event identifier supplied by the caller. */ id?: string; /** * ISO timestamp supplied by the caller. Devtools can also assign a timestamp * when one is omitted. */ timestamp?: string; /** * Request correlation ID for events emitted during request handling. */ requestId?: string; /** * Trace identifier for distributed tracing integrations. */ traceId?: string; /** * Span identifier for the current operation. */ spanId?: string; /** * Parent span identifier for nested operations. */ parentSpanId?: string; /** * W3C traceparent header value, when available. */ traceparent?: string; /** * W3C tracestate value associated with the current operation. */ tracestate?: string; /** * Watcher category used by devtools and instrumentation sinks. */ watcher?: ProviderInstrumentationWatcherName; /** * Provider-specific structured details. Values are redacted before being * recorded by `createProviderInstrumentation(...)`. */ details?: unknown; } /** * HTTP request instrumentation emitted by server adapters. */ export interface RequestProviderInstrumentationEvent extends BaseProviderInstrumentationEvent { type: "request"; /** * HTTP method. */ method: string; /** * Request path. */ path: string; /** * Matched contract name, when known. */ contractName?: string; /** * Which layer produced the response. */ responseOwner?: "route" | "framework" | "transport" | "unknown"; /** * Response status code. */ status?: number; /** * Request duration in milliseconds. */ durationMs?: number; /** * Per-stage timing breakdown of the request pipeline, in milliseconds: * `onRequest` hooks, request parsing, context creation, route hooks plus * `beforeHandle` hooks, the handler, and response preparation. */ stages?: { onRequestMs: number; parseMs: number; contextMs: number; beforeHandleMs: number; handlerMs: number; sendMs: number; }; /** * Human-readable summary for devtools lists. */ summary?: string; } /** * Error instrumentation emitted by framework hooks, use cases, or providers. */ export interface ErrorProviderInstrumentationEvent extends BaseProviderInstrumentationEvent { type: "error"; /** * Error message. */ message: string; /** * Error stack trace, when available. */ stack?: string; /** * Contract associated with the error. */ contractName?: string; /** * Use case associated with the error. */ useCaseName?: string; /** * Which layer owns the error. */ owner?: | "route" | "framework" | "provider" | "job" | "schedule" | "outbox" | "client" | "devtools" | "unknown"; } /** * Use-case lifecycle instrumentation. */ export interface UseCaseProviderInstrumentationEvent extends BaseProviderInstrumentationEvent { type: "usecase"; /** * Use-case name. */ name: string; /** * Optional classification used by devtools. */ kind?: "command" | "query"; /** * Lifecycle phase. */ phase: "start" | "end" | "error"; /** * Duration in milliseconds for completed phases. */ durationMs?: number; /** * Error summary for failed phases. */ error?: string; } /** * Event bus instrumentation. */ export interface EventBusProviderInstrumentationEvent extends BaseProviderInstrumentationEvent { type: "eventBus"; /** * Domain or integration event name. */ eventName: string; } /** * Background job instrumentation. */ export interface JobProviderInstrumentationEvent extends BaseProviderInstrumentationEvent { type: "job"; /** * Job name. */ jobName: string; /** * Job lifecycle status. */ status: | "scheduled" | "started" | "completed" | "failed" | "retryScheduled" | "deadLettered"; } /** * Durable outbox delivery instrumentation. */ export interface OutboxProviderInstrumentationEvent extends BaseProviderInstrumentationEvent { type: "outbox"; /** * Durable outbox message ID. */ messageId: string; /** * Message kind. */ messageKind: "event" | "job"; /** * Event or job name stored in the outbox message. */ messageName: string; /** * Delivery lifecycle status. */ status: "delivered" | "retryScheduled" | "deadLettered"; } /** * Schedule instrumentation. */ export interface ScheduleProviderInstrumentationEvent extends BaseProviderInstrumentationEvent { type: "schedule"; /** * Schedule name. */ scheduleName: string; /** * Schedule run lifecycle status. */ status: "started" | "completed" | "failed"; /** * Cron expression for the schedule, when known. */ cron?: string; /** * Time zone used by the schedule, when known. */ timezone?: string; } /** * Provider lifecycle instrumentation emitted during setup, start, and stop. */ export interface ProviderLifecycleInstrumentationEvent extends BaseProviderInstrumentationEvent { type: "provider"; /** * Provider name. */ providerName: string; /** * Lifecycle action. */ action: "setup" | "start" | "stop"; } /** * Custom provider instrumentation event for provider-owned operations. */ export interface CustomProviderInstrumentationEvent extends BaseProviderInstrumentationEvent { type: "custom"; /** * Stable event name. */ name: string; /** * Short display label for devtools. */ label?: string; /** * Human-readable summary for devtools lists. */ summary?: string; } /** * Union of instrumentation events that provider sinks can receive. */ export type ProviderInstrumentationEvent = | RequestProviderInstrumentationEvent | ErrorProviderInstrumentationEvent | UseCaseProviderInstrumentationEvent | EventBusProviderInstrumentationEvent | JobProviderInstrumentationEvent | OutboxProviderInstrumentationEvent | ScheduleProviderInstrumentationEvent | ProviderLifecycleInstrumentationEvent | CustomProviderInstrumentationEvent; /** * Input accepted by provider instrumentation recorders. */ export type ProviderInstrumentationEventInput = ProviderInstrumentationEvent; /** * Custom instrumentation input. `type: "custom"` is added by the helper. */ export type ProviderCustomInstrumentationEventInput = Omit< CustomProviderInstrumentationEvent, "type" >; /** * Sink for provider instrumentation events. */ export interface ProviderInstrumentationPort< EventInput extends ProviderInstrumentationEventInput = ProviderInstrumentationEventInput, WatcherName extends string = ProviderInstrumentationWatcherName, RecordResult = unknown, > { /** * Record an instrumentation event. */ record(event: EventInput): RecordResult; /** * Return whether a watcher is enabled. Missing methods are treated as * enabled so simple sinks only need to implement `record`. */ isWatcherEnabled?(name: WatcherName): boolean; } /** * Options for creating a provider instrumentation helper. */ export interface ProviderInstrumentationOptions { /** * Provider name to attach to custom event details. */ providerName: string; /** * Default watcher name for events emitted by the helper. */ watcher?: ProviderInstrumentationWatcherName; /** * Optional redactor applied after Beignet's default redaction pass. */ redact?: Redactor<ProviderInstrumentationEventInput>; } /** * Convenience wrapper used by providers to emit safe, watcher-aware events. */ export interface ProviderInstrumentation { /** * Resolved instrumentation port, when one is available. */ port: ProviderInstrumentationPort | undefined; /** * Return whether the given watcher is enabled. */ isEnabled(watcher?: ProviderInstrumentationWatcherName): boolean; /** * Record a fully formed provider instrumentation event. */ record(event: ProviderInstrumentationEventInput): unknown; /** * Record a provider-owned custom event. */ custom(event: ProviderCustomInstrumentationEventInput): unknown; } /** * Values accepted by `createProviderInstrumentation(...)`. * * Plain ports objects are accepted so callers can pass `ctx.ports` (or a * provider's `ports` argument) directly; the resolver reads * `ports.instrumentation`, then `ports.devtools`. */ export type ProviderInstrumentationTarget = | ProviderInstrumentationPort | ProviderInstrumentation | { devtools?: ProviderInstrumentationPort | ProviderInstrumentation; instrumentation?: ProviderInstrumentationPort | ProviderInstrumentation; } | Record<string, unknown> | undefined; function isObject(value: unknown): value is Record<string, unknown> { return typeof value === "object" && value !== null; } /** * Return whether a value is a provider instrumentation helper. * * Checks key presence with `in` before reading so probing proxy-backed * objects, such as test-context port guards that throw on unknown reads, * stays safe. */ export function isProviderInstrumentation( value: unknown, ): value is ProviderInstrumentation { return ( isObject(value) && "record" in value && "custom" in value && "isEnabled" in value && typeof value.record === "function" && typeof value.custom === "function" && typeof value.isEnabled === "function" ); } /** * Return whether a value is a provider instrumentation port. * * Checks key presence with `in` before reading so probing proxy-backed * objects stays safe. */ export function isProviderInstrumentationPort( value: unknown, ): value is ProviderInstrumentationPort { return ( isObject(value) && "record" in value && typeof value.record === "function" ); } function resolveProviderInstrumentationValue( value: unknown, ): ProviderInstrumentationPort | undefined { if (isProviderInstrumentation(value)) return value.port; if (isProviderInstrumentationPort(value)) return value; return undefined; } /** * Resolve an instrumentation port from a direct port, helper, or context-like * object. */ export function resolveProviderInstrumentationPort( target: ProviderInstrumentationTarget, ): ProviderInstrumentationPort | undefined { if (!target) return undefined; const directPort = resolveProviderInstrumentationValue(target); if (directPort) return directPort; if (!isObject(target)) return undefined; return ( resolveProviderInstrumentationValue( "instrumentation" in target ? target.instrumentation : undefined, ) ?? resolveProviderInstrumentationValue( "devtools" in target ? target.devtools : undefined, ) ); } function withProviderDetails(providerName: string, details: unknown): unknown { if (details === undefined) { return { providerName }; } if (isObject(details) && !Array.isArray(details)) { return { ...details, providerName }; } return { providerName, value: details }; } function withInstrumentationProviderDetails( event: ProviderInstrumentationEventInput, providerName: string, ): ProviderInstrumentationEventInput { if (event.type === "provider") return event; return { ...event, details: withProviderDetails(providerName, event.details), } as ProviderInstrumentationEventInput; } /** * Create a provider instrumentation helper that handles watcher checks, * default watcher assignment, redaction, and sink failures. */ export function createProviderInstrumentation( target: ProviderInstrumentationTarget, options: ProviderInstrumentationOptions, ): ProviderInstrumentation { if (isProviderInstrumentation(target)) { return target; } const port = resolveProviderInstrumentationPort(target); function isEnabled(watcher = options.watcher): boolean { if (!port) return false; if (!watcher) return true; try { return port.isWatcherEnabled?.(watcher) ?? true; } catch { return false; } } function record(event: ProviderInstrumentationEventInput): unknown { if (!port) return undefined; const watcher = event.watcher ?? options.watcher; if (watcher && !isEnabled(watcher)) return undefined; const eventWithWatcher = options.watcher && event.watcher === undefined ? { ...event, watcher: options.watcher } : event; const eventWithProvider = withInstrumentationProviderDetails( eventWithWatcher, options.providerName, ); let redacted: ProviderInstrumentationEventInput; try { redacted = options.redact ? options.redact(redactValue(eventWithProvider)) : redactValue(eventWithProvider); } catch { return undefined; } try { const result = port.record(redacted); if ( result !== null && (typeof result === "object" || typeof result === "function") && "then" in result && typeof result.then === "function" ) { return Promise.resolve(result).catch(() => undefined); } return result; } catch { return undefined; } } function custom(event: ProviderCustomInstrumentationEventInput): unknown { return record({ ...event, type: "custom", watcher: event.watcher ?? options.watcher, details: withProviderDetails(options.providerName, event.details), }); } return { port, isEnabled, record, custom, }; }