@seam-rpc/server 4.3.22 → 5.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/generate.js +25 -4
- package/dist/index copy.d.ts +84 -0
- package/dist/index copy.js +254 -0
- package/dist/index.d.ts +37 -29
- package/dist/index.js +171 -189
- package/dist/router.d.ts +14 -0
- package/dist/router.js +197 -0
- package/package.json +1 -1
- package/dist/bin/gen-client.js +0 -193
- package/dist/bin/gen-config.js +0 -44
- package/dist/test.d.ts +0 -2
- package/dist/test.js +0 -56
- package/dist/validation.js +0 -54
package/dist/bin/generate.js
CHANGED
|
@@ -18,10 +18,11 @@ export async function generateClient() {
|
|
|
18
18
|
// try {
|
|
19
19
|
const outputFiles = [];
|
|
20
20
|
for (let i = 0; i < sourceFiles.length; i++) {
|
|
21
|
-
|
|
22
|
-
const
|
|
23
|
-
const
|
|
24
|
-
|
|
21
|
+
gen(sourceFiles[i], outputPath);
|
|
22
|
+
// const sourceFile = sourceFiles[i];
|
|
23
|
+
// const path = await generateClientFile({ tsPath: sourceFile, jsPath: compiledFolder, outputPath });
|
|
24
|
+
// const symb = i < sourceFiles.length - 1 ? " ├╴" : " └╴";
|
|
25
|
+
// outputFiles.push(symb + removeRootPath(path));
|
|
25
26
|
}
|
|
26
27
|
console.log("\x1b[32m\x1b[1m%s\x1b[0m\n\x1b[94m%s\x1b[0m", `✅ Successfully generated client files at ${config.outputFolder}`, `${outputFiles.join("\n")}`);
|
|
27
28
|
// } catch (err: any) {
|
|
@@ -116,6 +117,26 @@ function convert(schema) {
|
|
|
116
117
|
const { node } = zodToTs(schema, { auxiliaryTypeStore: store });
|
|
117
118
|
return printNode(node);
|
|
118
119
|
}
|
|
120
|
+
function gen(src, outputPath) {
|
|
121
|
+
const options = {
|
|
122
|
+
declaration: true,
|
|
123
|
+
emitDeclarationOnly: true,
|
|
124
|
+
outDir: outputPath,
|
|
125
|
+
target: ts.ScriptTarget.ES2020,
|
|
126
|
+
module: ts.ModuleKind.NodeNext,
|
|
127
|
+
strict: true,
|
|
128
|
+
};
|
|
129
|
+
const host = ts.createCompilerHost(options);
|
|
130
|
+
const file = path.resolve(process.cwd(), src);
|
|
131
|
+
const program = ts.createProgram([file], options, host);
|
|
132
|
+
console.log(file, outputPath);
|
|
133
|
+
const emitResult = program.emit();
|
|
134
|
+
const diagnostics = ts.getPreEmitDiagnostics(program)
|
|
135
|
+
.concat(emitResult.diagnostics);
|
|
136
|
+
for (const d of diagnostics) {
|
|
137
|
+
console.log(ts.flattenDiagnosticMessageText(d.messageText, "\n"));
|
|
138
|
+
}
|
|
139
|
+
}
|
|
119
140
|
export async function generateClientFile({ tsPath, jsPath, outputPath, }) {
|
|
120
141
|
const tsFile = path.resolve(process.cwd(), tsPath);
|
|
121
142
|
const tsFileName = path.basename(tsFile).slice(0, -path.extname(tsFile).length);
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { Result, ApiError } from "@seam-rpc/core";
|
|
2
|
+
import EventEmitter from "events";
|
|
3
|
+
import express, { Express, NextFunction, Request, RequestHandler, Response } from "express";
|
|
4
|
+
import * as z from "zod";
|
|
5
|
+
export { ApiError as RpcError };
|
|
6
|
+
export type { Result };
|
|
7
|
+
export interface RouterDefinition {
|
|
8
|
+
[procName: string]: (...args: any[]) => Promise<any>;
|
|
9
|
+
}
|
|
10
|
+
type Simplify<T> = T extends File ? File : T extends object ? {
|
|
11
|
+
[K in keyof T]: Simplify<T[K]>;
|
|
12
|
+
} : T;
|
|
13
|
+
export interface SeamContext {
|
|
14
|
+
request: Request;
|
|
15
|
+
response: Response;
|
|
16
|
+
next: NextFunction;
|
|
17
|
+
}
|
|
18
|
+
export interface SeamErrorContext {
|
|
19
|
+
routerPath: string;
|
|
20
|
+
procedureName: string;
|
|
21
|
+
input?: Record<string, unknown> | null;
|
|
22
|
+
validatedInput?: Record<string, unknown> | null;
|
|
23
|
+
output?: unknown;
|
|
24
|
+
validatedOutput?: unknown;
|
|
25
|
+
request: Request;
|
|
26
|
+
response: Response;
|
|
27
|
+
next: NextFunction;
|
|
28
|
+
}
|
|
29
|
+
export interface SeamEvents {
|
|
30
|
+
apiError: [error: unknown, context: SeamErrorContext];
|
|
31
|
+
internalError: [error: unknown, context: SeamErrorContext];
|
|
32
|
+
inputValidationError: [error: unknown, context: SeamErrorContext];
|
|
33
|
+
outputValidationError: [error: unknown, context: SeamErrorContext];
|
|
34
|
+
}
|
|
35
|
+
type ProcedureHandler<Input extends ProcedureInput, Output extends ProcedureOutput, Errors extends ProcedureErrors> = (options: ProcedureOptions<Input, Errors>) => Result<z.infer<Output>, ApiError> | Promise<Result<z.infer<Output>, ApiError>>;
|
|
36
|
+
interface ProcedureOptions<Input extends ProcedureInput, Errors extends ProcedureErrors> {
|
|
37
|
+
input: Simplify<ProcedureInputData<Input>>;
|
|
38
|
+
ctx: SeamContext;
|
|
39
|
+
error: RpcErrorFactory<Errors>;
|
|
40
|
+
}
|
|
41
|
+
type RpcErrorFactory<Errors extends ProcedureErrors> = <Code extends keyof Errors>(code: Code, ...args: z.infer<Errors[Code]> extends undefined ? [] : [data: z.infer<Errors[Code]>]) => void;
|
|
42
|
+
type ProcedureInput = Record<string, z.ZodType>;
|
|
43
|
+
type ProcedureOutput = z.ZodType;
|
|
44
|
+
type ProcedureErrors = Record<string, z.ZodType>;
|
|
45
|
+
type ProcedureBuilderList = Record<string, ProcedureBuilder<any, any, any>>;
|
|
46
|
+
type ProcedureInputData<T extends ProcedureInput> = {
|
|
47
|
+
[K in keyof T]: z.infer<T[K]>;
|
|
48
|
+
};
|
|
49
|
+
interface SeamProcedure<Input extends ProcedureInput, Output extends ProcedureOutput, Errors extends ProcedureErrors> {
|
|
50
|
+
input?: Input;
|
|
51
|
+
output?: Output;
|
|
52
|
+
errors?: Errors;
|
|
53
|
+
handler?: ProcedureHandler<Input, Output, Errors>;
|
|
54
|
+
}
|
|
55
|
+
export type ProcedureBuilder<Input extends ProcedureInput, Output extends ProcedureOutput, Errors extends ProcedureErrors> = {
|
|
56
|
+
_def: SeamProcedure<Input, Output, Errors>;
|
|
57
|
+
input: <T extends ProcedureInput>(schema: T) => ProcedureBuilder<T, Output, Errors>;
|
|
58
|
+
output: <T extends ProcedureOutput>(schema: T) => ProcedureBuilder<Input, T, Errors>;
|
|
59
|
+
errors: <T extends ProcedureErrors>(errors: T) => ProcedureBuilder<Input, Output, T>;
|
|
60
|
+
handler: (handler: ProcedureHandler<Input, Output, Errors>) => ProcedureBuilder<Input, Output, Errors>;
|
|
61
|
+
};
|
|
62
|
+
export declare function createSeamSpace(app: Express, fileHandler?: RequestHandler): Promise<SeamSpace>;
|
|
63
|
+
export declare function seamRouter(path: string, procedures: ProcedureBuilderList): SeamRouter;
|
|
64
|
+
export declare function seamProcedure(): ProcedureBuilder<ProcedureInput, ProcedureOutput, ProcedureErrors>;
|
|
65
|
+
export declare class SeamRouter extends EventEmitter<SeamEvents> {
|
|
66
|
+
private _procedures;
|
|
67
|
+
private _router;
|
|
68
|
+
constructor(path: string, procedures: ProcedureBuilderList);
|
|
69
|
+
get router(): express.Router;
|
|
70
|
+
private runMiddleware;
|
|
71
|
+
private validateInput;
|
|
72
|
+
private validateOutput;
|
|
73
|
+
private validateData;
|
|
74
|
+
}
|
|
75
|
+
export declare class SeamSpace extends EventEmitter<SeamEvents> {
|
|
76
|
+
private _app;
|
|
77
|
+
private _fileHandler;
|
|
78
|
+
private _jsonParser;
|
|
79
|
+
constructor(_app: Express, _fileHandler: RequestHandler);
|
|
80
|
+
createRouter(path: string, procedures?: ProcedureBuilderList): SeamRouter;
|
|
81
|
+
get app(): express.Express;
|
|
82
|
+
get jsonParser(): import("connect").NextHandleFunction;
|
|
83
|
+
get fileHandler(): express.RequestHandler<import("express-serve-static-core").ParamsDictionary, any, any, import("qs").ParsedQs, Record<string, any>>;
|
|
84
|
+
}
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
import { extractFiles, injectFiles, ApiError } from "@seam-rpc/core";
|
|
2
|
+
import EventEmitter from "events";
|
|
3
|
+
import express, { Router } from "express";
|
|
4
|
+
import FormData from "form-data";
|
|
5
|
+
import * as z from "zod";
|
|
6
|
+
export { ApiError as RpcError };
|
|
7
|
+
;
|
|
8
|
+
;
|
|
9
|
+
const errorHandler = (code, ...args) => {
|
|
10
|
+
return new ApiError(code, args[0]);
|
|
11
|
+
};
|
|
12
|
+
export async function createSeamSpace(app, fileHandler) {
|
|
13
|
+
if (!fileHandler) {
|
|
14
|
+
let multer;
|
|
15
|
+
try {
|
|
16
|
+
multer = (await import("multer")).default;
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
throw new Error("Multer is required as default file handler. Install it or provide a custom fileHandler.");
|
|
20
|
+
}
|
|
21
|
+
const upload = multer();
|
|
22
|
+
fileHandler = upload.any();
|
|
23
|
+
}
|
|
24
|
+
return new SeamSpace(app, fileHandler);
|
|
25
|
+
}
|
|
26
|
+
export function seamRouter(path, procedures) {
|
|
27
|
+
return new SeamRouter(path, procedures);
|
|
28
|
+
}
|
|
29
|
+
export function seamProcedure() {
|
|
30
|
+
return createProcedureBuilder();
|
|
31
|
+
}
|
|
32
|
+
function createProcedureBuilder(definition = {}) {
|
|
33
|
+
return {
|
|
34
|
+
_def: definition,
|
|
35
|
+
input: schema => createProcedureBuilder({
|
|
36
|
+
...definition,
|
|
37
|
+
input: schema,
|
|
38
|
+
}),
|
|
39
|
+
output: schema => createProcedureBuilder({
|
|
40
|
+
...definition,
|
|
41
|
+
output: schema,
|
|
42
|
+
}),
|
|
43
|
+
errors: errors => createProcedureBuilder({
|
|
44
|
+
...definition,
|
|
45
|
+
errors
|
|
46
|
+
}),
|
|
47
|
+
handler: handler => createProcedureBuilder({
|
|
48
|
+
...definition,
|
|
49
|
+
handler
|
|
50
|
+
}),
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
export class SeamRouter extends EventEmitter {
|
|
54
|
+
constructor(path, procedures) {
|
|
55
|
+
super();
|
|
56
|
+
this._procedures = {};
|
|
57
|
+
this._router = Router();
|
|
58
|
+
for (const proc in procedures) {
|
|
59
|
+
this._procedures[proc] = procedures[proc]._def;
|
|
60
|
+
}
|
|
61
|
+
// seamSpace.app.use(path, this._router);
|
|
62
|
+
this._router.post("/:procName", async (req, res, next) => {
|
|
63
|
+
const procedure = this._procedures[req.params.procName];
|
|
64
|
+
if (!procedure || !procedure.handler)
|
|
65
|
+
return res.sendStatus(404);
|
|
66
|
+
let input;
|
|
67
|
+
let validatedInput = undefined;
|
|
68
|
+
let output = undefined;
|
|
69
|
+
let validatedOutput;
|
|
70
|
+
// Middleware
|
|
71
|
+
try {
|
|
72
|
+
input = await this.runMiddleware(req, res);
|
|
73
|
+
}
|
|
74
|
+
catch (err) {
|
|
75
|
+
console.error(err);
|
|
76
|
+
return res.status(415).send(String(err));
|
|
77
|
+
}
|
|
78
|
+
// Validate input
|
|
79
|
+
try {
|
|
80
|
+
validatedInput = this.validateInput(input, procedure.input);
|
|
81
|
+
}
|
|
82
|
+
catch (err) {
|
|
83
|
+
this.emit("inputValidationError", err, {
|
|
84
|
+
routerPath: path,
|
|
85
|
+
procedureName: req.params.procName,
|
|
86
|
+
input,
|
|
87
|
+
validatedInput,
|
|
88
|
+
output,
|
|
89
|
+
validatedOutput,
|
|
90
|
+
request: req,
|
|
91
|
+
response: res,
|
|
92
|
+
next,
|
|
93
|
+
});
|
|
94
|
+
res.sendStatus(400);
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
// Call procedure
|
|
98
|
+
const ctx = {
|
|
99
|
+
request: req,
|
|
100
|
+
response: res,
|
|
101
|
+
next,
|
|
102
|
+
};
|
|
103
|
+
function toResError(error) {
|
|
104
|
+
if (error instanceof ApiError)
|
|
105
|
+
return { isApiError: true, error: error.toJSON() };
|
|
106
|
+
else
|
|
107
|
+
return { isApiError: false };
|
|
108
|
+
}
|
|
109
|
+
try {
|
|
110
|
+
output = await procedure.handler({ input: validatedInput, ctx, error: errorHandler });
|
|
111
|
+
}
|
|
112
|
+
catch (error) {
|
|
113
|
+
this.emit("apiError", error, {
|
|
114
|
+
routerPath: path,
|
|
115
|
+
procedureName: req.params.procName,
|
|
116
|
+
input,
|
|
117
|
+
validatedInput,
|
|
118
|
+
output,
|
|
119
|
+
validatedOutput,
|
|
120
|
+
request: req,
|
|
121
|
+
response: res,
|
|
122
|
+
next: () => { res.status(400).json(toResError(error)); return next(); },
|
|
123
|
+
});
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
if (!output.ok) {
|
|
127
|
+
this.emit("apiError", output.error, {
|
|
128
|
+
routerPath: path,
|
|
129
|
+
procedureName: req.params.procName,
|
|
130
|
+
input,
|
|
131
|
+
validatedInput,
|
|
132
|
+
output,
|
|
133
|
+
validatedOutput,
|
|
134
|
+
request: req,
|
|
135
|
+
response: res,
|
|
136
|
+
next: () => { res.status(400).json(toResError(output.error)); return next(); },
|
|
137
|
+
});
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
// Validate output
|
|
141
|
+
try {
|
|
142
|
+
validatedOutput = this.validateOutput(output, z.object({ ok: z.literal(true), data: procedure.output }).or(z.object({ ok: z.literal(false), error: z.any() })));
|
|
143
|
+
}
|
|
144
|
+
catch (err) {
|
|
145
|
+
this.emit("outputValidationError", err, {
|
|
146
|
+
routerPath: path,
|
|
147
|
+
procedureName: req.params.procName,
|
|
148
|
+
input,
|
|
149
|
+
validatedInput,
|
|
150
|
+
output,
|
|
151
|
+
validatedOutput,
|
|
152
|
+
request: req,
|
|
153
|
+
response: res,
|
|
154
|
+
next,
|
|
155
|
+
});
|
|
156
|
+
res.sendStatus(500);
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
try {
|
|
160
|
+
const { json, files, paths } = extractFiles({ result: validatedOutput });
|
|
161
|
+
// Does not include file(s)
|
|
162
|
+
if (files.length === 0) {
|
|
163
|
+
res.json(json);
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
// Includes file(s)
|
|
167
|
+
const form = new FormData();
|
|
168
|
+
form.append("json", JSON.stringify(json));
|
|
169
|
+
form.append("paths", JSON.stringify(paths));
|
|
170
|
+
for (let i = 0; i < files.length; i++) {
|
|
171
|
+
const file = files[i];
|
|
172
|
+
const buffer = Buffer.from(await file.arrayBuffer());
|
|
173
|
+
form.append(`file-${i}`, buffer, {
|
|
174
|
+
filename: file.name || `file-${i}`,
|
|
175
|
+
contentType: file.type || "application/octet-stream",
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
res.writeHead(200, form.getHeaders());
|
|
179
|
+
form.pipe(res);
|
|
180
|
+
}
|
|
181
|
+
catch (error) {
|
|
182
|
+
this.emit("internalError", error, {
|
|
183
|
+
routerPath: path,
|
|
184
|
+
procedureName: req.params.procName,
|
|
185
|
+
input,
|
|
186
|
+
validatedInput,
|
|
187
|
+
output,
|
|
188
|
+
validatedOutput,
|
|
189
|
+
request: req,
|
|
190
|
+
response: res,
|
|
191
|
+
next: next,
|
|
192
|
+
});
|
|
193
|
+
res.sendStatus(500); //.send({ error: String(error) });
|
|
194
|
+
}
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
get router() {
|
|
198
|
+
return this._router;
|
|
199
|
+
}
|
|
200
|
+
async runMiddleware(req, res) {
|
|
201
|
+
const contentType = req.headers["content-type"] || "";
|
|
202
|
+
const runMiddleware = (middleware) => new Promise((resolve, reject) => middleware(req, res, err => (err ? reject(err) : resolve())));
|
|
203
|
+
// if (contentType.startsWith("application/json")) {
|
|
204
|
+
// await runMiddleware(this.seamSpace.jsonParser);
|
|
205
|
+
// } else if (contentType.startsWith("multipart/form-data")) {
|
|
206
|
+
// await runMiddleware(this.seamSpace.fileHandler);
|
|
207
|
+
// } else {
|
|
208
|
+
// throw new Error("Unsupported content type.");
|
|
209
|
+
// }
|
|
210
|
+
if (contentType.startsWith("application/json"))
|
|
211
|
+
return req.body;
|
|
212
|
+
// multipart/form-data (already checked before)
|
|
213
|
+
let input = JSON.parse(req.body.json);
|
|
214
|
+
const paths = JSON.parse(req.body.paths);
|
|
215
|
+
const files = (req.files ?? []).map((file, index) => ({
|
|
216
|
+
path: paths[index],
|
|
217
|
+
file: new File([file.buffer], file.originalname, { type: file.mimetype }),
|
|
218
|
+
}));
|
|
219
|
+
injectFiles(input, files);
|
|
220
|
+
return input;
|
|
221
|
+
}
|
|
222
|
+
validateInput(input, inputSchema) {
|
|
223
|
+
return this.validateData(input, z.object(inputSchema));
|
|
224
|
+
}
|
|
225
|
+
validateOutput(output, procOutput) {
|
|
226
|
+
return this.validateData(output, procOutput);
|
|
227
|
+
}
|
|
228
|
+
validateData(data, schema) {
|
|
229
|
+
if (!schema) {
|
|
230
|
+
if (data)
|
|
231
|
+
throw new Error("Received data, but no data expected.");
|
|
232
|
+
return null;
|
|
233
|
+
}
|
|
234
|
+
if (!data)
|
|
235
|
+
throw new Error("No data was received, but data was expected.");
|
|
236
|
+
return schema.parse(data);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
export class SeamSpace extends EventEmitter {
|
|
240
|
+
constructor(_app, _fileHandler) {
|
|
241
|
+
super();
|
|
242
|
+
this._app = _app;
|
|
243
|
+
this._fileHandler = _fileHandler;
|
|
244
|
+
this._jsonParser = express.json();
|
|
245
|
+
}
|
|
246
|
+
createRouter(path, procedures = {}) {
|
|
247
|
+
const router = new SeamRouter(path, procedures);
|
|
248
|
+
// router.addProcedures(procedures);
|
|
249
|
+
return router;
|
|
250
|
+
}
|
|
251
|
+
get app() { return this._app; }
|
|
252
|
+
get jsonParser() { return this._jsonParser; }
|
|
253
|
+
get fileHandler() { return this._fileHandler; }
|
|
254
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,12 +1,9 @@
|
|
|
1
|
-
import { Result,
|
|
1
|
+
import { Result, ApiError, ApiErrorInterface } from "@seam-rpc/core";
|
|
2
2
|
import EventEmitter from "events";
|
|
3
3
|
import express, { Express, NextFunction, Request, RequestHandler, Response } from "express";
|
|
4
4
|
import * as z from "zod";
|
|
5
|
-
export { RpcError };
|
|
5
|
+
export { ApiError as RpcError };
|
|
6
6
|
export type { Result };
|
|
7
|
-
export interface RouterDefinition {
|
|
8
|
-
[procName: string]: (...args: any[]) => Promise<any>;
|
|
9
|
-
}
|
|
10
7
|
type Simplify<T> = T extends File ? File : T extends object ? {
|
|
11
8
|
[K in keyof T]: Simplify<T[K]>;
|
|
12
9
|
} : T;
|
|
@@ -32,53 +29,64 @@ export interface SeamEvents {
|
|
|
32
29
|
inputValidationError: [error: unknown, context: SeamErrorContext];
|
|
33
30
|
outputValidationError: [error: unknown, context: SeamErrorContext];
|
|
34
31
|
}
|
|
35
|
-
type ProcedureHandler<Input extends ProcedureInput, Output extends ProcedureOutput
|
|
36
|
-
interface ProcedureOptions<Input extends ProcedureInput
|
|
32
|
+
type ProcedureHandler<Input extends ProcedureInput, Output extends ProcedureOutput> = (options: ProcedureOptions<Input>) => Result<z.infer<Output>, ApiError> | Promise<Result<z.infer<Output>, ApiError>>;
|
|
33
|
+
interface ProcedureOptions<Input extends ProcedureInput> {
|
|
37
34
|
input: Simplify<ProcedureInputData<Input>>;
|
|
38
35
|
ctx: SeamContext;
|
|
39
|
-
error: RpcErrorFactory<Errors>;
|
|
40
36
|
}
|
|
41
|
-
type RpcErrorFactory<Errors extends ProcedureErrors> = <Code extends keyof Errors>(code: Code, ...args: z.infer<Errors[Code]> extends undefined ? [] : [data: z.infer<Errors[Code]>]) => void;
|
|
42
37
|
type ProcedureInput = Record<string, z.ZodType>;
|
|
43
38
|
type ProcedureOutput = z.ZodType;
|
|
44
|
-
type ProcedureErrors = Record<string, z.ZodType>;
|
|
45
|
-
type ProcedureBuilderList = Record<string, ProcedureBuilder<any, any, any>>;
|
|
39
|
+
type ProcedureErrors = Record<string, z.ZodType | undefined>;
|
|
46
40
|
type ProcedureInputData<T extends ProcedureInput> = {
|
|
47
41
|
[K in keyof T]: z.infer<T[K]>;
|
|
48
42
|
};
|
|
49
|
-
interface SeamProcedure<Input extends ProcedureInput, Output extends ProcedureOutput, Errors extends ProcedureErrors> {
|
|
43
|
+
interface SeamProcedure<Input extends ProcedureInput = {}, Output extends ProcedureOutput = z.ZodUndefined, Errors extends ProcedureErrors = {}> {
|
|
50
44
|
input?: Input;
|
|
51
45
|
output?: Output;
|
|
52
46
|
errors?: Errors;
|
|
53
|
-
handler?: ProcedureHandler<Input, Output
|
|
47
|
+
handler?: ProcedureHandler<Input, Output>;
|
|
54
48
|
}
|
|
55
|
-
|
|
49
|
+
type ExtractResultError<T> = Awaited<T> extends Result<any, infer E> ? E : never;
|
|
50
|
+
type ErrorUnionToMap<E> = {
|
|
51
|
+
[K in E extends ApiErrorInterface<any, infer Code> ? Code : never]: Extract<E, ApiErrorInterface<any, K>> extends ApiErrorInterface<infer Map, K> ? Map[K] : never;
|
|
52
|
+
};
|
|
53
|
+
export type ProcedureBuilder<Input extends ProcedureInput = {}, Output extends ProcedureOutput = z.ZodUndefined, Errors extends ProcedureErrors = {}> = {
|
|
56
54
|
_def: SeamProcedure<Input, Output, Errors>;
|
|
57
55
|
input: <T extends ProcedureInput>(schema: T) => ProcedureBuilder<T, Output, Errors>;
|
|
58
56
|
output: <T extends ProcedureOutput>(schema: T) => ProcedureBuilder<Input, T, Errors>;
|
|
59
|
-
|
|
60
|
-
handler: (handler: ProcedureHandler<Input, Output, Errors>) => ProcedureBuilder<Input, Output, Errors>;
|
|
57
|
+
handler: <H extends ProcedureHandler<Input, Output>>(handler: H) => ProcedureBuilder<Input, Output, ErrorUnionToMap<ExtractResultError<ReturnType<H>>>>;
|
|
61
58
|
};
|
|
62
59
|
export declare function createSeamSpace(app: Express, fileHandler?: RequestHandler): Promise<SeamSpace>;
|
|
63
|
-
export declare function seamProcedure(): ProcedureBuilder
|
|
64
|
-
export
|
|
65
|
-
private seamSpace;
|
|
66
|
-
private procedures;
|
|
67
|
-
private router;
|
|
68
|
-
constructor(seamSpace: SeamSpace, path: string);
|
|
69
|
-
private runMiddleware;
|
|
70
|
-
private validateInput;
|
|
71
|
-
private validateOutput;
|
|
72
|
-
private validateData;
|
|
73
|
-
addProcedures(procedures: ProcedureBuilderList): void;
|
|
74
|
-
}
|
|
60
|
+
export declare function seamProcedure(): ProcedureBuilder;
|
|
61
|
+
export type SeamRouterBuilder = Record<string, ProcedureBuilder<any, any, any>>;
|
|
75
62
|
export declare class SeamSpace extends EventEmitter<SeamEvents> {
|
|
76
63
|
private _app;
|
|
77
64
|
private _fileHandler;
|
|
78
65
|
private _jsonParser;
|
|
79
66
|
constructor(_app: Express, _fileHandler: RequestHandler);
|
|
80
|
-
|
|
67
|
+
addRouters<T extends Record<string, SeamRouterBuilder>>(routers: T): RouterToClient<T>;
|
|
81
68
|
get app(): express.Express;
|
|
82
69
|
get jsonParser(): import("connect").NextHandleFunction;
|
|
83
70
|
get fileHandler(): express.RequestHandler<import("express-serve-static-core").ParamsDictionary, any, any, import("qs").ParsedQs, Record<string, any>>;
|
|
84
71
|
}
|
|
72
|
+
type ErrorMapToUnion<E extends Record<string, any>> = {
|
|
73
|
+
[K in keyof E]: ApiError<{
|
|
74
|
+
[P in K]: E[K];
|
|
75
|
+
}, K>;
|
|
76
|
+
}[keyof E];
|
|
77
|
+
type ResultFromErrors<Data, Errors extends Record<string, any>> = {
|
|
78
|
+
ok: true;
|
|
79
|
+
data: Data;
|
|
80
|
+
} | (ErrorMapToUnion<Errors> extends infer E ? E extends ApiError<any, any> ? {
|
|
81
|
+
ok: false;
|
|
82
|
+
error: E;
|
|
83
|
+
} : never : never);
|
|
84
|
+
type InferInput<P> = P extends ProcedureBuilder<infer I, any, any> ? {
|
|
85
|
+
[K in keyof I]: I[K] extends z.ZodType ? z.infer<I[K]> : never;
|
|
86
|
+
} : never;
|
|
87
|
+
type InferOutput<P> = P extends ProcedureBuilder<any, infer O, any> ? O extends z.ZodType ? z.infer<O> : never : never;
|
|
88
|
+
type InferErrors<P> = P extends ProcedureBuilder<any, any, infer E> ? E : never;
|
|
89
|
+
type ProcedureToFn<P> = P extends ProcedureBuilder<any, any, any> ? (input: InferInput<P>) => Promise<ResultFromErrors<InferOutput<P>, InferErrors<P>>> : never;
|
|
90
|
+
type RouterToClient<T> = {
|
|
91
|
+
[K in keyof T]: T[K] extends ProcedureBuilder<any, any, any> ? ProcedureToFn<T[K]> : RouterToClient<T[K]>;
|
|
92
|
+
};
|