@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.cjs
CHANGED
|
@@ -38,17 +38,156 @@ module.exports = __toCommonJS(api_exports);
|
|
|
38
38
|
|
|
39
39
|
// src/api/rest/index.ts
|
|
40
40
|
var import_common_helpers = require("@zenstackhq/common-helpers");
|
|
41
|
-
var
|
|
41
|
+
var import_orm2 = require("@zenstackhq/orm");
|
|
42
42
|
var import_decimal2 = require("decimal.js");
|
|
43
|
-
var
|
|
43
|
+
var import_superjson3 = __toESM(require("superjson"), 1);
|
|
44
44
|
var import_ts_japi = __toESM(require("ts-japi"), 1);
|
|
45
45
|
var import_ts_pattern2 = require("ts-pattern");
|
|
46
46
|
var import_url_pattern = __toESM(require("url-pattern"), 1);
|
|
47
|
+
var import_zod2 = __toESM(require("zod"), 1);
|
|
48
|
+
var import_v42 = require("zod-validation-error/v4");
|
|
49
|
+
|
|
50
|
+
// src/api/common/procedures.ts
|
|
51
|
+
var import_orm = require("@zenstackhq/orm");
|
|
52
|
+
var PROCEDURE_ROUTE_PREFIXES = "$procs";
|
|
53
|
+
function getProcedureDef(schema, proc) {
|
|
54
|
+
const procs = schema.procedures ?? {};
|
|
55
|
+
if (!Object.prototype.hasOwnProperty.call(procs, proc)) {
|
|
56
|
+
return void 0;
|
|
57
|
+
}
|
|
58
|
+
return procs[proc];
|
|
59
|
+
}
|
|
60
|
+
__name(getProcedureDef, "getProcedureDef");
|
|
61
|
+
function mapProcedureArgs(procDef, payload) {
|
|
62
|
+
const params = Object.values(procDef.params ?? {});
|
|
63
|
+
if (params.length === 0) {
|
|
64
|
+
if (typeof payload === "undefined") {
|
|
65
|
+
return void 0;
|
|
66
|
+
}
|
|
67
|
+
if (payload && typeof payload === "object" && !Array.isArray(payload)) {
|
|
68
|
+
const envelope2 = payload;
|
|
69
|
+
const argsPayload2 = Object.prototype.hasOwnProperty.call(envelope2, "args") ? envelope2.args : void 0;
|
|
70
|
+
if (typeof argsPayload2 === "undefined") {
|
|
71
|
+
return payload;
|
|
72
|
+
}
|
|
73
|
+
if (argsPayload2 && typeof argsPayload2 === "object" && !Array.isArray(argsPayload2)) {
|
|
74
|
+
if (Object.keys(argsPayload2).length === 0) {
|
|
75
|
+
return payload;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
throw new Error("procedure does not accept arguments");
|
|
80
|
+
}
|
|
81
|
+
if (typeof payload === "undefined" && params.every((p) => p.optional)) {
|
|
82
|
+
return void 0;
|
|
83
|
+
}
|
|
84
|
+
if (typeof payload === "undefined") {
|
|
85
|
+
throw new Error("missing procedure arguments");
|
|
86
|
+
}
|
|
87
|
+
if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
|
|
88
|
+
throw new Error("procedure payload must be an object");
|
|
89
|
+
}
|
|
90
|
+
const envelope = payload;
|
|
91
|
+
const argsPayload = Object.prototype.hasOwnProperty.call(envelope, "args") ? envelope.args : void 0;
|
|
92
|
+
if (typeof argsPayload === "undefined") {
|
|
93
|
+
if (params.every((p) => p.optional)) {
|
|
94
|
+
return payload;
|
|
95
|
+
}
|
|
96
|
+
throw new Error("missing procedure arguments");
|
|
97
|
+
}
|
|
98
|
+
if (!argsPayload || typeof argsPayload !== "object" || Array.isArray(argsPayload)) {
|
|
99
|
+
throw new Error("procedure `args` must be an object");
|
|
100
|
+
}
|
|
101
|
+
const obj = argsPayload;
|
|
102
|
+
for (const key of Object.keys(obj)) {
|
|
103
|
+
if (!params.some((p) => p.name === key)) {
|
|
104
|
+
throw new Error(`unknown procedure argument: ${key}`);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
for (const p of params) {
|
|
108
|
+
if (!Object.prototype.hasOwnProperty.call(obj, p.name)) {
|
|
109
|
+
if (p.optional) {
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
throw new Error(`missing procedure argument: ${p.name}`);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return payload;
|
|
116
|
+
}
|
|
117
|
+
__name(mapProcedureArgs, "mapProcedureArgs");
|
|
118
|
+
|
|
119
|
+
// src/api/common/schemas.ts
|
|
47
120
|
var import_zod = __toESM(require("zod"), 1);
|
|
121
|
+
var loggerSchema = import_zod.default.union([
|
|
122
|
+
import_zod.default.enum([
|
|
123
|
+
"debug",
|
|
124
|
+
"info",
|
|
125
|
+
"warn",
|
|
126
|
+
"error"
|
|
127
|
+
]).array(),
|
|
128
|
+
import_zod.default.function()
|
|
129
|
+
]);
|
|
130
|
+
|
|
131
|
+
// src/api/common/utils.ts
|
|
132
|
+
var import_superjson = __toESM(require("superjson"), 1);
|
|
133
|
+
async function processSuperJsonRequestPayload(payload) {
|
|
134
|
+
if (!payload || typeof payload !== "object" || Array.isArray(payload) || !("meta" in payload)) {
|
|
135
|
+
return {
|
|
136
|
+
result: payload,
|
|
137
|
+
error: void 0
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
const { meta, ...rest } = payload;
|
|
141
|
+
if (meta?.serialization) {
|
|
142
|
+
try {
|
|
143
|
+
return {
|
|
144
|
+
result: import_superjson.default.deserialize({
|
|
145
|
+
json: rest,
|
|
146
|
+
meta: meta.serialization
|
|
147
|
+
}),
|
|
148
|
+
error: void 0
|
|
149
|
+
};
|
|
150
|
+
} catch (err) {
|
|
151
|
+
return {
|
|
152
|
+
result: void 0,
|
|
153
|
+
error: `failed to deserialize request payload: ${err.message}`
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return {
|
|
158
|
+
result: rest,
|
|
159
|
+
error: void 0
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
__name(processSuperJsonRequestPayload, "processSuperJsonRequestPayload");
|
|
163
|
+
function unmarshalQ(value, meta) {
|
|
164
|
+
let parsedValue;
|
|
165
|
+
try {
|
|
166
|
+
parsedValue = JSON.parse(value);
|
|
167
|
+
} catch {
|
|
168
|
+
throw new Error('invalid "q" query parameter');
|
|
169
|
+
}
|
|
170
|
+
if (meta) {
|
|
171
|
+
let parsedMeta;
|
|
172
|
+
try {
|
|
173
|
+
parsedMeta = JSON.parse(meta);
|
|
174
|
+
} catch {
|
|
175
|
+
throw new Error('invalid "meta" query parameter');
|
|
176
|
+
}
|
|
177
|
+
if (parsedMeta.serialization) {
|
|
178
|
+
return import_superjson.default.deserialize({
|
|
179
|
+
json: parsedValue,
|
|
180
|
+
meta: parsedMeta.serialization
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
return parsedValue;
|
|
185
|
+
}
|
|
186
|
+
__name(unmarshalQ, "unmarshalQ");
|
|
48
187
|
|
|
49
188
|
// src/api/utils.ts
|
|
50
189
|
var import_decimal = require("decimal.js");
|
|
51
|
-
var
|
|
190
|
+
var import_superjson2 = __toESM(require("superjson"), 1);
|
|
52
191
|
var import_ts_pattern = require("ts-pattern");
|
|
53
192
|
var import_v3 = require("zod-validation-error/v3");
|
|
54
193
|
var import_v4 = require("zod-validation-error/v4");
|
|
@@ -67,13 +206,13 @@ ${error}` : ""}`);
|
|
|
67
206
|
}
|
|
68
207
|
__name(log, "log");
|
|
69
208
|
function registerCustomSerializers() {
|
|
70
|
-
|
|
209
|
+
import_superjson2.default.registerCustom({
|
|
71
210
|
isApplicable: /* @__PURE__ */ __name((v) => import_decimal.Decimal.isDecimal(v), "isApplicable"),
|
|
72
211
|
serialize: /* @__PURE__ */ __name((v) => v.toJSON(), "serialize"),
|
|
73
212
|
deserialize: /* @__PURE__ */ __name((v) => new import_decimal.Decimal(v), "deserialize")
|
|
74
213
|
}, "Decimal");
|
|
75
214
|
if (globalThis.Buffer) {
|
|
76
|
-
|
|
215
|
+
import_superjson2.default.registerCustom({
|
|
77
216
|
isApplicable: /* @__PURE__ */ __name((v) => Buffer.isBuffer(v), "isApplicable"),
|
|
78
217
|
serialize: /* @__PURE__ */ __name((v) => v.toString("base64"), "serialize"),
|
|
79
218
|
deserialize: /* @__PURE__ */ __name((v) => Buffer.from(v, "base64"), "deserialize")
|
|
@@ -205,55 +344,55 @@ var RestApiHandler = class {
|
|
|
205
344
|
};
|
|
206
345
|
filterParamPattern = new RegExp(/^filter(?<match>(\[[^[\]]+\])+)$/);
|
|
207
346
|
// zod schema for payload of creating and updating a resource
|
|
208
|
-
createUpdatePayloadSchema =
|
|
209
|
-
data:
|
|
210
|
-
type:
|
|
211
|
-
attributes:
|
|
212
|
-
relationships:
|
|
213
|
-
data:
|
|
214
|
-
|
|
215
|
-
type:
|
|
216
|
-
id:
|
|
217
|
-
|
|
218
|
-
|
|
347
|
+
createUpdatePayloadSchema = import_zod2.default.object({
|
|
348
|
+
data: import_zod2.default.object({
|
|
349
|
+
type: import_zod2.default.string(),
|
|
350
|
+
attributes: import_zod2.default.object({}).passthrough().optional(),
|
|
351
|
+
relationships: import_zod2.default.record(import_zod2.default.string(), import_zod2.default.object({
|
|
352
|
+
data: import_zod2.default.union([
|
|
353
|
+
import_zod2.default.object({
|
|
354
|
+
type: import_zod2.default.string(),
|
|
355
|
+
id: import_zod2.default.union([
|
|
356
|
+
import_zod2.default.string(),
|
|
357
|
+
import_zod2.default.number()
|
|
219
358
|
])
|
|
220
359
|
}),
|
|
221
|
-
|
|
222
|
-
type:
|
|
223
|
-
id:
|
|
224
|
-
|
|
225
|
-
|
|
360
|
+
import_zod2.default.array(import_zod2.default.object({
|
|
361
|
+
type: import_zod2.default.string(),
|
|
362
|
+
id: import_zod2.default.union([
|
|
363
|
+
import_zod2.default.string(),
|
|
364
|
+
import_zod2.default.number()
|
|
226
365
|
])
|
|
227
366
|
}))
|
|
228
367
|
])
|
|
229
368
|
})).optional()
|
|
230
369
|
}),
|
|
231
|
-
meta:
|
|
370
|
+
meta: import_zod2.default.object({}).passthrough().optional()
|
|
232
371
|
}).strict();
|
|
233
372
|
// zod schema for updating a single relationship
|
|
234
|
-
updateSingleRelationSchema =
|
|
235
|
-
data:
|
|
236
|
-
type:
|
|
237
|
-
id:
|
|
238
|
-
|
|
239
|
-
|
|
373
|
+
updateSingleRelationSchema = import_zod2.default.object({
|
|
374
|
+
data: import_zod2.default.object({
|
|
375
|
+
type: import_zod2.default.string(),
|
|
376
|
+
id: import_zod2.default.union([
|
|
377
|
+
import_zod2.default.string(),
|
|
378
|
+
import_zod2.default.number()
|
|
240
379
|
])
|
|
241
380
|
}).nullable()
|
|
242
381
|
});
|
|
243
382
|
// zod schema for updating collection relationship
|
|
244
|
-
updateCollectionRelationSchema =
|
|
245
|
-
data:
|
|
246
|
-
type:
|
|
247
|
-
id:
|
|
248
|
-
|
|
249
|
-
|
|
383
|
+
updateCollectionRelationSchema = import_zod2.default.object({
|
|
384
|
+
data: import_zod2.default.array(import_zod2.default.object({
|
|
385
|
+
type: import_zod2.default.string(),
|
|
386
|
+
id: import_zod2.default.union([
|
|
387
|
+
import_zod2.default.string(),
|
|
388
|
+
import_zod2.default.number()
|
|
250
389
|
])
|
|
251
390
|
}))
|
|
252
391
|
});
|
|
253
|
-
upsertMetaSchema =
|
|
254
|
-
meta:
|
|
255
|
-
operation:
|
|
256
|
-
matchFields:
|
|
392
|
+
upsertMetaSchema = import_zod2.default.object({
|
|
393
|
+
meta: import_zod2.default.object({
|
|
394
|
+
operation: import_zod2.default.literal("upsert"),
|
|
395
|
+
matchFields: import_zod2.default.array(import_zod2.default.string()).min(1)
|
|
257
396
|
})
|
|
258
397
|
});
|
|
259
398
|
// all known types and their metadata
|
|
@@ -266,6 +405,7 @@ var RestApiHandler = class {
|
|
|
266
405
|
externalIdMapping;
|
|
267
406
|
constructor(options) {
|
|
268
407
|
this.options = options;
|
|
408
|
+
this.validateOptions(options);
|
|
269
409
|
this.idDivider = options.idDivider ?? DEFAULT_ID_DIVIDER;
|
|
270
410
|
const segmentCharset = options.urlSegmentCharset ?? "a-zA-Z0-9-_~ %";
|
|
271
411
|
this.modelNameMapping = options.modelNameMapping ?? {};
|
|
@@ -286,6 +426,25 @@ var RestApiHandler = class {
|
|
|
286
426
|
this.buildTypeMap();
|
|
287
427
|
this.buildSerializers();
|
|
288
428
|
}
|
|
429
|
+
validateOptions(options) {
|
|
430
|
+
const schema = import_zod2.default.strictObject({
|
|
431
|
+
schema: import_zod2.default.object(),
|
|
432
|
+
log: loggerSchema.optional(),
|
|
433
|
+
endpoint: import_zod2.default.string().min(1),
|
|
434
|
+
pageSize: import_zod2.default.union([
|
|
435
|
+
import_zod2.default.number().int().positive(),
|
|
436
|
+
import_zod2.default.literal(Infinity)
|
|
437
|
+
]).optional(),
|
|
438
|
+
idDivider: import_zod2.default.string().min(1).optional(),
|
|
439
|
+
urlSegmentCharset: import_zod2.default.string().min(1).optional(),
|
|
440
|
+
modelNameMapping: import_zod2.default.record(import_zod2.default.string(), import_zod2.default.string()).optional(),
|
|
441
|
+
externalIdMapping: import_zod2.default.record(import_zod2.default.string(), import_zod2.default.string()).optional()
|
|
442
|
+
});
|
|
443
|
+
const parseResult = schema.safeParse(options);
|
|
444
|
+
if (!parseResult.success) {
|
|
445
|
+
throw new Error(`Invalid options: ${(0, import_v42.fromError)(parseResult.error)}`);
|
|
446
|
+
}
|
|
447
|
+
}
|
|
289
448
|
get schema() {
|
|
290
449
|
return this.options.schema;
|
|
291
450
|
}
|
|
@@ -346,6 +505,16 @@ var RestApiHandler = class {
|
|
|
346
505
|
path = "/" + path;
|
|
347
506
|
}
|
|
348
507
|
try {
|
|
508
|
+
if (path.startsWith("/$procs/")) {
|
|
509
|
+
const proc = path.split("/")[2];
|
|
510
|
+
return await this.processProcedureRequest({
|
|
511
|
+
client,
|
|
512
|
+
method,
|
|
513
|
+
proc,
|
|
514
|
+
query,
|
|
515
|
+
requestBody
|
|
516
|
+
});
|
|
517
|
+
}
|
|
349
518
|
switch (method) {
|
|
350
519
|
case "GET": {
|
|
351
520
|
let match4 = this.matchUrlPattern(path, "single");
|
|
@@ -419,7 +588,7 @@ var RestApiHandler = class {
|
|
|
419
588
|
} catch (err) {
|
|
420
589
|
if (err instanceof InvalidValueError) {
|
|
421
590
|
return this.makeError("invalidValue", err.message);
|
|
422
|
-
} else if (err instanceof
|
|
591
|
+
} else if (err instanceof import_orm2.ORMError) {
|
|
423
592
|
return this.handleORMError(err);
|
|
424
593
|
} else {
|
|
425
594
|
return this.handleGenericError(err);
|
|
@@ -430,6 +599,71 @@ var RestApiHandler = class {
|
|
|
430
599
|
return this.makeError("unknownError", err instanceof Error ? `${err.message}
|
|
431
600
|
${err.stack}` : "Unknown error");
|
|
432
601
|
}
|
|
602
|
+
async processProcedureRequest({ client, method, proc, query, requestBody }) {
|
|
603
|
+
if (!proc) {
|
|
604
|
+
return this.makeProcBadInputErrorResponse("missing procedure name");
|
|
605
|
+
}
|
|
606
|
+
const procDef = getProcedureDef(this.schema, proc);
|
|
607
|
+
if (!procDef) {
|
|
608
|
+
return this.makeProcBadInputErrorResponse(`unknown procedure: ${proc}`);
|
|
609
|
+
}
|
|
610
|
+
const isMutation = !!procDef.mutation;
|
|
611
|
+
if (isMutation) {
|
|
612
|
+
if (method !== "POST") {
|
|
613
|
+
return this.makeProcBadInputErrorResponse("invalid request method, only POST is supported");
|
|
614
|
+
}
|
|
615
|
+
} else {
|
|
616
|
+
if (method !== "GET") {
|
|
617
|
+
return this.makeProcBadInputErrorResponse("invalid request method, only GET is supported");
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
const argsPayload = method === "POST" ? requestBody : query;
|
|
621
|
+
const { result: processedArgsPayload, error } = await processSuperJsonRequestPayload(argsPayload);
|
|
622
|
+
if (error) {
|
|
623
|
+
return this.makeProcBadInputErrorResponse(error);
|
|
624
|
+
}
|
|
625
|
+
let procInput;
|
|
626
|
+
try {
|
|
627
|
+
procInput = mapProcedureArgs(procDef, processedArgsPayload);
|
|
628
|
+
} catch (err) {
|
|
629
|
+
return this.makeProcBadInputErrorResponse(err instanceof Error ? err.message : "invalid procedure arguments");
|
|
630
|
+
}
|
|
631
|
+
try {
|
|
632
|
+
log(this.log, "debug", () => `handling "$procs.${proc}" request`);
|
|
633
|
+
const clientResult = await client.$procs?.[proc](procInput);
|
|
634
|
+
const toSerialize = this.toPlainObject(clientResult);
|
|
635
|
+
const { json, meta } = import_superjson3.default.serialize(toSerialize);
|
|
636
|
+
const responseBody = {
|
|
637
|
+
data: json
|
|
638
|
+
};
|
|
639
|
+
if (meta) {
|
|
640
|
+
responseBody.meta = {
|
|
641
|
+
serialization: meta
|
|
642
|
+
};
|
|
643
|
+
}
|
|
644
|
+
return {
|
|
645
|
+
status: 200,
|
|
646
|
+
body: responseBody
|
|
647
|
+
};
|
|
648
|
+
} catch (err) {
|
|
649
|
+
log(this.log, "error", `error occurred when handling "$procs.${proc}" request`, err);
|
|
650
|
+
if (err instanceof import_orm2.ORMError) {
|
|
651
|
+
throw err;
|
|
652
|
+
}
|
|
653
|
+
return this.makeProcGenericErrorResponse(err);
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
makeProcBadInputErrorResponse(message) {
|
|
657
|
+
const resp = this.makeError("invalidPayload", message, 400);
|
|
658
|
+
log(this.log, "debug", () => `sending error response: ${JSON.stringify(resp)}`);
|
|
659
|
+
return resp;
|
|
660
|
+
}
|
|
661
|
+
makeProcGenericErrorResponse(err) {
|
|
662
|
+
const message = err instanceof Error ? err.message : "unknown error";
|
|
663
|
+
const resp = this.makeError("unknownError", message, 500);
|
|
664
|
+
log(this.log, "debug", () => `sending error response: ${JSON.stringify(resp)}`);
|
|
665
|
+
return resp;
|
|
666
|
+
}
|
|
433
667
|
async processSingleRead(client, type, resourceId, query) {
|
|
434
668
|
const typeInfo = this.getModelInfo(type);
|
|
435
669
|
if (!typeInfo) {
|
|
@@ -751,7 +985,7 @@ ${err.stack}` : "Unknown error");
|
|
|
751
985
|
processRequestBody(requestBody) {
|
|
752
986
|
let body = requestBody;
|
|
753
987
|
if (body.meta?.serialization) {
|
|
754
|
-
body =
|
|
988
|
+
body = import_superjson3.default.deserialize({
|
|
755
989
|
json: body,
|
|
756
990
|
meta: body.meta.serialization
|
|
757
991
|
});
|
|
@@ -1195,7 +1429,7 @@ ${err.stack}` : "Unknown error");
|
|
|
1195
1429
|
this.injectCompoundId(model, itemsWithId);
|
|
1196
1430
|
const serialized = await serializer.serialize(itemsWithId, options);
|
|
1197
1431
|
const plainResult = this.toPlainObject(serialized);
|
|
1198
|
-
const { json, meta } =
|
|
1432
|
+
const { json, meta } = import_superjson3.default.serialize(plainResult);
|
|
1199
1433
|
const result = json;
|
|
1200
1434
|
if (meta) {
|
|
1201
1435
|
result.meta = {
|
|
@@ -1774,15 +2008,15 @@ ${err.stack}` : "Unknown error");
|
|
|
1774
2008
|
}
|
|
1775
2009
|
}
|
|
1776
2010
|
handleORMError(err) {
|
|
1777
|
-
return (0, import_ts_pattern2.match)(err.reason).with(
|
|
2011
|
+
return (0, import_ts_pattern2.match)(err.reason).with(import_orm2.ORMErrorReason.INVALID_INPUT, () => {
|
|
1778
2012
|
return this.makeError("validationError", err.message, 422);
|
|
1779
|
-
}).with(
|
|
2013
|
+
}).with(import_orm2.ORMErrorReason.REJECTED_BY_POLICY, () => {
|
|
1780
2014
|
return this.makeError("forbidden", err.message, 403, {
|
|
1781
2015
|
reason: err.rejectedByPolicyReason
|
|
1782
2016
|
});
|
|
1783
|
-
}).with(
|
|
2017
|
+
}).with(import_orm2.ORMErrorReason.NOT_FOUND, () => {
|
|
1784
2018
|
return this.makeError("notFound", err.message, 404);
|
|
1785
|
-
}).with(
|
|
2019
|
+
}).with(import_orm2.ORMErrorReason.DB_QUERY_ERROR, () => {
|
|
1786
2020
|
return this.makeError("queryError", err.message, 400, {
|
|
1787
2021
|
dbErrorCode: err.dbErrorCode
|
|
1788
2022
|
});
|
|
@@ -1820,9 +2054,11 @@ ${err.stack}` : "Unknown error");
|
|
|
1820
2054
|
|
|
1821
2055
|
// src/api/rpc/index.ts
|
|
1822
2056
|
var import_common_helpers2 = require("@zenstackhq/common-helpers");
|
|
1823
|
-
var
|
|
1824
|
-
var
|
|
2057
|
+
var import_orm3 = require("@zenstackhq/orm");
|
|
2058
|
+
var import_superjson4 = __toESM(require("superjson"), 1);
|
|
1825
2059
|
var import_ts_pattern3 = require("ts-pattern");
|
|
2060
|
+
var import_zod3 = __toESM(require("zod"), 1);
|
|
2061
|
+
var import_v43 = require("zod-validation-error/v4");
|
|
1826
2062
|
registerCustomSerializers();
|
|
1827
2063
|
var RPCApiHandler = class {
|
|
1828
2064
|
static {
|
|
@@ -1831,6 +2067,17 @@ var RPCApiHandler = class {
|
|
|
1831
2067
|
options;
|
|
1832
2068
|
constructor(options) {
|
|
1833
2069
|
this.options = options;
|
|
2070
|
+
this.validateOptions(options);
|
|
2071
|
+
}
|
|
2072
|
+
validateOptions(options) {
|
|
2073
|
+
const schema = import_zod3.default.strictObject({
|
|
2074
|
+
schema: import_zod3.default.object(),
|
|
2075
|
+
log: loggerSchema.optional()
|
|
2076
|
+
});
|
|
2077
|
+
const parseResult = schema.safeParse(options);
|
|
2078
|
+
if (!parseResult.success) {
|
|
2079
|
+
throw new Error(`Invalid options: ${(0, import_v43.fromError)(parseResult.error)}`);
|
|
2080
|
+
}
|
|
1834
2081
|
}
|
|
1835
2082
|
get schema() {
|
|
1836
2083
|
return this.options.schema;
|
|
@@ -1845,6 +2092,15 @@ var RPCApiHandler = class {
|
|
|
1845
2092
|
if (parts.length !== 0 || !op || !model) {
|
|
1846
2093
|
return this.makeBadInputErrorResponse("invalid request path");
|
|
1847
2094
|
}
|
|
2095
|
+
if (model === PROCEDURE_ROUTE_PREFIXES) {
|
|
2096
|
+
return this.handleProcedureRequest({
|
|
2097
|
+
client,
|
|
2098
|
+
method: method.toUpperCase(),
|
|
2099
|
+
proc: op,
|
|
2100
|
+
query,
|
|
2101
|
+
requestBody
|
|
2102
|
+
});
|
|
2103
|
+
}
|
|
1848
2104
|
model = (0, import_common_helpers2.lowerCaseFirst)(model);
|
|
1849
2105
|
method = method.toUpperCase();
|
|
1850
2106
|
let args;
|
|
@@ -1869,11 +2125,12 @@ var RPCApiHandler = class {
|
|
|
1869
2125
|
case "aggregate":
|
|
1870
2126
|
case "groupBy":
|
|
1871
2127
|
case "count":
|
|
2128
|
+
case "exists":
|
|
1872
2129
|
if (method !== "GET") {
|
|
1873
2130
|
return this.makeBadInputErrorResponse("invalid request method, only GET is supported");
|
|
1874
2131
|
}
|
|
1875
2132
|
try {
|
|
1876
|
-
args = query?.["q"] ?
|
|
2133
|
+
args = query?.["q"] ? unmarshalQ(query["q"], query["meta"]) : {};
|
|
1877
2134
|
} catch {
|
|
1878
2135
|
return this.makeBadInputErrorResponse('invalid "q" query parameter');
|
|
1879
2136
|
}
|
|
@@ -1895,7 +2152,7 @@ var RPCApiHandler = class {
|
|
|
1895
2152
|
return this.makeBadInputErrorResponse("invalid request method, only DELETE is supported");
|
|
1896
2153
|
}
|
|
1897
2154
|
try {
|
|
1898
|
-
args = query?.["q"] ?
|
|
2155
|
+
args = query?.["q"] ? unmarshalQ(query["q"], query["meta"]) : {};
|
|
1899
2156
|
} catch (err) {
|
|
1900
2157
|
return this.makeBadInputErrorResponse(err instanceof Error ? err.message : 'invalid "q" query parameter');
|
|
1901
2158
|
}
|
|
@@ -1917,7 +2174,7 @@ var RPCApiHandler = class {
|
|
|
1917
2174
|
data: clientResult
|
|
1918
2175
|
};
|
|
1919
2176
|
if (clientResult) {
|
|
1920
|
-
const { json, meta } =
|
|
2177
|
+
const { json, meta } = import_superjson4.default.serialize(clientResult);
|
|
1921
2178
|
responseBody = {
|
|
1922
2179
|
data: json
|
|
1923
2180
|
};
|
|
@@ -1935,13 +2192,75 @@ var RPCApiHandler = class {
|
|
|
1935
2192
|
return response;
|
|
1936
2193
|
} catch (err) {
|
|
1937
2194
|
log(this.options.log, "error", `error occurred when handling "${model}.${op}" request`, err);
|
|
1938
|
-
if (err instanceof
|
|
2195
|
+
if (err instanceof import_orm3.ORMError) {
|
|
1939
2196
|
return this.makeORMErrorResponse(err);
|
|
1940
2197
|
} else {
|
|
1941
2198
|
return this.makeGenericErrorResponse(err);
|
|
1942
2199
|
}
|
|
1943
2200
|
}
|
|
1944
2201
|
}
|
|
2202
|
+
async handleProcedureRequest({ client, method, proc, query, requestBody }) {
|
|
2203
|
+
if (!proc) {
|
|
2204
|
+
return this.makeBadInputErrorResponse("missing procedure name");
|
|
2205
|
+
}
|
|
2206
|
+
const procDef = getProcedureDef(this.options.schema, proc);
|
|
2207
|
+
if (!procDef) {
|
|
2208
|
+
return this.makeBadInputErrorResponse(`unknown procedure: ${proc}`);
|
|
2209
|
+
}
|
|
2210
|
+
const isMutation = !!procDef.mutation;
|
|
2211
|
+
if (isMutation) {
|
|
2212
|
+
if (method !== "POST") {
|
|
2213
|
+
return this.makeBadInputErrorResponse("invalid request method, only POST is supported");
|
|
2214
|
+
}
|
|
2215
|
+
} else {
|
|
2216
|
+
if (method !== "GET") {
|
|
2217
|
+
return this.makeBadInputErrorResponse("invalid request method, only GET is supported");
|
|
2218
|
+
}
|
|
2219
|
+
}
|
|
2220
|
+
let argsPayload = method === "POST" ? requestBody : void 0;
|
|
2221
|
+
if (method === "GET") {
|
|
2222
|
+
try {
|
|
2223
|
+
argsPayload = query?.["q"] ? unmarshalQ(query["q"], query["meta"]) : void 0;
|
|
2224
|
+
} catch (err) {
|
|
2225
|
+
return this.makeBadInputErrorResponse(err instanceof Error ? err.message : 'invalid "q" query parameter');
|
|
2226
|
+
}
|
|
2227
|
+
}
|
|
2228
|
+
const { result: processedArgsPayload, error } = await processSuperJsonRequestPayload(argsPayload);
|
|
2229
|
+
if (error) {
|
|
2230
|
+
return this.makeBadInputErrorResponse(error);
|
|
2231
|
+
}
|
|
2232
|
+
let procInput;
|
|
2233
|
+
try {
|
|
2234
|
+
procInput = mapProcedureArgs(procDef, processedArgsPayload);
|
|
2235
|
+
} catch (err) {
|
|
2236
|
+
return this.makeBadInputErrorResponse(err instanceof Error ? err.message : "invalid procedure arguments");
|
|
2237
|
+
}
|
|
2238
|
+
try {
|
|
2239
|
+
log(this.options.log, "debug", () => `handling "$procs.${proc}" request`);
|
|
2240
|
+
const clientResult = await client.$procs?.[proc](procInput);
|
|
2241
|
+
const { json, meta } = import_superjson4.default.serialize(clientResult);
|
|
2242
|
+
const responseBody = {
|
|
2243
|
+
data: json
|
|
2244
|
+
};
|
|
2245
|
+
if (meta) {
|
|
2246
|
+
responseBody.meta = {
|
|
2247
|
+
serialization: meta
|
|
2248
|
+
};
|
|
2249
|
+
}
|
|
2250
|
+
const response = {
|
|
2251
|
+
status: 200,
|
|
2252
|
+
body: responseBody
|
|
2253
|
+
};
|
|
2254
|
+
log(this.options.log, "debug", () => `sending response for "$procs.${proc}" request: ${(0, import_common_helpers2.safeJSONStringify)(response)}`);
|
|
2255
|
+
return response;
|
|
2256
|
+
} catch (err) {
|
|
2257
|
+
log(this.options.log, "error", `error occurred when handling "$procs.${proc}" request`, err);
|
|
2258
|
+
if (err instanceof import_orm3.ORMError) {
|
|
2259
|
+
return this.makeORMErrorResponse(err);
|
|
2260
|
+
}
|
|
2261
|
+
return this.makeGenericErrorResponse(err);
|
|
2262
|
+
}
|
|
2263
|
+
}
|
|
1945
2264
|
isValidModel(client, model) {
|
|
1946
2265
|
return Object.keys(client.$schema.models).some((m) => (0, import_common_helpers2.lowerCaseFirst)(m) === (0, import_common_helpers2.lowerCaseFirst)(model));
|
|
1947
2266
|
}
|
|
@@ -1975,19 +2294,19 @@ var RPCApiHandler = class {
|
|
|
1975
2294
|
message: err.message,
|
|
1976
2295
|
reason: err.reason
|
|
1977
2296
|
};
|
|
1978
|
-
(0, import_ts_pattern3.match)(err.reason).with(
|
|
2297
|
+
(0, import_ts_pattern3.match)(err.reason).with(import_orm3.ORMErrorReason.NOT_FOUND, () => {
|
|
1979
2298
|
status = 404;
|
|
1980
2299
|
error.model = err.model;
|
|
1981
|
-
}).with(
|
|
2300
|
+
}).with(import_orm3.ORMErrorReason.INVALID_INPUT, () => {
|
|
1982
2301
|
status = 422;
|
|
1983
2302
|
error.rejectedByValidation = true;
|
|
1984
2303
|
error.model = err.model;
|
|
1985
|
-
}).with(
|
|
2304
|
+
}).with(import_orm3.ORMErrorReason.REJECTED_BY_POLICY, () => {
|
|
1986
2305
|
status = 403;
|
|
1987
2306
|
error.rejectedByPolicy = true;
|
|
1988
|
-
error.rejectReason = err.rejectedByPolicyReason;
|
|
1989
2307
|
error.model = err.model;
|
|
1990
|
-
|
|
2308
|
+
error.rejectReason = err.rejectedByPolicyReason;
|
|
2309
|
+
}).with(import_orm3.ORMErrorReason.DB_QUERY_ERROR, () => {
|
|
1991
2310
|
status = 400;
|
|
1992
2311
|
error.dbErrorCode = err.dbErrorCode;
|
|
1993
2312
|
}).otherwise(() => {
|
|
@@ -2005,7 +2324,7 @@ var RPCApiHandler = class {
|
|
|
2005
2324
|
const { meta, ...rest } = args ?? {};
|
|
2006
2325
|
if (meta?.serialization) {
|
|
2007
2326
|
try {
|
|
2008
|
-
args =
|
|
2327
|
+
args = import_superjson4.default.deserialize({
|
|
2009
2328
|
json: rest,
|
|
2010
2329
|
meta: meta.serialization
|
|
2011
2330
|
});
|
|
@@ -2023,29 +2342,6 @@ var RPCApiHandler = class {
|
|
|
2023
2342
|
error: void 0
|
|
2024
2343
|
};
|
|
2025
2344
|
}
|
|
2026
|
-
unmarshalQ(value, meta) {
|
|
2027
|
-
let parsedValue;
|
|
2028
|
-
try {
|
|
2029
|
-
parsedValue = JSON.parse(value);
|
|
2030
|
-
} catch {
|
|
2031
|
-
throw new Error('invalid "q" query parameter');
|
|
2032
|
-
}
|
|
2033
|
-
if (meta) {
|
|
2034
|
-
let parsedMeta;
|
|
2035
|
-
try {
|
|
2036
|
-
parsedMeta = JSON.parse(meta);
|
|
2037
|
-
} catch {
|
|
2038
|
-
throw new Error('invalid "meta" query parameter');
|
|
2039
|
-
}
|
|
2040
|
-
if (parsedMeta.serialization) {
|
|
2041
|
-
return import_superjson3.default.deserialize({
|
|
2042
|
-
json: parsedValue,
|
|
2043
|
-
meta: parsedMeta.serialization
|
|
2044
|
-
});
|
|
2045
|
-
}
|
|
2046
|
-
}
|
|
2047
|
-
return parsedValue;
|
|
2048
|
-
}
|
|
2049
2345
|
};
|
|
2050
2346
|
// Annotate the CommonJS export names for ESM import in node:
|
|
2051
2347
|
0 && (module.exports = {
|