@beignet/core
Version:
Core framework primitives for Beignet
568 lines (526 loc) • 13.8 kB
text/typescript
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,
};
}