import { RPC_ERROR_CODES, failure, success } from "./errors.ts"; import { importSubjectContext, type SubjectContext } from "./identity.ts"; import type { AnyProcedures, InferProcedureInput, InferProcedureOutput, ServiceContract, ServiceResult, } from "./types.ts"; export interface HandlerContext { subject?: SubjectContext; } export type ServiceHandlers = { [K in keyof Procedures]: ( input: InferProcedureInput, ctx: HandlerContext, ) => Promise> | InferProcedureOutput; }; export interface ImplementOptions { selfApp: string; checkPermission?: (permission: string, subject?: SubjectContext) => Promise | boolean; } export interface ServiceImplementation { contract: ServiceContract; invoke(procedure: string, payload: unknown, identity?: string): Promise; } export function implement( contract: ServiceContract, handlers: ServiceHandlers, options: ImplementOptions, ): ServiceImplementation { return { contract, async invoke(procedureName, payload, identity) { const definition = contract.procedures[procedureName as keyof Procedures]; const handler = handlers[procedureName as keyof Procedures]; if (!definition || !handler) return failure(RPC_ERROR_CODES.unknown, "Unknown procedure"); let subject: SubjectContext | undefined; if (identity !== undefined) { try { subject = await importSubjectContext(identity, options.selfApp); } catch { return failure(RPC_ERROR_CODES.identity, "Invalid identity"); } } if (definition.permission) { if (!options.checkPermission) return failure(RPC_ERROR_CODES.denied, "Forbidden"); try { if (!(await options.checkPermission(definition.permission, subject))) { return failure(RPC_ERROR_CODES.denied, "Forbidden"); } } catch { return failure(RPC_ERROR_CODES.denied, "Forbidden"); } } let input: unknown = payload; if (definition.input) { const parsed = definition.input.parse(payload as Record); if (!parsed.ok) return failure(RPC_ERROR_CODES.invalid, "Invalid input"); input = parsed.value; } try { const value = await (handler as (value: unknown, ctx: HandlerContext) => unknown)(input, { subject, }); return success(value); } catch { return failure(RPC_ERROR_CODES.handler, "Internal error"); } }, }; }