opinionated-machine
Version:
Very opinionated DI framework for fastify, built on top of awilix
96 lines (95 loc) • 4.5 kB
TypeScript
import type { AnyDualModeContractDefinition, AnySSEContractDefinition } from '@lokalise/api-contracts';
import type { z } from 'zod';
import type { BuildFastifyDualModeRoutesReturnType, BuildFastifySSERoutesReturnType } from '../routes/fastifyRouteTypes.ts';
import { AbstractSSEController } from '../sse/AbstractSSEController.ts';
import type { DualModeControllerConfig } from './dualModeTypes.ts';
/**
* Extract all event names from dual-mode contracts as a union of string literals.
*/
export type AllDualModeContractEventNames<Contracts extends Record<string, AnyDualModeContractDefinition>> = Contracts[keyof Contracts]['serverSentEventSchemas'] extends infer E ? E extends Record<string, z.ZodTypeAny> ? keyof E & string : never : never;
/**
* Extract the schema for a specific event name across all dual-mode contracts.
*/
export type ExtractDualModeEventSchema<Contracts extends Record<string, AnyDualModeContractDefinition>, EventName extends string> = {
[K in keyof Contracts]: EventName extends keyof Contracts[K]['serverSentEventSchemas'] ? Contracts[K]['serverSentEventSchemas'][EventName] : never;
}[keyof Contracts];
/**
* Abstract base class for dual-mode controllers.
*
* Dual-mode controllers handle both SSE streaming and sync responses on the
* same route path, automatically branching based on the `Accept` header.
*
* This class extends `AbstractSSEController` to reuse connection management,
* broadcasting, and lifecycle hooks for the SSE mode.
*
* @template APIContracts - Map of route names to dual-mode route definitions
*
* @example
* ```typescript
* class ChatController extends AbstractDualModeController<typeof contracts> {
* public static contracts = {
* chatCompletion: buildSseContract({ requestBodySchema: ..., successResponseBodySchema: ..., ... }),
* } as const
*
* constructor(deps: Dependencies, config?: DualModeControllerConfig) {
* super(deps, config)
* }
*
* public buildDualModeRoutes() {
* return {
* chatCompletion: this.handleChatCompletion,
* }
* }
*
* private handleChatCompletion = buildHandler(ChatController.contracts.chatCompletion, {
* sync: async (request, reply) => {
* // Return complete response
* return { reply: 'Hello', usage: { tokens: 5 } }
* },
* sse: async (request, sse) => {
* // Stream SSE events with autoClose mode
* const session = sse.start('autoClose')
* await session.send('chunk', { delta: 'Hello' })
* await session.send('done', { usage: { total: 5 } })
* // Connection closes automatically when handler returns
* },
* })
* }
* ```
*/
export declare abstract class AbstractDualModeController<APIContracts extends Record<string, AnyDualModeContractDefinition>> extends AbstractSSEController<Record<string, AnySSEContractDefinition>> {
/**
* Dual-mode controllers must override this constructor and call super with their
* dependencies object and the dual-mode config.
*
* @param _dependencies - The dependencies object (cradle proxy in awilix)
* @param config - Optional dual-mode controller configuration
*/
constructor(_dependencies: object, config?: DualModeControllerConfig);
/**
* Build and return dual-mode route configurations.
* Must be implemented by concrete controllers.
*/
abstract buildDualModeRoutes(): BuildFastifyDualModeRoutesReturnType<APIContracts>;
/**
* SSE routes are not used directly - dual-mode uses buildDualModeRoutes() instead.
* This returns an empty object to satisfy the AbstractSSEController contract.
*/
buildSSERoutes(): BuildFastifySSERoutesReturnType<Record<string, AnySSEContractDefinition>>;
/**
* Send an event to a connection with type-safe event names and data.
*
* This method provides autocomplete and type checking for event names and data
* that match any event defined in the controller's dual-mode contracts.
*
* @param connectionId - The connection to send to
* @param message - The event message with typed event name and data
* @returns true if sent successfully, false if connection not found
*/
sendDualModeEventInternal<EventName extends AllDualModeContractEventNames<APIContracts>>(connectionId: string, message: {
event: EventName;
data: z.input<ExtractDualModeEventSchema<APIContracts, EventName>>;
id?: string;
retry?: number;
}): Promise<boolean>;
}