// @effect-diagnostics strictBooleanExpressions:off import type * as EffectServiceContext from 'effect/Context'; import { HttpApi } from 'effect/http-api'; import { classifyEffectBffEntryModule, type EffectBffEntryShapeFacts, isValidatorAwareHandlerFactory, strictEffectApproachMessage, } from './entry-shape'; import { createHttpApiHandler, type EffectBffOpenApiConfig, type EffectDataPlatformValidationOptions, type EffectRequestValidator, type EffectRpcBffDefinition, type EffectRuntimeLayer, } from './handler'; import type { EffectContext } from './operation-context'; export type EffectBffRequestHandler = ( request: Request, context?: EffectServiceContext.Context | EffectContext, ) => Promise | Response; export type EffectBffHandlerFactory = (options?: { openapi?: EffectBffOpenApiConfig; dataPlatform?: EffectDataPlatformValidationOptions; validateRequest?: EffectRequestValidator; }) => { handler: EffectBffRequestHandler; dispose: () => Promise; }; export type EffectApiModule = { api?: unknown; layer?: unknown; rpc?: EffectRpcBffDefinition; handler?: EffectBffRequestHandler; createHandler?: EffectBffHandlerFactory; default?: unknown; }; type LoadedEffectBffHandler = { handler: EffectBffRequestHandler; dispose?: () => Promise; }; type ResolveEffectBffModuleHandlerOptions = { openapi?: EffectBffOpenApiConfig; dataPlatform?: EffectDataPlatformValidationOptions; /** * Server-seam request validator (cross-project policy). Strict Effect * entries forward it into `createHttpApiHandler`, where it applies to * direct, batched and rpc requests. */ validateRequest?: EffectRequestValidator; onWarning?: (message: string) => void; }; function isRequestHandler(value: unknown): value is EffectBffRequestHandler { return typeof value === 'function'; } function shareDispose(dispose: () => Promise) { let disposePromise: Promise | undefined; return () => { disposePromise ??= Promise.resolve().then(dispose); return disposePromise; }; } function rejectUnsupportedEffectModuleShape( options: ResolveEffectBffModuleHandlerOptions, shape: string, ) { options.onWarning?.(`${strictEffectApproachMessage} Rejected ${shape}.`); return true; } function callEffectBffRequestHandler( handler: EffectBffRequestHandler, request: Request, context: Parameters[1], ) { return context === undefined ? handler(request) : handler(request, context); } function createLoadedHandler(webHandler: { handler: EffectBffRequestHandler; dispose: () => Promise; }): LoadedEffectBffHandler { return { handler: (request, context) => callEffectBffRequestHandler(webHandler.handler, request, context), dispose: shareDispose(webHandler.dispose), }; } function createLoadedHttpApiHandler( webHandler: ReturnType, ): LoadedEffectBffHandler { return createLoadedHandler({ handler: webHandler.handler as EffectBffRequestHandler, dispose: webHandler.dispose, }); } function classifyEffectBffRuntimeEntryShape(module: EffectApiModule) { return classifyEffectBffEntryModule(module, { isRequestHandler, isValidatorAwareHandlerFactory, isHttpApi: HttpApi.isHttpApi, }); } function resolveClassifiedEffectBffModuleHandler( facts: EffectBffEntryShapeFacts | null, options: ResolveEffectBffModuleHandlerOptions = {}, ): LoadedEffectBffHandler | null { if (!facts) { return null; } if (facts.unsupportedShape) { if (rejectUnsupportedEffectModuleShape(options, facts.unsupportedShape)) { return null; } } if (facts.module.rpc !== undefined && !facts.createHandlerValidatorAware) { rejectUnsupportedEffectModuleShape(options, 'unbranded RPC runtime'); return null; } if (typeof facts.createHandler === 'function') { if (facts.createHandlerValidatorAware) { const factory = facts.createHandler as EffectBffHandlerFactory; const webHandler = factory({ openapi: options.openapi, dataPlatform: options.dataPlatform, validateRequest: options.validateRequest, }); return createLoadedHandler(webHandler); } if (facts.api === undefined || !facts.hasRuntimeLayer) { rejectUnsupportedEffectModuleShape( options, 'unbranded `createHandler` export', ); return null; } options.onWarning?.( `${strictEffectApproachMessage} Ignored unbranded \`createHandler\` export and rebuilt the handler from its { api, layer } exports.`, ); } if (facts.api !== undefined && facts.hasRuntimeLayer) { if (facts.createHandler === undefined) { options.onWarning?.( '[BFF][Effect] Detected { api, layer } export without createHandler. Prefer `defineEffectBff(...)` from @modern-js/bff-effect/effect to avoid module instance mismatch.', ); } const webHandler = createHttpApiHandler({ api: facts.api as HttpApi.Top, layer: facts.layer as EffectRuntimeLayer, openapi: options.openapi, dataPlatform: options.dataPlatform, validateRequest: options.validateRequest, }); return createLoadedHttpApiHandler(webHandler); } return null; } function resolveNormalizedEffectBffModuleHandler( normalizedModule: EffectApiModule, options: ResolveEffectBffModuleHandlerOptions = {}, ): LoadedEffectBffHandler | null { return resolveClassifiedEffectBffModuleHandler( classifyEffectBffRuntimeEntryShape(normalizedModule), options, ); } export function resolveEffectBffModuleHandler( mod: EffectApiModule, options: ResolveEffectBffModuleHandlerOptions = {}, ): Promise { return Promise.resolve(resolveNormalizedEffectBffModuleHandler(mod, options)); }