@zenstackhq/server 3.1.0 → 3.2.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/api.cjs +376 -80
- package/dist/api.cjs.map +1 -1
- package/dist/api.d.cts +12 -1
- package/dist/api.d.ts +12 -1
- package/dist/api.js +368 -72
- package/dist/api.js.map +1 -1
- package/package.json +7 -7
package/dist/api.js
CHANGED
|
@@ -3,17 +3,156 @@ var __name = (target, value) => __defProp(target, "name", { value, configurable:
|
|
|
3
3
|
|
|
4
4
|
// src/api/rest/index.ts
|
|
5
5
|
import { clone, enumerate, lowerCaseFirst, paramCase } from "@zenstackhq/common-helpers";
|
|
6
|
-
import { ORMError, ORMErrorReason } from "@zenstackhq/orm";
|
|
6
|
+
import { ORMError as ORMError2, ORMErrorReason } from "@zenstackhq/orm";
|
|
7
7
|
import { Decimal as Decimal2 } from "decimal.js";
|
|
8
|
-
import
|
|
8
|
+
import SuperJSON3 from "superjson";
|
|
9
9
|
import tsjapi from "ts-japi";
|
|
10
10
|
import { match as match2 } from "ts-pattern";
|
|
11
11
|
import UrlPattern from "url-pattern";
|
|
12
|
+
import z2 from "zod";
|
|
13
|
+
import { fromError } from "zod-validation-error/v4";
|
|
14
|
+
|
|
15
|
+
// src/api/common/procedures.ts
|
|
16
|
+
import { ORMError } from "@zenstackhq/orm";
|
|
17
|
+
var PROCEDURE_ROUTE_PREFIXES = "$procs";
|
|
18
|
+
function getProcedureDef(schema, proc) {
|
|
19
|
+
const procs = schema.procedures ?? {};
|
|
20
|
+
if (!Object.prototype.hasOwnProperty.call(procs, proc)) {
|
|
21
|
+
return void 0;
|
|
22
|
+
}
|
|
23
|
+
return procs[proc];
|
|
24
|
+
}
|
|
25
|
+
__name(getProcedureDef, "getProcedureDef");
|
|
26
|
+
function mapProcedureArgs(procDef, payload) {
|
|
27
|
+
const params = Object.values(procDef.params ?? {});
|
|
28
|
+
if (params.length === 0) {
|
|
29
|
+
if (typeof payload === "undefined") {
|
|
30
|
+
return void 0;
|
|
31
|
+
}
|
|
32
|
+
if (payload && typeof payload === "object" && !Array.isArray(payload)) {
|
|
33
|
+
const envelope2 = payload;
|
|
34
|
+
const argsPayload2 = Object.prototype.hasOwnProperty.call(envelope2, "args") ? envelope2.args : void 0;
|
|
35
|
+
if (typeof argsPayload2 === "undefined") {
|
|
36
|
+
return payload;
|
|
37
|
+
}
|
|
38
|
+
if (argsPayload2 && typeof argsPayload2 === "object" && !Array.isArray(argsPayload2)) {
|
|
39
|
+
if (Object.keys(argsPayload2).length === 0) {
|
|
40
|
+
return payload;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
throw new Error("procedure does not accept arguments");
|
|
45
|
+
}
|
|
46
|
+
if (typeof payload === "undefined" && params.every((p) => p.optional)) {
|
|
47
|
+
return void 0;
|
|
48
|
+
}
|
|
49
|
+
if (typeof payload === "undefined") {
|
|
50
|
+
throw new Error("missing procedure arguments");
|
|
51
|
+
}
|
|
52
|
+
if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
|
|
53
|
+
throw new Error("procedure payload must be an object");
|
|
54
|
+
}
|
|
55
|
+
const envelope = payload;
|
|
56
|
+
const argsPayload = Object.prototype.hasOwnProperty.call(envelope, "args") ? envelope.args : void 0;
|
|
57
|
+
if (typeof argsPayload === "undefined") {
|
|
58
|
+
if (params.every((p) => p.optional)) {
|
|
59
|
+
return payload;
|
|
60
|
+
}
|
|
61
|
+
throw new Error("missing procedure arguments");
|
|
62
|
+
}
|
|
63
|
+
if (!argsPayload || typeof argsPayload !== "object" || Array.isArray(argsPayload)) {
|
|
64
|
+
throw new Error("procedure `args` must be an object");
|
|
65
|
+
}
|
|
66
|
+
const obj = argsPayload;
|
|
67
|
+
for (const key of Object.keys(obj)) {
|
|
68
|
+
if (!params.some((p) => p.name === key)) {
|
|
69
|
+
throw new Error(`unknown procedure argument: ${key}`);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
for (const p of params) {
|
|
73
|
+
if (!Object.prototype.hasOwnProperty.call(obj, p.name)) {
|
|
74
|
+
if (p.optional) {
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
throw new Error(`missing procedure argument: ${p.name}`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return payload;
|
|
81
|
+
}
|
|
82
|
+
__name(mapProcedureArgs, "mapProcedureArgs");
|
|
83
|
+
|
|
84
|
+
// src/api/common/schemas.ts
|
|
12
85
|
import z from "zod";
|
|
86
|
+
var loggerSchema = z.union([
|
|
87
|
+
z.enum([
|
|
88
|
+
"debug",
|
|
89
|
+
"info",
|
|
90
|
+
"warn",
|
|
91
|
+
"error"
|
|
92
|
+
]).array(),
|
|
93
|
+
z.function()
|
|
94
|
+
]);
|
|
95
|
+
|
|
96
|
+
// src/api/common/utils.ts
|
|
97
|
+
import SuperJSON from "superjson";
|
|
98
|
+
async function processSuperJsonRequestPayload(payload) {
|
|
99
|
+
if (!payload || typeof payload !== "object" || Array.isArray(payload) || !("meta" in payload)) {
|
|
100
|
+
return {
|
|
101
|
+
result: payload,
|
|
102
|
+
error: void 0
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
const { meta, ...rest } = payload;
|
|
106
|
+
if (meta?.serialization) {
|
|
107
|
+
try {
|
|
108
|
+
return {
|
|
109
|
+
result: SuperJSON.deserialize({
|
|
110
|
+
json: rest,
|
|
111
|
+
meta: meta.serialization
|
|
112
|
+
}),
|
|
113
|
+
error: void 0
|
|
114
|
+
};
|
|
115
|
+
} catch (err) {
|
|
116
|
+
return {
|
|
117
|
+
result: void 0,
|
|
118
|
+
error: `failed to deserialize request payload: ${err.message}`
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return {
|
|
123
|
+
result: rest,
|
|
124
|
+
error: void 0
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
__name(processSuperJsonRequestPayload, "processSuperJsonRequestPayload");
|
|
128
|
+
function unmarshalQ(value, meta) {
|
|
129
|
+
let parsedValue;
|
|
130
|
+
try {
|
|
131
|
+
parsedValue = JSON.parse(value);
|
|
132
|
+
} catch {
|
|
133
|
+
throw new Error('invalid "q" query parameter');
|
|
134
|
+
}
|
|
135
|
+
if (meta) {
|
|
136
|
+
let parsedMeta;
|
|
137
|
+
try {
|
|
138
|
+
parsedMeta = JSON.parse(meta);
|
|
139
|
+
} catch {
|
|
140
|
+
throw new Error('invalid "meta" query parameter');
|
|
141
|
+
}
|
|
142
|
+
if (parsedMeta.serialization) {
|
|
143
|
+
return SuperJSON.deserialize({
|
|
144
|
+
json: parsedValue,
|
|
145
|
+
meta: parsedMeta.serialization
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return parsedValue;
|
|
150
|
+
}
|
|
151
|
+
__name(unmarshalQ, "unmarshalQ");
|
|
13
152
|
|
|
14
153
|
// src/api/utils.ts
|
|
15
154
|
import { Decimal } from "decimal.js";
|
|
16
|
-
import
|
|
155
|
+
import SuperJSON2 from "superjson";
|
|
17
156
|
import { match } from "ts-pattern";
|
|
18
157
|
import { fromError as fromError3 } from "zod-validation-error/v3";
|
|
19
158
|
import { fromError as fromError4 } from "zod-validation-error/v4";
|
|
@@ -32,13 +171,13 @@ ${error}` : ""}`);
|
|
|
32
171
|
}
|
|
33
172
|
__name(log, "log");
|
|
34
173
|
function registerCustomSerializers() {
|
|
35
|
-
|
|
174
|
+
SuperJSON2.registerCustom({
|
|
36
175
|
isApplicable: /* @__PURE__ */ __name((v) => Decimal.isDecimal(v), "isApplicable"),
|
|
37
176
|
serialize: /* @__PURE__ */ __name((v) => v.toJSON(), "serialize"),
|
|
38
177
|
deserialize: /* @__PURE__ */ __name((v) => new Decimal(v), "deserialize")
|
|
39
178
|
}, "Decimal");
|
|
40
179
|
if (globalThis.Buffer) {
|
|
41
|
-
|
|
180
|
+
SuperJSON2.registerCustom({
|
|
42
181
|
isApplicable: /* @__PURE__ */ __name((v) => Buffer.isBuffer(v), "isApplicable"),
|
|
43
182
|
serialize: /* @__PURE__ */ __name((v) => v.toString("base64"), "serialize"),
|
|
44
183
|
deserialize: /* @__PURE__ */ __name((v) => Buffer.from(v, "base64"), "deserialize")
|
|
@@ -170,55 +309,55 @@ var RestApiHandler = class {
|
|
|
170
309
|
};
|
|
171
310
|
filterParamPattern = new RegExp(/^filter(?<match>(\[[^[\]]+\])+)$/);
|
|
172
311
|
// zod schema for payload of creating and updating a resource
|
|
173
|
-
createUpdatePayloadSchema =
|
|
174
|
-
data:
|
|
175
|
-
type:
|
|
176
|
-
attributes:
|
|
177
|
-
relationships:
|
|
178
|
-
data:
|
|
179
|
-
|
|
180
|
-
type:
|
|
181
|
-
id:
|
|
182
|
-
|
|
183
|
-
|
|
312
|
+
createUpdatePayloadSchema = z2.object({
|
|
313
|
+
data: z2.object({
|
|
314
|
+
type: z2.string(),
|
|
315
|
+
attributes: z2.object({}).passthrough().optional(),
|
|
316
|
+
relationships: z2.record(z2.string(), z2.object({
|
|
317
|
+
data: z2.union([
|
|
318
|
+
z2.object({
|
|
319
|
+
type: z2.string(),
|
|
320
|
+
id: z2.union([
|
|
321
|
+
z2.string(),
|
|
322
|
+
z2.number()
|
|
184
323
|
])
|
|
185
324
|
}),
|
|
186
|
-
|
|
187
|
-
type:
|
|
188
|
-
id:
|
|
189
|
-
|
|
190
|
-
|
|
325
|
+
z2.array(z2.object({
|
|
326
|
+
type: z2.string(),
|
|
327
|
+
id: z2.union([
|
|
328
|
+
z2.string(),
|
|
329
|
+
z2.number()
|
|
191
330
|
])
|
|
192
331
|
}))
|
|
193
332
|
])
|
|
194
333
|
})).optional()
|
|
195
334
|
}),
|
|
196
|
-
meta:
|
|
335
|
+
meta: z2.object({}).passthrough().optional()
|
|
197
336
|
}).strict();
|
|
198
337
|
// zod schema for updating a single relationship
|
|
199
|
-
updateSingleRelationSchema =
|
|
200
|
-
data:
|
|
201
|
-
type:
|
|
202
|
-
id:
|
|
203
|
-
|
|
204
|
-
|
|
338
|
+
updateSingleRelationSchema = z2.object({
|
|
339
|
+
data: z2.object({
|
|
340
|
+
type: z2.string(),
|
|
341
|
+
id: z2.union([
|
|
342
|
+
z2.string(),
|
|
343
|
+
z2.number()
|
|
205
344
|
])
|
|
206
345
|
}).nullable()
|
|
207
346
|
});
|
|
208
347
|
// zod schema for updating collection relationship
|
|
209
|
-
updateCollectionRelationSchema =
|
|
210
|
-
data:
|
|
211
|
-
type:
|
|
212
|
-
id:
|
|
213
|
-
|
|
214
|
-
|
|
348
|
+
updateCollectionRelationSchema = z2.object({
|
|
349
|
+
data: z2.array(z2.object({
|
|
350
|
+
type: z2.string(),
|
|
351
|
+
id: z2.union([
|
|
352
|
+
z2.string(),
|
|
353
|
+
z2.number()
|
|
215
354
|
])
|
|
216
355
|
}))
|
|
217
356
|
});
|
|
218
|
-
upsertMetaSchema =
|
|
219
|
-
meta:
|
|
220
|
-
operation:
|
|
221
|
-
matchFields:
|
|
357
|
+
upsertMetaSchema = z2.object({
|
|
358
|
+
meta: z2.object({
|
|
359
|
+
operation: z2.literal("upsert"),
|
|
360
|
+
matchFields: z2.array(z2.string()).min(1)
|
|
222
361
|
})
|
|
223
362
|
});
|
|
224
363
|
// all known types and their metadata
|
|
@@ -231,6 +370,7 @@ var RestApiHandler = class {
|
|
|
231
370
|
externalIdMapping;
|
|
232
371
|
constructor(options) {
|
|
233
372
|
this.options = options;
|
|
373
|
+
this.validateOptions(options);
|
|
234
374
|
this.idDivider = options.idDivider ?? DEFAULT_ID_DIVIDER;
|
|
235
375
|
const segmentCharset = options.urlSegmentCharset ?? "a-zA-Z0-9-_~ %";
|
|
236
376
|
this.modelNameMapping = options.modelNameMapping ?? {};
|
|
@@ -251,6 +391,25 @@ var RestApiHandler = class {
|
|
|
251
391
|
this.buildTypeMap();
|
|
252
392
|
this.buildSerializers();
|
|
253
393
|
}
|
|
394
|
+
validateOptions(options) {
|
|
395
|
+
const schema = z2.strictObject({
|
|
396
|
+
schema: z2.object(),
|
|
397
|
+
log: loggerSchema.optional(),
|
|
398
|
+
endpoint: z2.string().min(1),
|
|
399
|
+
pageSize: z2.union([
|
|
400
|
+
z2.number().int().positive(),
|
|
401
|
+
z2.literal(Infinity)
|
|
402
|
+
]).optional(),
|
|
403
|
+
idDivider: z2.string().min(1).optional(),
|
|
404
|
+
urlSegmentCharset: z2.string().min(1).optional(),
|
|
405
|
+
modelNameMapping: z2.record(z2.string(), z2.string()).optional(),
|
|
406
|
+
externalIdMapping: z2.record(z2.string(), z2.string()).optional()
|
|
407
|
+
});
|
|
408
|
+
const parseResult = schema.safeParse(options);
|
|
409
|
+
if (!parseResult.success) {
|
|
410
|
+
throw new Error(`Invalid options: ${fromError(parseResult.error)}`);
|
|
411
|
+
}
|
|
412
|
+
}
|
|
254
413
|
get schema() {
|
|
255
414
|
return this.options.schema;
|
|
256
415
|
}
|
|
@@ -311,6 +470,16 @@ var RestApiHandler = class {
|
|
|
311
470
|
path = "/" + path;
|
|
312
471
|
}
|
|
313
472
|
try {
|
|
473
|
+
if (path.startsWith("/$procs/")) {
|
|
474
|
+
const proc = path.split("/")[2];
|
|
475
|
+
return await this.processProcedureRequest({
|
|
476
|
+
client,
|
|
477
|
+
method,
|
|
478
|
+
proc,
|
|
479
|
+
query,
|
|
480
|
+
requestBody
|
|
481
|
+
});
|
|
482
|
+
}
|
|
314
483
|
switch (method) {
|
|
315
484
|
case "GET": {
|
|
316
485
|
let match4 = this.matchUrlPattern(path, "single");
|
|
@@ -384,7 +553,7 @@ var RestApiHandler = class {
|
|
|
384
553
|
} catch (err) {
|
|
385
554
|
if (err instanceof InvalidValueError) {
|
|
386
555
|
return this.makeError("invalidValue", err.message);
|
|
387
|
-
} else if (err instanceof
|
|
556
|
+
} else if (err instanceof ORMError2) {
|
|
388
557
|
return this.handleORMError(err);
|
|
389
558
|
} else {
|
|
390
559
|
return this.handleGenericError(err);
|
|
@@ -395,6 +564,71 @@ var RestApiHandler = class {
|
|
|
395
564
|
return this.makeError("unknownError", err instanceof Error ? `${err.message}
|
|
396
565
|
${err.stack}` : "Unknown error");
|
|
397
566
|
}
|
|
567
|
+
async processProcedureRequest({ client, method, proc, query, requestBody }) {
|
|
568
|
+
if (!proc) {
|
|
569
|
+
return this.makeProcBadInputErrorResponse("missing procedure name");
|
|
570
|
+
}
|
|
571
|
+
const procDef = getProcedureDef(this.schema, proc);
|
|
572
|
+
if (!procDef) {
|
|
573
|
+
return this.makeProcBadInputErrorResponse(`unknown procedure: ${proc}`);
|
|
574
|
+
}
|
|
575
|
+
const isMutation = !!procDef.mutation;
|
|
576
|
+
if (isMutation) {
|
|
577
|
+
if (method !== "POST") {
|
|
578
|
+
return this.makeProcBadInputErrorResponse("invalid request method, only POST is supported");
|
|
579
|
+
}
|
|
580
|
+
} else {
|
|
581
|
+
if (method !== "GET") {
|
|
582
|
+
return this.makeProcBadInputErrorResponse("invalid request method, only GET is supported");
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
const argsPayload = method === "POST" ? requestBody : query;
|
|
586
|
+
const { result: processedArgsPayload, error } = await processSuperJsonRequestPayload(argsPayload);
|
|
587
|
+
if (error) {
|
|
588
|
+
return this.makeProcBadInputErrorResponse(error);
|
|
589
|
+
}
|
|
590
|
+
let procInput;
|
|
591
|
+
try {
|
|
592
|
+
procInput = mapProcedureArgs(procDef, processedArgsPayload);
|
|
593
|
+
} catch (err) {
|
|
594
|
+
return this.makeProcBadInputErrorResponse(err instanceof Error ? err.message : "invalid procedure arguments");
|
|
595
|
+
}
|
|
596
|
+
try {
|
|
597
|
+
log(this.log, "debug", () => `handling "$procs.${proc}" request`);
|
|
598
|
+
const clientResult = await client.$procs?.[proc](procInput);
|
|
599
|
+
const toSerialize = this.toPlainObject(clientResult);
|
|
600
|
+
const { json, meta } = SuperJSON3.serialize(toSerialize);
|
|
601
|
+
const responseBody = {
|
|
602
|
+
data: json
|
|
603
|
+
};
|
|
604
|
+
if (meta) {
|
|
605
|
+
responseBody.meta = {
|
|
606
|
+
serialization: meta
|
|
607
|
+
};
|
|
608
|
+
}
|
|
609
|
+
return {
|
|
610
|
+
status: 200,
|
|
611
|
+
body: responseBody
|
|
612
|
+
};
|
|
613
|
+
} catch (err) {
|
|
614
|
+
log(this.log, "error", `error occurred when handling "$procs.${proc}" request`, err);
|
|
615
|
+
if (err instanceof ORMError2) {
|
|
616
|
+
throw err;
|
|
617
|
+
}
|
|
618
|
+
return this.makeProcGenericErrorResponse(err);
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
makeProcBadInputErrorResponse(message) {
|
|
622
|
+
const resp = this.makeError("invalidPayload", message, 400);
|
|
623
|
+
log(this.log, "debug", () => `sending error response: ${JSON.stringify(resp)}`);
|
|
624
|
+
return resp;
|
|
625
|
+
}
|
|
626
|
+
makeProcGenericErrorResponse(err) {
|
|
627
|
+
const message = err instanceof Error ? err.message : "unknown error";
|
|
628
|
+
const resp = this.makeError("unknownError", message, 500);
|
|
629
|
+
log(this.log, "debug", () => `sending error response: ${JSON.stringify(resp)}`);
|
|
630
|
+
return resp;
|
|
631
|
+
}
|
|
398
632
|
async processSingleRead(client, type, resourceId, query) {
|
|
399
633
|
const typeInfo = this.getModelInfo(type);
|
|
400
634
|
if (!typeInfo) {
|
|
@@ -716,7 +950,7 @@ ${err.stack}` : "Unknown error");
|
|
|
716
950
|
processRequestBody(requestBody) {
|
|
717
951
|
let body = requestBody;
|
|
718
952
|
if (body.meta?.serialization) {
|
|
719
|
-
body =
|
|
953
|
+
body = SuperJSON3.deserialize({
|
|
720
954
|
json: body,
|
|
721
955
|
meta: body.meta.serialization
|
|
722
956
|
});
|
|
@@ -1160,7 +1394,7 @@ ${err.stack}` : "Unknown error");
|
|
|
1160
1394
|
this.injectCompoundId(model, itemsWithId);
|
|
1161
1395
|
const serialized = await serializer.serialize(itemsWithId, options);
|
|
1162
1396
|
const plainResult = this.toPlainObject(serialized);
|
|
1163
|
-
const { json, meta } =
|
|
1397
|
+
const { json, meta } = SuperJSON3.serialize(plainResult);
|
|
1164
1398
|
const result = json;
|
|
1165
1399
|
if (meta) {
|
|
1166
1400
|
result.meta = {
|
|
@@ -1785,9 +2019,11 @@ ${err.stack}` : "Unknown error");
|
|
|
1785
2019
|
|
|
1786
2020
|
// src/api/rpc/index.ts
|
|
1787
2021
|
import { lowerCaseFirst as lowerCaseFirst2, safeJSONStringify } from "@zenstackhq/common-helpers";
|
|
1788
|
-
import { ORMError as
|
|
1789
|
-
import
|
|
2022
|
+
import { ORMError as ORMError3, ORMErrorReason as ORMErrorReason2 } from "@zenstackhq/orm";
|
|
2023
|
+
import SuperJSON4 from "superjson";
|
|
1790
2024
|
import { match as match3 } from "ts-pattern";
|
|
2025
|
+
import z3 from "zod";
|
|
2026
|
+
import { fromError as fromError2 } from "zod-validation-error/v4";
|
|
1791
2027
|
registerCustomSerializers();
|
|
1792
2028
|
var RPCApiHandler = class {
|
|
1793
2029
|
static {
|
|
@@ -1796,6 +2032,17 @@ var RPCApiHandler = class {
|
|
|
1796
2032
|
options;
|
|
1797
2033
|
constructor(options) {
|
|
1798
2034
|
this.options = options;
|
|
2035
|
+
this.validateOptions(options);
|
|
2036
|
+
}
|
|
2037
|
+
validateOptions(options) {
|
|
2038
|
+
const schema = z3.strictObject({
|
|
2039
|
+
schema: z3.object(),
|
|
2040
|
+
log: loggerSchema.optional()
|
|
2041
|
+
});
|
|
2042
|
+
const parseResult = schema.safeParse(options);
|
|
2043
|
+
if (!parseResult.success) {
|
|
2044
|
+
throw new Error(`Invalid options: ${fromError2(parseResult.error)}`);
|
|
2045
|
+
}
|
|
1799
2046
|
}
|
|
1800
2047
|
get schema() {
|
|
1801
2048
|
return this.options.schema;
|
|
@@ -1810,6 +2057,15 @@ var RPCApiHandler = class {
|
|
|
1810
2057
|
if (parts.length !== 0 || !op || !model) {
|
|
1811
2058
|
return this.makeBadInputErrorResponse("invalid request path");
|
|
1812
2059
|
}
|
|
2060
|
+
if (model === PROCEDURE_ROUTE_PREFIXES) {
|
|
2061
|
+
return this.handleProcedureRequest({
|
|
2062
|
+
client,
|
|
2063
|
+
method: method.toUpperCase(),
|
|
2064
|
+
proc: op,
|
|
2065
|
+
query,
|
|
2066
|
+
requestBody
|
|
2067
|
+
});
|
|
2068
|
+
}
|
|
1813
2069
|
model = lowerCaseFirst2(model);
|
|
1814
2070
|
method = method.toUpperCase();
|
|
1815
2071
|
let args;
|
|
@@ -1834,11 +2090,12 @@ var RPCApiHandler = class {
|
|
|
1834
2090
|
case "aggregate":
|
|
1835
2091
|
case "groupBy":
|
|
1836
2092
|
case "count":
|
|
2093
|
+
case "exists":
|
|
1837
2094
|
if (method !== "GET") {
|
|
1838
2095
|
return this.makeBadInputErrorResponse("invalid request method, only GET is supported");
|
|
1839
2096
|
}
|
|
1840
2097
|
try {
|
|
1841
|
-
args = query?.["q"] ?
|
|
2098
|
+
args = query?.["q"] ? unmarshalQ(query["q"], query["meta"]) : {};
|
|
1842
2099
|
} catch {
|
|
1843
2100
|
return this.makeBadInputErrorResponse('invalid "q" query parameter');
|
|
1844
2101
|
}
|
|
@@ -1860,7 +2117,7 @@ var RPCApiHandler = class {
|
|
|
1860
2117
|
return this.makeBadInputErrorResponse("invalid request method, only DELETE is supported");
|
|
1861
2118
|
}
|
|
1862
2119
|
try {
|
|
1863
|
-
args = query?.["q"] ?
|
|
2120
|
+
args = query?.["q"] ? unmarshalQ(query["q"], query["meta"]) : {};
|
|
1864
2121
|
} catch (err) {
|
|
1865
2122
|
return this.makeBadInputErrorResponse(err instanceof Error ? err.message : 'invalid "q" query parameter');
|
|
1866
2123
|
}
|
|
@@ -1882,7 +2139,7 @@ var RPCApiHandler = class {
|
|
|
1882
2139
|
data: clientResult
|
|
1883
2140
|
};
|
|
1884
2141
|
if (clientResult) {
|
|
1885
|
-
const { json, meta } =
|
|
2142
|
+
const { json, meta } = SuperJSON4.serialize(clientResult);
|
|
1886
2143
|
responseBody = {
|
|
1887
2144
|
data: json
|
|
1888
2145
|
};
|
|
@@ -1900,13 +2157,75 @@ var RPCApiHandler = class {
|
|
|
1900
2157
|
return response;
|
|
1901
2158
|
} catch (err) {
|
|
1902
2159
|
log(this.options.log, "error", `error occurred when handling "${model}.${op}" request`, err);
|
|
1903
|
-
if (err instanceof
|
|
2160
|
+
if (err instanceof ORMError3) {
|
|
1904
2161
|
return this.makeORMErrorResponse(err);
|
|
1905
2162
|
} else {
|
|
1906
2163
|
return this.makeGenericErrorResponse(err);
|
|
1907
2164
|
}
|
|
1908
2165
|
}
|
|
1909
2166
|
}
|
|
2167
|
+
async handleProcedureRequest({ client, method, proc, query, requestBody }) {
|
|
2168
|
+
if (!proc) {
|
|
2169
|
+
return this.makeBadInputErrorResponse("missing procedure name");
|
|
2170
|
+
}
|
|
2171
|
+
const procDef = getProcedureDef(this.options.schema, proc);
|
|
2172
|
+
if (!procDef) {
|
|
2173
|
+
return this.makeBadInputErrorResponse(`unknown procedure: ${proc}`);
|
|
2174
|
+
}
|
|
2175
|
+
const isMutation = !!procDef.mutation;
|
|
2176
|
+
if (isMutation) {
|
|
2177
|
+
if (method !== "POST") {
|
|
2178
|
+
return this.makeBadInputErrorResponse("invalid request method, only POST is supported");
|
|
2179
|
+
}
|
|
2180
|
+
} else {
|
|
2181
|
+
if (method !== "GET") {
|
|
2182
|
+
return this.makeBadInputErrorResponse("invalid request method, only GET is supported");
|
|
2183
|
+
}
|
|
2184
|
+
}
|
|
2185
|
+
let argsPayload = method === "POST" ? requestBody : void 0;
|
|
2186
|
+
if (method === "GET") {
|
|
2187
|
+
try {
|
|
2188
|
+
argsPayload = query?.["q"] ? unmarshalQ(query["q"], query["meta"]) : void 0;
|
|
2189
|
+
} catch (err) {
|
|
2190
|
+
return this.makeBadInputErrorResponse(err instanceof Error ? err.message : 'invalid "q" query parameter');
|
|
2191
|
+
}
|
|
2192
|
+
}
|
|
2193
|
+
const { result: processedArgsPayload, error } = await processSuperJsonRequestPayload(argsPayload);
|
|
2194
|
+
if (error) {
|
|
2195
|
+
return this.makeBadInputErrorResponse(error);
|
|
2196
|
+
}
|
|
2197
|
+
let procInput;
|
|
2198
|
+
try {
|
|
2199
|
+
procInput = mapProcedureArgs(procDef, processedArgsPayload);
|
|
2200
|
+
} catch (err) {
|
|
2201
|
+
return this.makeBadInputErrorResponse(err instanceof Error ? err.message : "invalid procedure arguments");
|
|
2202
|
+
}
|
|
2203
|
+
try {
|
|
2204
|
+
log(this.options.log, "debug", () => `handling "$procs.${proc}" request`);
|
|
2205
|
+
const clientResult = await client.$procs?.[proc](procInput);
|
|
2206
|
+
const { json, meta } = SuperJSON4.serialize(clientResult);
|
|
2207
|
+
const responseBody = {
|
|
2208
|
+
data: json
|
|
2209
|
+
};
|
|
2210
|
+
if (meta) {
|
|
2211
|
+
responseBody.meta = {
|
|
2212
|
+
serialization: meta
|
|
2213
|
+
};
|
|
2214
|
+
}
|
|
2215
|
+
const response = {
|
|
2216
|
+
status: 200,
|
|
2217
|
+
body: responseBody
|
|
2218
|
+
};
|
|
2219
|
+
log(this.options.log, "debug", () => `sending response for "$procs.${proc}" request: ${safeJSONStringify(response)}`);
|
|
2220
|
+
return response;
|
|
2221
|
+
} catch (err) {
|
|
2222
|
+
log(this.options.log, "error", `error occurred when handling "$procs.${proc}" request`, err);
|
|
2223
|
+
if (err instanceof ORMError3) {
|
|
2224
|
+
return this.makeORMErrorResponse(err);
|
|
2225
|
+
}
|
|
2226
|
+
return this.makeGenericErrorResponse(err);
|
|
2227
|
+
}
|
|
2228
|
+
}
|
|
1910
2229
|
isValidModel(client, model) {
|
|
1911
2230
|
return Object.keys(client.$schema.models).some((m) => lowerCaseFirst2(m) === lowerCaseFirst2(model));
|
|
1912
2231
|
}
|
|
@@ -1950,8 +2269,8 @@ var RPCApiHandler = class {
|
|
|
1950
2269
|
}).with(ORMErrorReason2.REJECTED_BY_POLICY, () => {
|
|
1951
2270
|
status = 403;
|
|
1952
2271
|
error.rejectedByPolicy = true;
|
|
1953
|
-
error.rejectReason = err.rejectedByPolicyReason;
|
|
1954
2272
|
error.model = err.model;
|
|
2273
|
+
error.rejectReason = err.rejectedByPolicyReason;
|
|
1955
2274
|
}).with(ORMErrorReason2.DB_QUERY_ERROR, () => {
|
|
1956
2275
|
status = 400;
|
|
1957
2276
|
error.dbErrorCode = err.dbErrorCode;
|
|
@@ -1970,7 +2289,7 @@ var RPCApiHandler = class {
|
|
|
1970
2289
|
const { meta, ...rest } = args ?? {};
|
|
1971
2290
|
if (meta?.serialization) {
|
|
1972
2291
|
try {
|
|
1973
|
-
args =
|
|
2292
|
+
args = SuperJSON4.deserialize({
|
|
1974
2293
|
json: rest,
|
|
1975
2294
|
meta: meta.serialization
|
|
1976
2295
|
});
|
|
@@ -1988,29 +2307,6 @@ var RPCApiHandler = class {
|
|
|
1988
2307
|
error: void 0
|
|
1989
2308
|
};
|
|
1990
2309
|
}
|
|
1991
|
-
unmarshalQ(value, meta) {
|
|
1992
|
-
let parsedValue;
|
|
1993
|
-
try {
|
|
1994
|
-
parsedValue = JSON.parse(value);
|
|
1995
|
-
} catch {
|
|
1996
|
-
throw new Error('invalid "q" query parameter');
|
|
1997
|
-
}
|
|
1998
|
-
if (meta) {
|
|
1999
|
-
let parsedMeta;
|
|
2000
|
-
try {
|
|
2001
|
-
parsedMeta = JSON.parse(meta);
|
|
2002
|
-
} catch {
|
|
2003
|
-
throw new Error('invalid "meta" query parameter');
|
|
2004
|
-
}
|
|
2005
|
-
if (parsedMeta.serialization) {
|
|
2006
|
-
return SuperJSON3.deserialize({
|
|
2007
|
-
json: parsedValue,
|
|
2008
|
-
meta: parsedMeta.serialization
|
|
2009
|
-
});
|
|
2010
|
-
}
|
|
2011
|
-
}
|
|
2012
|
-
return parsedValue;
|
|
2013
|
-
}
|
|
2014
2310
|
};
|
|
2015
2311
|
export {
|
|
2016
2312
|
RPCApiHandler,
|