@seam-rpc/server 4.3.21 → 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 +27 -6
- 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/index.js
CHANGED
|
@@ -1,14 +1,10 @@
|
|
|
1
|
-
import { extractFiles, injectFiles,
|
|
1
|
+
import { extractFiles, injectFiles, ApiError } from "@seam-rpc/core";
|
|
2
2
|
import EventEmitter from "events";
|
|
3
3
|
import express, { Router } from "express";
|
|
4
4
|
import FormData from "form-data";
|
|
5
5
|
import * as z from "zod";
|
|
6
|
-
export { RpcError };
|
|
6
|
+
export { ApiError as RpcError };
|
|
7
7
|
;
|
|
8
|
-
;
|
|
9
|
-
const errorHandler = (code, ...args) => {
|
|
10
|
-
return new RpcError(code, args[0]);
|
|
11
|
-
};
|
|
12
8
|
export async function createSeamSpace(app, fileHandler) {
|
|
13
9
|
if (!fileHandler) {
|
|
14
10
|
let multer;
|
|
@@ -37,202 +33,189 @@ function createProcedureBuilder(definition = {}) {
|
|
|
37
33
|
...definition,
|
|
38
34
|
output: schema,
|
|
39
35
|
}),
|
|
40
|
-
|
|
41
|
-
...definition,
|
|
42
|
-
errors
|
|
43
|
-
}),
|
|
44
|
-
handler: handler => createProcedureBuilder({
|
|
36
|
+
handler: (handler) => createProcedureBuilder({
|
|
45
37
|
...definition,
|
|
46
|
-
handler
|
|
38
|
+
handler,
|
|
47
39
|
}),
|
|
48
40
|
};
|
|
49
41
|
}
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
validatedInput,
|
|
81
|
-
output,
|
|
82
|
-
validatedOutput,
|
|
83
|
-
request: req,
|
|
84
|
-
response: res,
|
|
85
|
-
next,
|
|
86
|
-
});
|
|
87
|
-
res.sendStatus(400);
|
|
88
|
-
return;
|
|
89
|
-
}
|
|
90
|
-
// Call procedure
|
|
91
|
-
const ctx = {
|
|
42
|
+
function defineSeamRouter(seamSpace, path, seamRouterBuilder) {
|
|
43
|
+
const router = Router();
|
|
44
|
+
router.post("/:procName", async (req, res, next) => {
|
|
45
|
+
const procedure = seamRouterBuilder[req.params.procName]._def;
|
|
46
|
+
if (!procedure || !procedure.handler)
|
|
47
|
+
return res.sendStatus(404);
|
|
48
|
+
let input;
|
|
49
|
+
let validatedInput = undefined;
|
|
50
|
+
let output = undefined;
|
|
51
|
+
let validatedOutput;
|
|
52
|
+
// Middleware
|
|
53
|
+
try {
|
|
54
|
+
input = await runMiddleware(seamSpace, req, res);
|
|
55
|
+
}
|
|
56
|
+
catch (err) {
|
|
57
|
+
console.error(err);
|
|
58
|
+
return res.status(415).send(String(err));
|
|
59
|
+
}
|
|
60
|
+
// Validate input
|
|
61
|
+
try {
|
|
62
|
+
validatedInput = validateInput(input, procedure.input);
|
|
63
|
+
}
|
|
64
|
+
catch (err) {
|
|
65
|
+
seamSpace.emit("inputValidationError", err, {
|
|
66
|
+
routerPath: path,
|
|
67
|
+
procedureName: req.params.procName,
|
|
68
|
+
input,
|
|
69
|
+
validatedInput,
|
|
70
|
+
output,
|
|
71
|
+
validatedOutput,
|
|
92
72
|
request: req,
|
|
93
73
|
response: res,
|
|
94
74
|
next,
|
|
95
|
-
};
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
validatedOutput
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
75
|
+
});
|
|
76
|
+
res.sendStatus(400);
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
// Call procedure
|
|
80
|
+
const ctx = {
|
|
81
|
+
request: req,
|
|
82
|
+
response: res,
|
|
83
|
+
next,
|
|
84
|
+
};
|
|
85
|
+
function toResError(error) {
|
|
86
|
+
if (error instanceof ApiError)
|
|
87
|
+
return { isApiError: true, error: error.toJSON() };
|
|
88
|
+
else
|
|
89
|
+
return { isApiError: false };
|
|
90
|
+
}
|
|
91
|
+
try {
|
|
92
|
+
output = await procedure.handler({ input: validatedInput, ctx });
|
|
93
|
+
}
|
|
94
|
+
catch (error) {
|
|
95
|
+
seamSpace.emit("apiError", error, {
|
|
96
|
+
routerPath: path,
|
|
97
|
+
procedureName: req.params.procName,
|
|
98
|
+
input,
|
|
99
|
+
validatedInput,
|
|
100
|
+
output,
|
|
101
|
+
validatedOutput,
|
|
102
|
+
request: req,
|
|
103
|
+
response: res,
|
|
104
|
+
next: () => { res.status(400).json(toResError(error)); return next(); },
|
|
105
|
+
});
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
if (!output.ok) {
|
|
109
|
+
seamSpace.emit("apiError", output.error, {
|
|
110
|
+
routerPath: path,
|
|
111
|
+
procedureName: req.params.procName,
|
|
112
|
+
input,
|
|
113
|
+
validatedInput,
|
|
114
|
+
output,
|
|
115
|
+
validatedOutput,
|
|
116
|
+
request: req,
|
|
117
|
+
response: res,
|
|
118
|
+
next: () => { res.status(400).json(toResError(output.error)); return next(); },
|
|
119
|
+
});
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
// Validate output
|
|
123
|
+
try {
|
|
124
|
+
validatedOutput = validateOutput(output, z.object({ ok: z.literal(true), data: procedure.output }).or(z.object({ ok: z.literal(false), error: z.any() })));
|
|
125
|
+
}
|
|
126
|
+
catch (err) {
|
|
127
|
+
seamSpace.emit("outputValidationError", err, {
|
|
128
|
+
routerPath: path,
|
|
129
|
+
procedureName: req.params.procName,
|
|
130
|
+
input,
|
|
131
|
+
validatedInput,
|
|
132
|
+
output,
|
|
133
|
+
validatedOutput,
|
|
134
|
+
request: req,
|
|
135
|
+
response: res,
|
|
136
|
+
next,
|
|
137
|
+
});
|
|
138
|
+
res.sendStatus(500);
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
try {
|
|
142
|
+
const { json, files, paths } = extractFiles({ result: validatedOutput });
|
|
143
|
+
// Does not include file(s)
|
|
144
|
+
if (files.length === 0) {
|
|
145
|
+
res.json(json);
|
|
150
146
|
return;
|
|
151
147
|
}
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
form.append("paths", JSON.stringify(paths));
|
|
163
|
-
for (let i = 0; i < files.length; i++) {
|
|
164
|
-
const file = files[i];
|
|
165
|
-
const buffer = Buffer.from(await file.arrayBuffer());
|
|
166
|
-
form.append(`file-${i}`, buffer, {
|
|
167
|
-
filename: file.name || `file-${i}`,
|
|
168
|
-
contentType: file.type || "application/octet-stream",
|
|
169
|
-
});
|
|
170
|
-
}
|
|
171
|
-
res.writeHead(200, form.getHeaders());
|
|
172
|
-
form.pipe(res);
|
|
173
|
-
}
|
|
174
|
-
catch (error) {
|
|
175
|
-
seamSpace.emit("internalError", error, {
|
|
176
|
-
routerPath: path,
|
|
177
|
-
procedureName: req.params.procName,
|
|
178
|
-
input,
|
|
179
|
-
validatedInput,
|
|
180
|
-
output,
|
|
181
|
-
validatedOutput,
|
|
182
|
-
request: req,
|
|
183
|
-
response: res,
|
|
184
|
-
next: next,
|
|
148
|
+
// Includes file(s)
|
|
149
|
+
const form = new FormData();
|
|
150
|
+
form.append("json", JSON.stringify(json));
|
|
151
|
+
form.append("paths", JSON.stringify(paths));
|
|
152
|
+
for (let i = 0; i < files.length; i++) {
|
|
153
|
+
const file = files[i];
|
|
154
|
+
const buffer = Buffer.from(await file.arrayBuffer());
|
|
155
|
+
form.append(`file-${i}`, buffer, {
|
|
156
|
+
filename: file.name || `file-${i}`,
|
|
157
|
+
contentType: file.type || "application/octet-stream",
|
|
185
158
|
});
|
|
186
|
-
res.sendStatus(500); //.send({ error: String(error) });
|
|
187
159
|
}
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
}
|
|
191
|
-
async runMiddleware(req, res) {
|
|
192
|
-
const contentType = req.headers["content-type"] || "";
|
|
193
|
-
const runMiddleware = (middleware) => new Promise((resolve, reject) => middleware(req, res, err => (err ? reject(err) : resolve())));
|
|
194
|
-
if (contentType.startsWith("application/json")) {
|
|
195
|
-
await runMiddleware(this.seamSpace.jsonParser);
|
|
196
|
-
}
|
|
197
|
-
else if (contentType.startsWith("multipart/form-data")) {
|
|
198
|
-
await runMiddleware(this.seamSpace.fileHandler);
|
|
160
|
+
res.writeHead(200, form.getHeaders());
|
|
161
|
+
form.pipe(res);
|
|
199
162
|
}
|
|
200
|
-
|
|
201
|
-
|
|
163
|
+
catch (error) {
|
|
164
|
+
seamSpace.emit("internalError", error, {
|
|
165
|
+
routerPath: path,
|
|
166
|
+
procedureName: req.params.procName,
|
|
167
|
+
input,
|
|
168
|
+
validatedInput,
|
|
169
|
+
output,
|
|
170
|
+
validatedOutput,
|
|
171
|
+
request: req,
|
|
172
|
+
response: res,
|
|
173
|
+
next: next,
|
|
174
|
+
});
|
|
175
|
+
res.sendStatus(500);
|
|
202
176
|
}
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
}));
|
|
212
|
-
injectFiles(input, files);
|
|
213
|
-
return input;
|
|
214
|
-
}
|
|
215
|
-
validateInput(input, inputSchema) {
|
|
216
|
-
return this.validateData(input, z.object(inputSchema));
|
|
177
|
+
});
|
|
178
|
+
seamSpace.app.use(`/${path}`, router);
|
|
179
|
+
}
|
|
180
|
+
async function runMiddleware(seamSpace, req, res) {
|
|
181
|
+
const contentType = req.headers["content-type"] || "";
|
|
182
|
+
const runMiddleware = (middleware) => new Promise((resolve, reject) => middleware(req, res, err => (err ? reject(err) : resolve())));
|
|
183
|
+
if (contentType.startsWith("application/json")) {
|
|
184
|
+
await runMiddleware(seamSpace.jsonParser);
|
|
217
185
|
}
|
|
218
|
-
|
|
219
|
-
|
|
186
|
+
else if (contentType.startsWith("multipart/form-data")) {
|
|
187
|
+
await runMiddleware(seamSpace.fileHandler);
|
|
220
188
|
}
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
if (data)
|
|
224
|
-
throw new Error("Received data, but no data expected.");
|
|
225
|
-
return null;
|
|
226
|
-
}
|
|
227
|
-
if (!data)
|
|
228
|
-
throw new Error("No data was received, but data was expected.");
|
|
229
|
-
return schema.parse(data);
|
|
189
|
+
else {
|
|
190
|
+
throw new Error("Unsupported content type.");
|
|
230
191
|
}
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
192
|
+
if (contentType.startsWith("application/json"))
|
|
193
|
+
return req.body;
|
|
194
|
+
// multipart/form-data (already checked before)
|
|
195
|
+
let input = JSON.parse(req.body.json);
|
|
196
|
+
const paths = JSON.parse(req.body.paths);
|
|
197
|
+
const files = (req.files ?? []).map((file, index) => ({
|
|
198
|
+
path: paths[index],
|
|
199
|
+
file: new File([file.buffer], file.originalname, { type: file.mimetype }),
|
|
200
|
+
}));
|
|
201
|
+
injectFiles(input, files);
|
|
202
|
+
return input;
|
|
203
|
+
}
|
|
204
|
+
function validateInput(input, inputSchema) {
|
|
205
|
+
return validateData(input, z.object(inputSchema));
|
|
206
|
+
}
|
|
207
|
+
function validateOutput(output, procOutput) {
|
|
208
|
+
return validateData(output, procOutput);
|
|
209
|
+
}
|
|
210
|
+
function validateData(data, schema) {
|
|
211
|
+
if (!schema) {
|
|
212
|
+
if (data)
|
|
213
|
+
throw new Error("Received data, but no data expected.");
|
|
214
|
+
return null;
|
|
235
215
|
}
|
|
216
|
+
if (!data)
|
|
217
|
+
throw new Error("No data was received, but data was expected.");
|
|
218
|
+
return schema.parse(data);
|
|
236
219
|
}
|
|
237
220
|
export class SeamSpace extends EventEmitter {
|
|
238
221
|
constructor(_app, _fileHandler) {
|
|
@@ -241,10 +224,9 @@ export class SeamSpace extends EventEmitter {
|
|
|
241
224
|
this._fileHandler = _fileHandler;
|
|
242
225
|
this._jsonParser = express.json();
|
|
243
226
|
}
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
return router;
|
|
227
|
+
addRouters(routers) {
|
|
228
|
+
Object.entries(routers).forEach(e => defineSeamRouter(this, e[0], e[1]));
|
|
229
|
+
return {};
|
|
248
230
|
}
|
|
249
231
|
get app() { return this._app; }
|
|
250
232
|
get jsonParser() { return this._jsonParser; }
|
package/dist/router.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Router } from "express";
|
|
2
|
+
import { ProcedureBuilderList, SeamSpace } from "./index.js";
|
|
3
|
+
export declare class SeamRouter {
|
|
4
|
+
private seamSpace;
|
|
5
|
+
private _procedures;
|
|
6
|
+
private _router;
|
|
7
|
+
constructor(seamSpace: SeamSpace, name: string);
|
|
8
|
+
get router(): Router;
|
|
9
|
+
private runMiddleware;
|
|
10
|
+
private validateInput;
|
|
11
|
+
private validateOutput;
|
|
12
|
+
private validateData;
|
|
13
|
+
addProcedures(procedures: ProcedureBuilderList): void;
|
|
14
|
+
}
|
package/dist/router.js
ADDED
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import { extractFiles, injectFiles, ApiError } from "@seam-rpc/core";
|
|
2
|
+
import { Router } from "express";
|
|
3
|
+
import FormData from "form-data";
|
|
4
|
+
import * as z from "zod";
|
|
5
|
+
export class SeamRouter {
|
|
6
|
+
constructor(seamSpace, name) {
|
|
7
|
+
this.seamSpace = seamSpace;
|
|
8
|
+
this._procedures = {};
|
|
9
|
+
this._router = Router();
|
|
10
|
+
this._router.post("/:procName", async (req, res, next) => {
|
|
11
|
+
const procedure = this._procedures[req.params.procName];
|
|
12
|
+
if (!procedure || !procedure.handler)
|
|
13
|
+
return res.sendStatus(404);
|
|
14
|
+
let input;
|
|
15
|
+
let validatedInput = undefined;
|
|
16
|
+
let output = undefined;
|
|
17
|
+
let validatedOutput;
|
|
18
|
+
// Middleware
|
|
19
|
+
try {
|
|
20
|
+
input = await this.runMiddleware(req, res);
|
|
21
|
+
}
|
|
22
|
+
catch (err) {
|
|
23
|
+
console.error(err);
|
|
24
|
+
return res.status(415).send(String(err));
|
|
25
|
+
}
|
|
26
|
+
// Validate input
|
|
27
|
+
try {
|
|
28
|
+
validatedInput = this.validateInput(input, procedure.input);
|
|
29
|
+
}
|
|
30
|
+
catch (err) {
|
|
31
|
+
seamSpace.emit("inputValidationError", err, {
|
|
32
|
+
routerName: name,
|
|
33
|
+
procedureName: req.params.procName,
|
|
34
|
+
input,
|
|
35
|
+
validatedInput,
|
|
36
|
+
output,
|
|
37
|
+
validatedOutput,
|
|
38
|
+
request: req,
|
|
39
|
+
response: res,
|
|
40
|
+
next,
|
|
41
|
+
});
|
|
42
|
+
res.sendStatus(400);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
// Call procedure
|
|
46
|
+
const ctx = {
|
|
47
|
+
request: req,
|
|
48
|
+
response: res,
|
|
49
|
+
next,
|
|
50
|
+
};
|
|
51
|
+
function toResError(error) {
|
|
52
|
+
if (error instanceof ApiError)
|
|
53
|
+
return { isApiError: true, error: error.toJSON() };
|
|
54
|
+
else
|
|
55
|
+
return { isApiError: false };
|
|
56
|
+
}
|
|
57
|
+
try {
|
|
58
|
+
output = await procedure.handler({ input: validatedInput, ctx, error: errorHandler });
|
|
59
|
+
}
|
|
60
|
+
catch (error) {
|
|
61
|
+
seamSpace.emit("apiError", error, {
|
|
62
|
+
routerName: name,
|
|
63
|
+
procedureName: req.params.procName,
|
|
64
|
+
input,
|
|
65
|
+
validatedInput,
|
|
66
|
+
output,
|
|
67
|
+
validatedOutput,
|
|
68
|
+
request: req,
|
|
69
|
+
response: res,
|
|
70
|
+
next: () => { res.status(400).json(toResError(error)); return next(); },
|
|
71
|
+
});
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
if (!output.ok) {
|
|
75
|
+
seamSpace.emit("apiError", output.error, {
|
|
76
|
+
routerName: name,
|
|
77
|
+
procedureName: req.params.procName,
|
|
78
|
+
input,
|
|
79
|
+
validatedInput,
|
|
80
|
+
output,
|
|
81
|
+
validatedOutput,
|
|
82
|
+
request: req,
|
|
83
|
+
response: res,
|
|
84
|
+
next: () => { res.status(400).json(toResError(output.error)); return next(); },
|
|
85
|
+
});
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
// Validate output
|
|
89
|
+
try {
|
|
90
|
+
validatedOutput = this.validateOutput(output, z.object({ ok: z.literal(true), data: procedure.output }).or(z.object({ ok: z.literal(false), error: z.any() })));
|
|
91
|
+
}
|
|
92
|
+
catch (err) {
|
|
93
|
+
seamSpace.emit("outputValidationError", err, {
|
|
94
|
+
routerName: name,
|
|
95
|
+
procedureName: req.params.procName,
|
|
96
|
+
input,
|
|
97
|
+
validatedInput,
|
|
98
|
+
output,
|
|
99
|
+
validatedOutput,
|
|
100
|
+
request: req,
|
|
101
|
+
response: res,
|
|
102
|
+
next,
|
|
103
|
+
});
|
|
104
|
+
res.sendStatus(500);
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
try {
|
|
108
|
+
const { json, files, paths } = extractFiles({ result: validatedOutput });
|
|
109
|
+
// Does not include file(s)
|
|
110
|
+
if (files.length === 0) {
|
|
111
|
+
res.json(json);
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
// Includes file(s)
|
|
115
|
+
const form = new FormData();
|
|
116
|
+
form.append("json", JSON.stringify(json));
|
|
117
|
+
form.append("paths", JSON.stringify(paths));
|
|
118
|
+
for (let i = 0; i < files.length; i++) {
|
|
119
|
+
const file = files[i];
|
|
120
|
+
const buffer = Buffer.from(await file.arrayBuffer());
|
|
121
|
+
form.append(`file-${i}`, buffer, {
|
|
122
|
+
filename: file.name || `file-${i}`,
|
|
123
|
+
contentType: file.type || "application/octet-stream",
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
res.writeHead(200, form.getHeaders());
|
|
127
|
+
form.pipe(res);
|
|
128
|
+
}
|
|
129
|
+
catch (error) {
|
|
130
|
+
seamSpace.emit("internalError", error, {
|
|
131
|
+
routerName: name,
|
|
132
|
+
procedureName: req.params.procName,
|
|
133
|
+
input,
|
|
134
|
+
validatedInput,
|
|
135
|
+
output,
|
|
136
|
+
validatedOutput,
|
|
137
|
+
request: req,
|
|
138
|
+
response: res,
|
|
139
|
+
next: next,
|
|
140
|
+
});
|
|
141
|
+
res.sendStatus(500); //.send({ error: String(error) });
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
seamSpace.app.use(name, this._router);
|
|
145
|
+
}
|
|
146
|
+
get router() {
|
|
147
|
+
return this._router;
|
|
148
|
+
}
|
|
149
|
+
async runMiddleware(req, res) {
|
|
150
|
+
const contentType = req.headers["content-type"] || "";
|
|
151
|
+
const runMiddleware = (middleware) => new Promise((resolve, reject) => middleware(req, res, err => (err ? reject(err) : resolve())));
|
|
152
|
+
if (contentType.startsWith("application/json")) {
|
|
153
|
+
await runMiddleware(this.seamSpace.jsonParser);
|
|
154
|
+
}
|
|
155
|
+
else if (contentType.startsWith("multipart/form-data")) {
|
|
156
|
+
await runMiddleware(this.seamSpace.fileHandler);
|
|
157
|
+
}
|
|
158
|
+
else {
|
|
159
|
+
throw new Error("Unsupported content type.");
|
|
160
|
+
}
|
|
161
|
+
if (contentType.startsWith("application/json"))
|
|
162
|
+
return req.body;
|
|
163
|
+
// multipart/form-data (already checked before)
|
|
164
|
+
let input = JSON.parse(req.body.json);
|
|
165
|
+
const paths = JSON.parse(req.body.paths);
|
|
166
|
+
const files = (req.files ?? []).map((file, index) => ({
|
|
167
|
+
path: paths[index],
|
|
168
|
+
file: new File([file.buffer], file.originalname, { type: file.mimetype }),
|
|
169
|
+
}));
|
|
170
|
+
injectFiles(input, files);
|
|
171
|
+
return input;
|
|
172
|
+
}
|
|
173
|
+
validateInput(input, inputSchema) {
|
|
174
|
+
return this.validateData(input, z.object(inputSchema));
|
|
175
|
+
}
|
|
176
|
+
validateOutput(output, procOutput) {
|
|
177
|
+
return this.validateData(output, procOutput);
|
|
178
|
+
}
|
|
179
|
+
validateData(data, schema) {
|
|
180
|
+
if (!schema) {
|
|
181
|
+
if (data)
|
|
182
|
+
throw new Error("Received data, but no data expected.");
|
|
183
|
+
return null;
|
|
184
|
+
}
|
|
185
|
+
if (!data)
|
|
186
|
+
throw new Error("No data was received, but data was expected.");
|
|
187
|
+
return schema.parse(data);
|
|
188
|
+
}
|
|
189
|
+
addProcedures(procedures) {
|
|
190
|
+
for (const proc in procedures) {
|
|
191
|
+
this._procedures[proc] = procedures[proc]._def;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
const errorHandler = (code, ...args) => {
|
|
196
|
+
return new ApiError(code, args[0]);
|
|
197
|
+
};
|