2024-01-04 21:20:47 +01:00
|
|
|
import { z } from "zod";
|
2024-01-30 21:50:19 +01:00
|
|
|
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";
|
2023-10-19 21:48:44 +02:00
|
|
|
|
|
|
|
export default class Service {
|
2024-01-30 21:50:19 +01:00
|
|
|
private name: string;
|
|
|
|
|
|
|
|
private adaptors: Record<string, Adaptor>;
|
|
|
|
|
|
|
|
constructor(name: string) {
|
|
|
|
this.name = name;
|
|
|
|
this.adaptors = {};
|
|
|
|
}
|
|
|
|
|
|
|
|
public addAdaptor(name: string, adaptor: Adaptor) {
|
|
|
|
this.adaptors[name] = adaptor;
|
|
|
|
}
|
|
|
|
|
|
|
|
public subscribe<T extends z.ZodType<O>, O, U>(
|
|
|
|
adaptor: string,
|
|
|
|
subject: string,
|
|
|
|
fn: RouteSubscribeTypeFn<z.infer<T>, U>,
|
|
|
|
schema: T | undefined = undefined,
|
|
|
|
) {
|
|
|
|
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<z.infer<T>>;
|
|
|
|
|
|
|
|
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(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<T, O, U extends z.ZodType<O>>(
|
|
|
|
adaptor: string,
|
|
|
|
req: Request<T>,
|
|
|
|
schema?: z.ZodType<O>,
|
|
|
|
): Promise<Response<z.infer<U>>> {
|
|
|
|
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<z.infer<U>> = {
|
|
|
|
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();
|
|
|
|
}
|
|
|
|
}
|
2024-01-04 21:20:47 +01:00
|
|
|
}
|