import type { z } from "zod"; import type Adaptor from "./adaptors/adaptor"; import { RequestError } from "./error"; import { type InternalRequest, InternalRequestSchema, type InternalResponse, InternalResponseSchema, type Request, type Response, } from "./messages"; import type { RouteSubscribeTypeFn } from "./types"; export default class Service { private name: string; private adaptors: Record; constructor(name: string) { this.name = name; this.adaptors = {}; } public addAdaptor(name: string, adaptor: Adaptor) { this.adaptors[name] = adaptor; } public subscribe, O, U>( adaptor: string, subject: string, fn: RouteSubscribeTypeFn, U>, schema?: T, ) { this.adaptors[adaptor].subscribe( `${this.name}.${subject}`, async (rawReq) => { const rawReqJson = JSON.parse(rawReq); const internalRequestJson = InternalRequestSchema.safeParse(rawReqJson); if (!internalRequestJson.success) { return JSON.stringify({ statusCode: 400, data: "bad request structure", } satisfies InternalResponse); } const internalRequest = internalRequestJson.data; const req = { service: internalRequest.from, subject: subject, } as Request>; if (internalRequest.data && schema) { const validate = schema.safeParse(internalRequest.data); if (!validate.success) { return JSON.stringify({ statusCode: 400, data: validate.error, } satisfies InternalResponse); } req.data = validate.data; } try { const res = await fn(this, req); const internalResponse = { statusCode: res.statusCode, data: res.data, } satisfies InternalResponse; return JSON.stringify(internalResponse); } catch (err) { if (err instanceof RequestError) { return JSON.stringify({ statusCode: err.statusCode, data: err.message, } satisfies InternalResponse); } return JSON.stringify({ statusCode: 500, data: "unknow error apend", } satisfies InternalResponse); } }, ); } public async request>( adaptor: string, req: Request, schema?: z.ZodType, ): Promise>> { if (!this.adaptors[adaptor]) { throw new Error(`${adaptor} adaptor not exist`); } const internalRequest = { from: this.name, data: req.data, } satisfies InternalRequest; const internalRequestJson = JSON.stringify(internalRequest); try { const rawRes = await this.adaptors[adaptor].request( `${req.service}.${req.subject}`, internalRequestJson, ); const rawResJson: unknown = JSON.parse(rawRes); const internalResponseJson = InternalResponseSchema.safeParse(rawResJson); if (!internalResponseJson.success) { throw new RequestError(internalResponseJson.error.toString(), 500); } const internalResponse = internalResponseJson.data; if ( internalResponse.statusCode < 200 || internalResponse.statusCode >= 299 ) { throw new RequestError( internalResponse.data, internalResponse.statusCode, ); } const res: Response> = { statusCode: internalResponse.statusCode, }; if (internalResponse.data && schema) { const validate = schema.safeParse(internalResponse.data); if (!validate.success) { throw new RequestError(validate.error.message, 400); } res.data = validate.data; } return res; } catch (err) { if (err instanceof RequestError) { throw err; } throw new RequestError("unexpected error", 500); } } public async listen() { for (const index in this.adaptors) { await this.adaptors[index].listen(this.name); } } public async stop() { for (const index in this.adaptors) { await this.adaptors[index].stop(); } } }