/** * Logger interface used throughout Wooks. Passed via `EventContextOptions` * and accessed with `useLogger()`. Compatible with `@prostojs/logger` and * most standard loggers. */ interface Logger { info(msg: string, ...args: unknown[]): void; warn(msg: string, ...args: unknown[]): void; error(msg: string, ...args: unknown[]): void; debug(msg: string, ...args: unknown[]): void; /** Creates a child logger scoped to a topic. Matches `ProstoLogger.createTopic()`. */ createTopic?: (name: string) => Logger; } /** * A typed, writable context slot. Created with `key(name)`. * Use `ctx.set(k, value)` to store and `ctx.get(k)` to retrieve. */ interface Key { readonly _id: number; readonly _name: string; /** @internal Type brand — not used at runtime. */ readonly _T?: T; } /** * A lazily-computed, read-only context slot. Created with `cached(fn)`. * The factory runs once per context on first `ctx.get()` call; the result is cached. */ interface Cached { readonly _id: number; readonly _name: string; readonly _fn: (ctx: EventContext) => T; /** @internal Type brand — not used at runtime. */ readonly _T?: T; } /** Union type for any context slot — either a writable `Key` or a computed `Cached`. */ type Accessor = Key | Cached; /** * Type-level marker used in `defineEventKind` schemas. Each `slot()` * becomes a typed `Key` on the resulting `EventKind`. No runtime behavior. */ interface SlotMarker { /** @internal Type brand — not used at runtime. */ readonly _T?: T; } /** * Declares the shape of an event type — its name and typed seed slots. * Created with `defineEventKind(name, schema)`. * * Use `kind.keys.` to access typed context keys for each slot. */ interface EventKind>> { /** Unique event kind name (e.g. `'http'`, `'cli'`). */ readonly name: string; /** Typed context keys for each slot in the schema. */ readonly keys: { [K in keyof S]: S[K] extends SlotMarker ? Key : never; }; /** @internal Pre-computed entries for fast `seed()`. */ readonly _entries: Array<[string, Key]>; } /** * Extracts the seed values type for an `EventKind`. This is the object * shape passed to `ctx.seed(kind, seeds)` or `createEventContext(opts, kind, seeds, fn)`. */ type EventKindSeeds = K extends EventKind ? { [P in keyof S]: S[P] extends SlotMarker ? V : never; } : never; /** Options for creating an {@link EventContext}. */ interface EventContextOptions { /** Logger instance available to all composables via `useLogger()`. */ logger: Logger; /** Optional parent context. Enables transparent read-through and scoped writes. */ parent?: EventContext; } /** * Per-event container for typed slots, propagated via `AsyncLocalStorage`. * Composables read and write data through `get`/`set` using typed `Key` or `Cached` accessors. * * Supports a parent chain: when a slot is not found locally, `get()` traverses * parent contexts. `set()` writes to the nearest context that already holds the slot, * or locally if the slot is new. * * Typically created by adapters (HTTP, CLI, etc.) — application code * interacts with it indirectly through composables. * * @example * ```ts * const ctx = new EventContext({ logger }) * ctx.set(userIdKey, '123') * ctx.get(userIdKey) // '123' * ``` */ declare class EventContext { /** Logger instance for this event. */ readonly logger: Logger; /** Parent context for read-through and scoped writes. */ readonly parent?: EventContext; private slots; constructor(options: EventContextOptions); /** * Controls whether `get()`, `has()`, and `set()` traverse the parent chain * for a given slot. Override in subclasses to isolate specific slots — * returning `false` forces local computation/storage, preventing inheritance. * * @param _id - The numeric slot identifier (`accessor._id`) * @returns `true` to allow parent traversal (default), `false` to block it */ protected _shouldTraverseParent(_id: number): boolean; /** * Reads a value from a typed slot. * - For `Key`: returns the previously `set` value, checking parent chain if not found locally. * - For `Cached`: returns a cached result from this context or any parent. If not found * anywhere, runs the factory on first access, caches locally, and returns the result. * Throws on circular dependencies. Errors are cached and re-thrown on subsequent access. */ get(accessor: Key | Cached): T; /** * Writes a value to a typed slot. If the slot already exists somewhere in the * parent chain, the value is written there. Otherwise, it is written locally. */ set(key: Key | Cached, value: T): void; /** * Returns `true` if the slot has been set or computed in this context or any parent. */ has(accessor: Accessor): boolean; /** * Reads a value from a typed slot in this context only, ignoring parents. * Same semantics as `get()` but without parent chain traversal. */ getOwn(accessor: Key | Cached): T; /** * Writes a value to a typed slot in this context only, ignoring parents. */ setOwn(key: Key | Cached, value: T): void; /** * Returns `true` if the slot has been set or computed in this context only. */ hasOwn(accessor: Accessor): boolean; /** * Seeds an event kind's slots into this context. Sets the event type key * and populates all slots declared in the kind's schema. * * @param kind - The event kind (from `defineEventKind`) * @param seeds - Values for each slot in the kind's schema * @param fn - Optional callback to run after seeding (returned value is forwarded) */ seed>(kind: EventKind, seeds: EventKindSeeds>): void; seed, R>(kind: EventKind, seeds: EventKindSeeds>, fn: () => R): R; /** Walk the parent chain looking for a set slot. Returns `undefined` if not found. */ private _findSlot; /** Set value in the first context in the chain that has this slot. Returns true if found. */ private _setIfExists; } /** * Creates a typed, writable context slot. Use `ctx.set(k, value)` to store * and `ctx.get(k)` to retrieve. Throws if read before being set. * * @param name - Debug label (shown in error messages, not used for lookup) * * @example * ```ts * const userIdKey = key('userId') * ctx.set(userIdKey, '123') * ctx.get(userIdKey) // '123' * ``` */ declare function key(name: string): Key; /** * Creates a lazily-computed, read-only context slot. The factory runs once * per `EventContext` on first `ctx.get(slot)` call; the result is cached * for the context lifetime. Errors are also cached and re-thrown. * * @param fn - Factory receiving the current `EventContext`, returning the value to cache * * @example * ```ts * const parsedUrl = cached((ctx) => new URL(ctx.get(rawUrlKey))) * // first call computes, subsequent calls return cached result * ctx.get(parsedUrl) * ``` */ declare function cached(fn: (ctx: EventContext) => T): Cached; /** * Creates a parameterized cached computation. Maintains a `Map` per * event context — one cached result per unique key argument. * * @param fn - Factory receiving the lookup key and `EventContext`, returning the value to cache * @returns A function `(key: K, ctx?: EventContext) => V` that computes on first call per key * * @example * ```ts * const parseCookie = cachedBy((name: string, ctx) => { * const raw = ctx.get(cookieHeaderKey) * return parseSingleCookie(raw, name) * }) * * parseCookie('session') // computed and cached for 'session' * parseCookie('theme') // computed and cached for 'theme' * parseCookie('session') // returns cached result * ``` */ declare function cachedBy(fn: (key: K, ctx: EventContext) => V): (key: K, ctx?: EventContext) => V; /** * Type-level marker used inside `defineEventKind` schemas. Each `slot()` * becomes a typed `Key` on the resulting `EventKind`. Has no runtime behavior. * * @example * ```ts * const httpKind = defineEventKind('http', { * req: slot(), * response: slot(), * }) * ``` */ declare function slot(): SlotMarker; /** * Declares a named event kind with typed seed slots. The returned object * contains `keys` — typed accessors for reading seed values from context — * and is passed to `ctx.seed(kind, seeds)` or `createEventContext()`. * * @param name - Unique event kind name (e.g. `'http'`, `'cli'`, `'workflow'`) * @param schema - Object mapping slot names to `slot()` markers * @returns An `EventKind` with typed `keys` for context access * * @example * ```ts * const httpKind = defineEventKind('http', { * req: slot(), * response: slot(), * }) * * // Access typed seed values: * const req = ctx.get(httpKind.keys.req) // IncomingMessage * ``` */ declare function defineEventKind>>(name: string, schema: S): EventKind; /** * A composable function created by {@link defineWook}. Callable as `(ctx?) => T`, * with an exposed `_slot` for advanced use cases such as slot isolation in child contexts. */ interface WookComposable { (ctx?: EventContext): T; /** The underlying `Cached` slot. Useful for building isolation lists in child contexts. */ readonly _slot: Cached; } /** * Creates a composable with per-event caching. The factory runs once per * `EventContext`; subsequent calls within the same event return the cached result. * * This is the recommended way to build composables in Wooks. All built-in * composables (`useRequest`, `useResponse`, `useCookies`, etc.) are created with `defineWook`. * * @param factory - Receives the `EventContext` and returns the composable's public API * @returns A composable function with an exposed `_slot` for isolation * * @example * ```ts * export const useCurrentUser = defineWook((ctx) => { * const { basicCredentials } = useAuthorization(ctx) * const username = basicCredentials()?.username * return { * username, * profile: async () => username ? await db.findUser(username) : null, * } * }) * * // In a handler — factory runs once, cached for the request: * const { username, profile } = useCurrentUser() * ``` */ declare function defineWook(factory: (ctx: EventContext) => T): WookComposable; /** * No-op base class for observability integration. Subclass and override * `with()` / `hook()` to add tracing, metrics, or logging around event * lifecycle points. * * The default implementation simply calls the callback with no overhead. * Replace via `replaceContextInjector()` to enable instrumentation. */ declare class ContextInjector { /** * Wraps a callback with optional named attributes for observability. * Default implementation just calls `cb()` — override for tracing. */ with(name: N, attributes: Record, cb: () => T): T; with(name: N, cb: () => T): T; /** * Hook called by adapters at specific lifecycle points (e.g., after route lookup). * Default implementation is a no-op — override for observability. */ hook(_method: string, _name: 'Handler:not_found' | 'Handler:routed', _route?: string): void; } /** * Returns the current `ContextInjector` instance, or `null` if none has been installed. * Used internally by adapters to wrap lifecycle events. */ declare function getContextInjector(): ContextInjector | null; /** * Replaces the global `ContextInjector` with a custom implementation. * Use this to integrate OpenTelemetry or other observability tools. * * @param newCi - Custom `ContextInjector` subclass instance * * @example * ```ts * class OtelInjector extends ContextInjector { * with(name: string, attrs: Record, cb: () => T): T { * return tracer.startActiveSpan(name, (span) => { * span.setAttributes(attrs) * try { return cb() } finally { span.end() } * }) * } * } * replaceContextInjector(new OtelInjector()) * ``` */ declare function replaceContextInjector(newCi: ContextInjector): void; /** * Resets the global `ContextInjector` back to `null` (no-op default). * Useful for tests or when disabling instrumentation. */ declare function resetContextInjector(): void; /** Built-in hook names used by the framework. */ type TContextInjectorHooks = 'Event:start'; /** Context key for route parameters. Set by adapters after route matching. */ declare const routeParamsKey: Key>; /** Context key for the event type name (e.g. `'http'`, `'cli'`). Set by `ctx.seed()`. */ declare const eventTypeKey: Key; /** * Returns the route parameters for the current event. Works with HTTP * routes, CLI commands, workflow steps — any adapter that sets `routeParamsKey`. * * @param ctx - Optional explicit context (defaults to `current()`) * @returns Object with `params` (the full params record) and `get(name)` for typed access * * @example * ```ts * app.get('/users/:id', () => { * const { params, get } = useRouteParams<{ id: string }>() * console.log(get('id')) // typed as string * }) * ``` */ declare function useRouteParams = Record>(ctx?: EventContext): { params: T; get: (name: K) => T[K]; }; /** * Provides a unique, per-event identifier. The ID is a random UUID, generated * lazily on first `getId()` call and cached for the event lifetime. * * @param ctx - Optional explicit context (defaults to `current()`) * * @example * ```ts * const { getId } = useEventId() * logger.info(`Request ${getId()}`) * ``` */ declare function useEventId(ctx?: EventContext): { getId: () => string; }; /** * Runs a callback with the given `EventContext` as the active context. * All composables and `current()` calls inside `fn` will resolve to `ctx`. * * @param ctx - The event context to make active * @param fn - Callback to execute within the context scope * @returns The return value of `fn` * * @example * ```ts * const ctx = new EventContext({ logger }) * run(ctx, () => { * // current() returns ctx here * const logger = useLogger() * }) * ``` */ declare function run(ctx: EventContext, fn: () => R): R; /** * Returns the active `EventContext` for the current async scope. * Throws if called outside an event context (e.g., at module level). * * All composables use this internally. Prefer composables over direct `current()` access. * * @throws Error if no active event context exists */ declare function current(): EventContext; /** * Returns the active `EventContext`, or `undefined` if none is active. * Use this when context availability is uncertain (e.g., in code that may * run both inside and outside an event handler). */ declare function tryGetCurrent(): EventContext | undefined; /** * Returns the logger for the current event context. * * When called with a `topic` string, creates a child logger via * `logger.createTopic()` (if supported). Falls back to the base * logger when `createTopic` is not available. * * @example * ```ts * const logger = useLogger() * logger.info('Processing request') * * const scoped = useLogger('auth') * scoped.warn('Token expired') * ``` */ declare function useLogger(): Logger; declare function useLogger(topic: string): Logger; declare function useLogger(ctx: EventContext): Logger; declare function useLogger(topic: string, ctx: EventContext): Logger; /** * Creates a new `EventContext`, makes it the active context via * `AsyncLocalStorage`, and runs `fn` inside it. * * @param options - Context options (must include `logger`) * @param fn - Callback to execute within the new context * @returns The return value of `fn` * * The kindless overload is a convenience for tests that need a context scope * without declaring an event kind. Production code should always provide a kind. * * @example * ```ts * createEventContext({ logger }, () => { * // composables work here * }) * ``` */ declare function createEventContext(options: EventContextOptions, fn: () => R): R; /** * Creates a new `EventContext` with an event kind, seeds the kind's slots, * and runs `fn` inside the context. * * @param options - Context options (must include `logger`) * @param kind - Event kind (from `defineEventKind`) * @param seeds - Seed values for the event kind's slots * @param fn - Callback to execute within the new context * @returns The return value of `fn` * * @example * ```ts * const httpKind = defineEventKind('http', { req: slot() }) * * createEventContext({ logger }, httpKind, { req: incomingMessage }, () => { * const req = current().get(httpKind.keys.req) * }) * ``` */ declare function createEventContext, R>(options: EventContextOptions, kind: EventKind, seeds: EventKindSeeds>, fn: () => R): R; export { ContextInjector, EventContext, cached, cachedBy, createEventContext, current, defineEventKind, defineWook, eventTypeKey, getContextInjector, key, replaceContextInjector, resetContextInjector, routeParamsKey, run, slot, tryGetCurrent, useEventId, useLogger, useRouteParams }; export type { Accessor, Cached, EventContextOptions, EventKind, EventKindSeeds, Key, Logger, SlotMarker, TContextInjectorHooks, WookComposable };