@zenstackhq/server 2.20.0 → 3.0.0-beta.12
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/LICENSE +1 -1
- package/dist/api.cjs +299 -0
- package/dist/api.cjs.map +1 -0
- package/dist/api.d.cts +28 -0
- package/dist/api.d.ts +28 -0
- package/dist/api.js +264 -0
- package/dist/api.js.map +1 -0
- package/dist/express.cjs +75 -0
- package/dist/express.cjs.map +1 -0
- package/dist/express.d.cts +31 -0
- package/dist/express.d.ts +31 -0
- package/dist/express.js +50 -0
- package/dist/express.js.map +1 -0
- package/dist/types-BH-88xJo.d.cts +68 -0
- package/dist/types-BH-88xJo.d.ts +68 -0
- package/package.json +57 -58
- package/README.md +0 -5
- package/api/base.d.ts +0 -49
- package/api/base.js +0 -19
- package/api/base.js.map +0 -1
- package/api/index.d.ts +0 -2
- package/api/index.js +0 -8
- package/api/index.js.map +0 -1
- package/api/rest/index.d.ts +0 -34
- package/api/rest/index.js +0 -1598
- package/api/rest/index.js.map +0 -1
- package/api/rpc/index.d.ts +0 -4
- package/api/rpc/index.js +0 -248
- package/api/rpc/index.js.map +0 -1
- package/api/utils.d.ts +0 -8
- package/api/utils.js +0 -54
- package/api/utils.js.map +0 -1
- package/elysia/handler.d.ts +0 -44
- package/elysia/handler.js +0 -62
- package/elysia/handler.js.map +0 -1
- package/elysia/index.d.ts +0 -1
- package/elysia/index.js +0 -18
- package/elysia/index.js.map +0 -1
- package/express/index.d.ts +0 -2
- package/express/index.js +0 -21
- package/express/index.js.map +0 -1
- package/express/middleware.d.ts +0 -27
- package/express/middleware.js +0 -58
- package/express/middleware.js.map +0 -1
- package/fastify/index.d.ts +0 -2
- package/fastify/index.js +0 -21
- package/fastify/index.js.map +0 -1
- package/fastify/plugin.d.ts +0 -18
- package/fastify/plugin.js +0 -48
- package/fastify/plugin.js.map +0 -1
- package/hono/handler.d.ts +0 -12
- package/hono/handler.js +0 -47
- package/hono/handler.js.map +0 -1
- package/hono/index.d.ts +0 -1
- package/hono/index.js +0 -18
- package/hono/index.js.map +0 -1
- package/nestjs/api-handler.service.d.ts +0 -15
- package/nestjs/api-handler.service.js +0 -72
- package/nestjs/api-handler.service.js.map +0 -1
- package/nestjs/index.d.ts +0 -3
- package/nestjs/index.js +0 -20
- package/nestjs/index.js.map +0 -1
- package/nestjs/interfaces/api-handler-options.interface.d.ts +0 -17
- package/nestjs/interfaces/api-handler-options.interface.js +0 -3
- package/nestjs/interfaces/api-handler-options.interface.js.map +0 -1
- package/nestjs/interfaces/index.d.ts +0 -2
- package/nestjs/interfaces/index.js +0 -19
- package/nestjs/interfaces/index.js.map +0 -1
- package/nestjs/interfaces/zenstack-module-options.interface.d.ts +0 -35
- package/nestjs/interfaces/zenstack-module-options.interface.js +0 -3
- package/nestjs/interfaces/zenstack-module-options.interface.js.map +0 -1
- package/nestjs/zenstack.constants.d.ts +0 -4
- package/nestjs/zenstack.constants.js +0 -8
- package/nestjs/zenstack.constants.js.map +0 -1
- package/nestjs/zenstack.module.d.ts +0 -12
- package/nestjs/zenstack.module.js +0 -61
- package/nestjs/zenstack.module.js.map +0 -1
- package/next/app-route-handler.d.ts +0 -16
- package/next/app-route-handler.js +0 -65
- package/next/app-route-handler.js.map +0 -1
- package/next/index.d.ts +0 -38
- package/next/index.js +0 -17
- package/next/index.js.map +0 -1
- package/next/pages-route-handler.d.ts +0 -9
- package/next/pages-route-handler.js +0 -45
- package/next/pages-route-handler.js.map +0 -1
- package/nuxt/handler.d.ts +0 -12
- package/nuxt/handler.js +0 -45
- package/nuxt/handler.js.map +0 -1
- package/nuxt/index.d.ts +0 -1
- package/nuxt/index.js +0 -18
- package/nuxt/index.js.map +0 -1
- package/shared.d.ts +0 -20
- package/shared.js +0 -50
- package/shared.js.map +0 -1
- package/sveltekit/handler.d.ts +0 -20
- package/sveltekit/handler.js +0 -68
- package/sveltekit/handler.js.map +0 -1
- package/sveltekit/index.d.ts +0 -2
- package/sveltekit/index.js +0 -21
- package/sveltekit/index.js.map +0 -1
- package/tanstack-start/handler.d.ts +0 -11
- package/tanstack-start/handler.js +0 -75
- package/tanstack-start/handler.js.map +0 -1
- package/tanstack-start/index.d.ts +0 -17
- package/tanstack-start/index.js +0 -16
- package/tanstack-start/index.js.map +0 -1
- package/types.d.ts +0 -49
- package/types.js +0 -3
- package/types.js.map +0 -1
package/LICENSE
CHANGED
package/dist/api.cjs
ADDED
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
9
|
+
var __export = (target, all) => {
|
|
10
|
+
for (var name in all)
|
|
11
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
12
|
+
};
|
|
13
|
+
var __copyProps = (to, from, except, desc) => {
|
|
14
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
15
|
+
for (let key of __getOwnPropNames(from))
|
|
16
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
17
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
18
|
+
}
|
|
19
|
+
return to;
|
|
20
|
+
};
|
|
21
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
22
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
23
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
24
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
25
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
26
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
27
|
+
mod
|
|
28
|
+
));
|
|
29
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
30
|
+
|
|
31
|
+
// src/api/index.ts
|
|
32
|
+
var api_exports = {};
|
|
33
|
+
__export(api_exports, {
|
|
34
|
+
RPCApiHandler: () => RPCApiHandler
|
|
35
|
+
});
|
|
36
|
+
module.exports = __toCommonJS(api_exports);
|
|
37
|
+
|
|
38
|
+
// src/api/rpc/index.ts
|
|
39
|
+
var import_common_helpers = require("@zenstackhq/common-helpers");
|
|
40
|
+
var import_runtime2 = require("@zenstackhq/runtime");
|
|
41
|
+
var import_superjson2 = __toESM(require("superjson"), 1);
|
|
42
|
+
|
|
43
|
+
// src/api/utils.ts
|
|
44
|
+
var import_decimal = require("decimal.js");
|
|
45
|
+
var import_superjson = __toESM(require("superjson"), 1);
|
|
46
|
+
var import_ts_pattern = require("ts-pattern");
|
|
47
|
+
function log(logger, level, message, error) {
|
|
48
|
+
if (!logger) {
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
const getMessage = typeof message === "function" ? message : () => message;
|
|
52
|
+
if (typeof logger === "function") {
|
|
53
|
+
logger(level, getMessage(), error);
|
|
54
|
+
} else if (logger.includes(level)) {
|
|
55
|
+
const logFn = (0, import_ts_pattern.match)(level).with("debug", () => console.debug).with("info", () => console.info).with("warn", () => console.warn).with("error", () => console.error).exhaustive();
|
|
56
|
+
logFn(`@zenstackhq/server: [${level}] ${getMessage()}${error ? `
|
|
57
|
+
${error}` : ""}`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
__name(log, "log");
|
|
61
|
+
function registerCustomSerializers() {
|
|
62
|
+
import_superjson.default.registerCustom({
|
|
63
|
+
isApplicable: /* @__PURE__ */ __name((v) => import_decimal.Decimal.isDecimal(v), "isApplicable"),
|
|
64
|
+
serialize: /* @__PURE__ */ __name((v) => v.toJSON(), "serialize"),
|
|
65
|
+
deserialize: /* @__PURE__ */ __name((v) => new import_decimal.Decimal(v), "deserialize")
|
|
66
|
+
}, "Decimal");
|
|
67
|
+
if (globalThis.Buffer) {
|
|
68
|
+
import_superjson.default.registerCustom({
|
|
69
|
+
isApplicable: /* @__PURE__ */ __name((v) => Buffer.isBuffer(v), "isApplicable"),
|
|
70
|
+
serialize: /* @__PURE__ */ __name((v) => v.toString("base64"), "serialize"),
|
|
71
|
+
deserialize: /* @__PURE__ */ __name((v) => Buffer.from(v, "base64"), "deserialize")
|
|
72
|
+
}, "Bytes");
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
__name(registerCustomSerializers, "registerCustomSerializers");
|
|
76
|
+
|
|
77
|
+
// src/api/rpc/index.ts
|
|
78
|
+
registerCustomSerializers();
|
|
79
|
+
var RPCApiHandler = class {
|
|
80
|
+
static {
|
|
81
|
+
__name(this, "RPCApiHandler");
|
|
82
|
+
}
|
|
83
|
+
options;
|
|
84
|
+
constructor(options) {
|
|
85
|
+
this.options = options;
|
|
86
|
+
}
|
|
87
|
+
get schema() {
|
|
88
|
+
return this.options.schema;
|
|
89
|
+
}
|
|
90
|
+
async handleRequest({ client, method, path, query, requestBody }) {
|
|
91
|
+
const parts = path.split("/").filter((p) => !!p);
|
|
92
|
+
const op = parts.pop();
|
|
93
|
+
let model = parts.pop();
|
|
94
|
+
if (parts.length !== 0 || !op || !model) {
|
|
95
|
+
return this.makeBadInputErrorResponse("invalid request path");
|
|
96
|
+
}
|
|
97
|
+
model = (0, import_common_helpers.lowerCaseFirst)(model);
|
|
98
|
+
method = method.toUpperCase();
|
|
99
|
+
let args;
|
|
100
|
+
let resCode = 200;
|
|
101
|
+
switch (op) {
|
|
102
|
+
case "create":
|
|
103
|
+
case "createMany":
|
|
104
|
+
case "createManyAndReturn":
|
|
105
|
+
case "upsert":
|
|
106
|
+
if (method !== "POST") {
|
|
107
|
+
return this.makeBadInputErrorResponse("invalid request method, only POST is supported");
|
|
108
|
+
}
|
|
109
|
+
if (!requestBody) {
|
|
110
|
+
return this.makeBadInputErrorResponse("missing request body");
|
|
111
|
+
}
|
|
112
|
+
args = requestBody;
|
|
113
|
+
resCode = 201;
|
|
114
|
+
break;
|
|
115
|
+
case "findFirst":
|
|
116
|
+
case "findUnique":
|
|
117
|
+
case "findMany":
|
|
118
|
+
case "aggregate":
|
|
119
|
+
case "groupBy":
|
|
120
|
+
case "count":
|
|
121
|
+
if (method !== "GET") {
|
|
122
|
+
return this.makeBadInputErrorResponse("invalid request method, only GET is supported");
|
|
123
|
+
}
|
|
124
|
+
try {
|
|
125
|
+
args = query?.["q"] ? this.unmarshalQ(query["q"], query["meta"]) : {};
|
|
126
|
+
} catch {
|
|
127
|
+
return this.makeBadInputErrorResponse('invalid "q" query parameter');
|
|
128
|
+
}
|
|
129
|
+
break;
|
|
130
|
+
case "update":
|
|
131
|
+
case "updateMany":
|
|
132
|
+
case "updateManyAndReturn":
|
|
133
|
+
if (method !== "PUT" && method !== "PATCH") {
|
|
134
|
+
return this.makeBadInputErrorResponse("invalid request method, only PUT or PATCH are supported");
|
|
135
|
+
}
|
|
136
|
+
if (!requestBody) {
|
|
137
|
+
return this.makeBadInputErrorResponse("missing request body");
|
|
138
|
+
}
|
|
139
|
+
args = requestBody;
|
|
140
|
+
break;
|
|
141
|
+
case "delete":
|
|
142
|
+
case "deleteMany":
|
|
143
|
+
if (method !== "DELETE") {
|
|
144
|
+
return this.makeBadInputErrorResponse("invalid request method, only DELETE is supported");
|
|
145
|
+
}
|
|
146
|
+
try {
|
|
147
|
+
args = query?.["q"] ? this.unmarshalQ(query["q"], query["meta"]) : {};
|
|
148
|
+
} catch (err) {
|
|
149
|
+
return this.makeBadInputErrorResponse(err instanceof Error ? err.message : 'invalid "q" query parameter');
|
|
150
|
+
}
|
|
151
|
+
break;
|
|
152
|
+
default:
|
|
153
|
+
return this.makeBadInputErrorResponse("invalid operation: " + op);
|
|
154
|
+
}
|
|
155
|
+
const { result: processedArgs, error } = await this.processRequestPayload(args);
|
|
156
|
+
if (error) {
|
|
157
|
+
return this.makeBadInputErrorResponse(error);
|
|
158
|
+
}
|
|
159
|
+
try {
|
|
160
|
+
if (!this.isValidModel(client, model)) {
|
|
161
|
+
return this.makeBadInputErrorResponse(`unknown model name: ${model}`);
|
|
162
|
+
}
|
|
163
|
+
log(this.options.log, "debug", () => `handling "${model}.${op}" request with args: ${(0, import_common_helpers.safeJSONStringify)(processedArgs)}`);
|
|
164
|
+
const clientResult = await client[model][op](processedArgs);
|
|
165
|
+
let responseBody = {
|
|
166
|
+
data: clientResult
|
|
167
|
+
};
|
|
168
|
+
if (clientResult) {
|
|
169
|
+
const { json, meta } = import_superjson2.default.serialize(clientResult);
|
|
170
|
+
responseBody = {
|
|
171
|
+
data: json
|
|
172
|
+
};
|
|
173
|
+
if (meta) {
|
|
174
|
+
responseBody.meta = {
|
|
175
|
+
serialization: meta
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
const response = {
|
|
180
|
+
status: resCode,
|
|
181
|
+
body: responseBody
|
|
182
|
+
};
|
|
183
|
+
log(this.options.log, "debug", () => `sending response for "${model}.${op}" request: ${(0, import_common_helpers.safeJSONStringify)(response)}`);
|
|
184
|
+
return response;
|
|
185
|
+
} catch (err) {
|
|
186
|
+
log(this.options.log, "error", `error occurred when handling "${model}.${op}" request`, err);
|
|
187
|
+
if (err instanceof import_runtime2.ZenStackError) {
|
|
188
|
+
return this.makeZenStackErrorResponse(err);
|
|
189
|
+
} else {
|
|
190
|
+
return this.makeGenericErrorResponse(err);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
isValidModel(client, model) {
|
|
195
|
+
return Object.keys(client.$schema.models).some((m) => (0, import_common_helpers.lowerCaseFirst)(m) === (0, import_common_helpers.lowerCaseFirst)(model));
|
|
196
|
+
}
|
|
197
|
+
makeBadInputErrorResponse(message) {
|
|
198
|
+
const resp = {
|
|
199
|
+
status: 400,
|
|
200
|
+
body: {
|
|
201
|
+
error: {
|
|
202
|
+
message
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
};
|
|
206
|
+
log(this.options.log, "debug", () => `sending error response: ${(0, import_common_helpers.safeJSONStringify)(resp)}`);
|
|
207
|
+
return resp;
|
|
208
|
+
}
|
|
209
|
+
makeGenericErrorResponse(err) {
|
|
210
|
+
const resp = {
|
|
211
|
+
status: 500,
|
|
212
|
+
body: {
|
|
213
|
+
error: {
|
|
214
|
+
message: err.message || "unknown error"
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
};
|
|
218
|
+
log(this.options.log, "debug", () => `sending error response: ${(0, import_common_helpers.safeJSONStringify)(resp)}`);
|
|
219
|
+
return resp;
|
|
220
|
+
}
|
|
221
|
+
makeZenStackErrorResponse(err) {
|
|
222
|
+
let status = 400;
|
|
223
|
+
const error = {
|
|
224
|
+
message: err.message
|
|
225
|
+
};
|
|
226
|
+
if (err.cause && err.cause instanceof Error) {
|
|
227
|
+
error.cause = err.cause.message;
|
|
228
|
+
}
|
|
229
|
+
if (err instanceof import_runtime2.NotFoundError) {
|
|
230
|
+
status = 404;
|
|
231
|
+
error.model = err.model;
|
|
232
|
+
} else if (err instanceof import_runtime2.InputValidationError) {
|
|
233
|
+
status = 422;
|
|
234
|
+
error.rejectedByValidation = true;
|
|
235
|
+
error.model = err.model;
|
|
236
|
+
} else if (err instanceof import_runtime2.RejectedByPolicyError) {
|
|
237
|
+
status = 403;
|
|
238
|
+
error.rejectedByPolicy = true;
|
|
239
|
+
error.rejectReason = err.reason;
|
|
240
|
+
error.model = err.model;
|
|
241
|
+
}
|
|
242
|
+
const resp = {
|
|
243
|
+
status,
|
|
244
|
+
body: {
|
|
245
|
+
error
|
|
246
|
+
}
|
|
247
|
+
};
|
|
248
|
+
log(this.options.log, "debug", () => `sending error response: ${(0, import_common_helpers.safeJSONStringify)(resp)}`);
|
|
249
|
+
return resp;
|
|
250
|
+
}
|
|
251
|
+
async processRequestPayload(args) {
|
|
252
|
+
const { meta, ...rest } = args;
|
|
253
|
+
if (meta?.serialization) {
|
|
254
|
+
try {
|
|
255
|
+
args = import_superjson2.default.deserialize({
|
|
256
|
+
json: rest,
|
|
257
|
+
meta: meta.serialization
|
|
258
|
+
});
|
|
259
|
+
} catch (err) {
|
|
260
|
+
return {
|
|
261
|
+
result: void 0,
|
|
262
|
+
error: `failed to deserialize request payload: ${err.message}`
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
return {
|
|
267
|
+
result: args,
|
|
268
|
+
error: void 0
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
unmarshalQ(value, meta) {
|
|
272
|
+
let parsedValue;
|
|
273
|
+
try {
|
|
274
|
+
parsedValue = JSON.parse(value);
|
|
275
|
+
} catch {
|
|
276
|
+
throw new Error('invalid "q" query parameter');
|
|
277
|
+
}
|
|
278
|
+
if (meta) {
|
|
279
|
+
let parsedMeta;
|
|
280
|
+
try {
|
|
281
|
+
parsedMeta = JSON.parse(meta);
|
|
282
|
+
} catch {
|
|
283
|
+
throw new Error('invalid "meta" query parameter');
|
|
284
|
+
}
|
|
285
|
+
if (parsedMeta.serialization) {
|
|
286
|
+
return import_superjson2.default.deserialize({
|
|
287
|
+
json: parsedValue,
|
|
288
|
+
meta: parsedMeta.serialization
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
return parsedValue;
|
|
293
|
+
}
|
|
294
|
+
};
|
|
295
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
296
|
+
0 && (module.exports = {
|
|
297
|
+
RPCApiHandler
|
|
298
|
+
});
|
|
299
|
+
//# sourceMappingURL=api.cjs.map
|
package/dist/api.cjs.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/api/index.ts","../src/api/rpc/index.ts","../src/api/utils.ts"],"sourcesContent":["export { RPCApiHandler } from './rpc';\n","import { lowerCaseFirst, safeJSONStringify } from '@zenstackhq/common-helpers';\nimport {\n InputValidationError,\n NotFoundError,\n RejectedByPolicyError,\n ZenStackError,\n type ClientContract,\n} from '@zenstackhq/runtime';\nimport type { SchemaDef } from '@zenstackhq/runtime/schema';\nimport SuperJSON from 'superjson';\nimport type { ApiHandler, LogConfig, RequestContext, Response } from '../../types';\nimport { log, registerCustomSerializers } from '../utils';\n\nregisterCustomSerializers();\n\n/**\n * Options for {@link RPCApiHandler}\n */\nexport type RPCApiHandlerOptions<Schema extends SchemaDef> = {\n schema: Schema;\n log?: LogConfig;\n};\n\n/**\n * RPC style API request handler that mirrors the ZenStackClient API\n */\nexport class RPCApiHandler<Schema extends SchemaDef> implements ApiHandler<Schema> {\n constructor(private readonly options: RPCApiHandlerOptions<Schema>) {}\n\n get schema(): Schema {\n return this.options.schema;\n }\n\n async handleRequest({ client, method, path, query, requestBody }: RequestContext<Schema>): Promise<Response> {\n const parts = path.split('/').filter((p) => !!p);\n const op = parts.pop();\n let model = parts.pop();\n\n if (parts.length !== 0 || !op || !model) {\n return this.makeBadInputErrorResponse('invalid request path');\n }\n\n model = lowerCaseFirst(model);\n method = method.toUpperCase();\n let args: unknown;\n let resCode = 200;\n\n switch (op) {\n case 'create':\n case 'createMany':\n case 'createManyAndReturn':\n case 'upsert':\n if (method !== 'POST') {\n return this.makeBadInputErrorResponse('invalid request method, only POST is supported');\n }\n if (!requestBody) {\n return this.makeBadInputErrorResponse('missing request body');\n }\n\n args = requestBody;\n resCode = 201;\n break;\n\n case 'findFirst':\n case 'findUnique':\n case 'findMany':\n case 'aggregate':\n case 'groupBy':\n case 'count':\n if (method !== 'GET') {\n return this.makeBadInputErrorResponse('invalid request method, only GET is supported');\n }\n try {\n args = query?.['q']\n ? this.unmarshalQ(query['q'] as string, query['meta'] as string | undefined)\n : {};\n } catch {\n return this.makeBadInputErrorResponse('invalid \"q\" query parameter');\n }\n break;\n\n case 'update':\n case 'updateMany':\n case 'updateManyAndReturn':\n if (method !== 'PUT' && method !== 'PATCH') {\n return this.makeBadInputErrorResponse('invalid request method, only PUT or PATCH are supported');\n }\n if (!requestBody) {\n return this.makeBadInputErrorResponse('missing request body');\n }\n\n args = requestBody;\n break;\n\n case 'delete':\n case 'deleteMany':\n if (method !== 'DELETE') {\n return this.makeBadInputErrorResponse('invalid request method, only DELETE is supported');\n }\n try {\n args = query?.['q']\n ? this.unmarshalQ(query['q'] as string, query['meta'] as string | undefined)\n : {};\n } catch (err) {\n return this.makeBadInputErrorResponse(\n err instanceof Error ? err.message : 'invalid \"q\" query parameter',\n );\n }\n break;\n\n default:\n return this.makeBadInputErrorResponse('invalid operation: ' + op);\n }\n\n const { result: processedArgs, error } = await this.processRequestPayload(args);\n if (error) {\n return this.makeBadInputErrorResponse(error);\n }\n\n try {\n if (!this.isValidModel(client, model)) {\n return this.makeBadInputErrorResponse(`unknown model name: ${model}`);\n }\n\n log(\n this.options.log,\n 'debug',\n () => `handling \"${model}.${op}\" request with args: ${safeJSONStringify(processedArgs)}`,\n );\n\n const clientResult = await (client as any)[model][op](processedArgs);\n let responseBody: any = { data: clientResult };\n\n // superjson serialize response\n if (clientResult) {\n const { json, meta } = SuperJSON.serialize(clientResult);\n responseBody = { data: json };\n if (meta) {\n responseBody.meta = { serialization: meta };\n }\n }\n\n const response = { status: resCode, body: responseBody };\n log(\n this.options.log,\n 'debug',\n () => `sending response for \"${model}.${op}\" request: ${safeJSONStringify(response)}`,\n );\n return response;\n } catch (err) {\n log(this.options.log, 'error', `error occurred when handling \"${model}.${op}\" request`, err);\n if (err instanceof ZenStackError) {\n return this.makeZenStackErrorResponse(err);\n } else {\n return this.makeGenericErrorResponse(err);\n }\n }\n }\n\n private isValidModel(client: ClientContract<Schema>, model: string) {\n return Object.keys(client.$schema.models).some((m) => lowerCaseFirst(m) === lowerCaseFirst(model));\n }\n\n private makeBadInputErrorResponse(message: string) {\n const resp = {\n status: 400,\n body: { error: { message } },\n };\n log(this.options.log, 'debug', () => `sending error response: ${safeJSONStringify(resp)}`);\n return resp;\n }\n\n private makeGenericErrorResponse(err: unknown) {\n const resp = {\n status: 500,\n body: { error: { message: (err as Error).message || 'unknown error' } },\n };\n log(this.options.log, 'debug', () => `sending error response: ${safeJSONStringify(resp)}`);\n return resp;\n }\n\n private makeZenStackErrorResponse(err: ZenStackError) {\n let status = 400;\n const error: any = { message: err.message };\n if (err.cause && err.cause instanceof Error) {\n error.cause = err.cause.message;\n }\n\n if (err instanceof NotFoundError) {\n status = 404;\n error.model = err.model;\n } else if (err instanceof InputValidationError) {\n status = 422;\n error.rejectedByValidation = true;\n error.model = err.model;\n } else if (err instanceof RejectedByPolicyError) {\n status = 403;\n error.rejectedByPolicy = true;\n error.rejectReason = err.reason;\n error.model = err.model;\n }\n\n const resp = { status, body: { error } };\n log(this.options.log, 'debug', () => `sending error response: ${safeJSONStringify(resp)}`);\n return resp;\n }\n\n private async processRequestPayload(args: any) {\n const { meta, ...rest } = args;\n if (meta?.serialization) {\n try {\n // superjson deserialization\n args = SuperJSON.deserialize({ json: rest, meta: meta.serialization });\n } catch (err) {\n return { result: undefined, error: `failed to deserialize request payload: ${(err as Error).message}` };\n }\n }\n return { result: args, error: undefined };\n }\n\n private unmarshalQ(value: string, meta: string | undefined) {\n let parsedValue: any;\n try {\n parsedValue = JSON.parse(value);\n } catch {\n throw new Error('invalid \"q\" query parameter');\n }\n\n if (meta) {\n let parsedMeta: any;\n try {\n parsedMeta = JSON.parse(meta);\n } catch {\n throw new Error('invalid \"meta\" query parameter');\n }\n\n if (parsedMeta.serialization) {\n return SuperJSON.deserialize({ json: parsedValue, meta: parsedMeta.serialization });\n }\n }\n\n return parsedValue;\n }\n}\n","import { Decimal } from 'decimal.js';\nimport SuperJSON from 'superjson';\nimport { match } from 'ts-pattern';\nimport type { LogConfig, LogLevel } from '../types';\n\nexport function log(logger: LogConfig | undefined, level: LogLevel, message: string | (() => string), error?: unknown) {\n if (!logger) {\n return;\n }\n\n const getMessage = typeof message === 'function' ? message : () => message;\n\n if (typeof logger === 'function') {\n logger(level, getMessage(), error);\n } else if (logger.includes(level)) {\n const logFn = match(level)\n .with('debug', () => console.debug)\n .with('info', () => console.info)\n .with('warn', () => console.warn)\n .with('error', () => console.error)\n .exhaustive();\n logFn(`@zenstackhq/server: [${level}] ${getMessage()}${error ? `\\n${error}` : ''}`);\n }\n}\n\n/**\n * Registers custom superjson serializers.\n */\nexport function registerCustomSerializers() {\n SuperJSON.registerCustom<Decimal, string>(\n {\n isApplicable: (v): v is Decimal => Decimal.isDecimal(v),\n serialize: (v) => v.toJSON(),\n deserialize: (v) => new Decimal(v),\n },\n 'Decimal',\n );\n\n // `Buffer` is not available in edge runtime\n if (globalThis.Buffer) {\n SuperJSON.registerCustom<Buffer, string>(\n {\n isApplicable: (v): v is Buffer => Buffer.isBuffer(v),\n serialize: (v) => v.toString('base64'),\n deserialize: (v) => Buffer.from(v, 'base64'),\n },\n 'Bytes',\n );\n }\n}\n"],"mappings":"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","names":["import_runtime","import_superjson","log","logger","level","message","error","getMessage","includes","logFn","match","with","console","debug","info","warn","exhaustive","registerCustomSerializers","SuperJSON","registerCustom","isApplicable","v","Decimal","isDecimal","serialize","toJSON","deserialize","globalThis","Buffer","isBuffer","toString","from","registerCustomSerializers","RPCApiHandler","options","schema","handleRequest","client","method","path","query","requestBody","parts","split","filter","p","op","pop","model","length","makeBadInputErrorResponse","lowerCaseFirst","toUpperCase","args","resCode","unmarshalQ","err","Error","message","result","processedArgs","error","processRequestPayload","isValidModel","log","safeJSONStringify","clientResult","responseBody","data","json","meta","SuperJSON","serialize","serialization","response","status","body","ZenStackError","makeZenStackErrorResponse","makeGenericErrorResponse","Object","keys","$schema","models","some","m","resp","cause","NotFoundError","InputValidationError","rejectedByValidation","RejectedByPolicyError","rejectedByPolicy","rejectReason","reason","rest","deserialize","undefined","value","parsedValue","JSON","parse","parsedMeta"]}
|
package/dist/api.d.cts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { SchemaDef } from '@zenstackhq/runtime/schema';
|
|
2
|
+
import { A as ApiHandler, L as LogConfig, R as RequestContext, a as Response } from './types-BH-88xJo.cjs';
|
|
3
|
+
import '@zenstackhq/runtime';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Options for {@link RPCApiHandler}
|
|
7
|
+
*/
|
|
8
|
+
type RPCApiHandlerOptions<Schema extends SchemaDef> = {
|
|
9
|
+
schema: Schema;
|
|
10
|
+
log?: LogConfig;
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* RPC style API request handler that mirrors the ZenStackClient API
|
|
14
|
+
*/
|
|
15
|
+
declare class RPCApiHandler<Schema extends SchemaDef> implements ApiHandler<Schema> {
|
|
16
|
+
private readonly options;
|
|
17
|
+
constructor(options: RPCApiHandlerOptions<Schema>);
|
|
18
|
+
get schema(): Schema;
|
|
19
|
+
handleRequest({ client, method, path, query, requestBody }: RequestContext<Schema>): Promise<Response>;
|
|
20
|
+
private isValidModel;
|
|
21
|
+
private makeBadInputErrorResponse;
|
|
22
|
+
private makeGenericErrorResponse;
|
|
23
|
+
private makeZenStackErrorResponse;
|
|
24
|
+
private processRequestPayload;
|
|
25
|
+
private unmarshalQ;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export { RPCApiHandler };
|
package/dist/api.d.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { SchemaDef } from '@zenstackhq/runtime/schema';
|
|
2
|
+
import { A as ApiHandler, L as LogConfig, R as RequestContext, a as Response } from './types-BH-88xJo.js';
|
|
3
|
+
import '@zenstackhq/runtime';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Options for {@link RPCApiHandler}
|
|
7
|
+
*/
|
|
8
|
+
type RPCApiHandlerOptions<Schema extends SchemaDef> = {
|
|
9
|
+
schema: Schema;
|
|
10
|
+
log?: LogConfig;
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* RPC style API request handler that mirrors the ZenStackClient API
|
|
14
|
+
*/
|
|
15
|
+
declare class RPCApiHandler<Schema extends SchemaDef> implements ApiHandler<Schema> {
|
|
16
|
+
private readonly options;
|
|
17
|
+
constructor(options: RPCApiHandlerOptions<Schema>);
|
|
18
|
+
get schema(): Schema;
|
|
19
|
+
handleRequest({ client, method, path, query, requestBody }: RequestContext<Schema>): Promise<Response>;
|
|
20
|
+
private isValidModel;
|
|
21
|
+
private makeBadInputErrorResponse;
|
|
22
|
+
private makeGenericErrorResponse;
|
|
23
|
+
private makeZenStackErrorResponse;
|
|
24
|
+
private processRequestPayload;
|
|
25
|
+
private unmarshalQ;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export { RPCApiHandler };
|
package/dist/api.js
ADDED
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
3
|
+
|
|
4
|
+
// src/api/rpc/index.ts
|
|
5
|
+
import { lowerCaseFirst, safeJSONStringify } from "@zenstackhq/common-helpers";
|
|
6
|
+
import { InputValidationError, NotFoundError, RejectedByPolicyError, ZenStackError } from "@zenstackhq/runtime";
|
|
7
|
+
import SuperJSON2 from "superjson";
|
|
8
|
+
|
|
9
|
+
// src/api/utils.ts
|
|
10
|
+
import { Decimal } from "decimal.js";
|
|
11
|
+
import SuperJSON from "superjson";
|
|
12
|
+
import { match } from "ts-pattern";
|
|
13
|
+
function log(logger, level, message, error) {
|
|
14
|
+
if (!logger) {
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
const getMessage = typeof message === "function" ? message : () => message;
|
|
18
|
+
if (typeof logger === "function") {
|
|
19
|
+
logger(level, getMessage(), error);
|
|
20
|
+
} else if (logger.includes(level)) {
|
|
21
|
+
const logFn = match(level).with("debug", () => console.debug).with("info", () => console.info).with("warn", () => console.warn).with("error", () => console.error).exhaustive();
|
|
22
|
+
logFn(`@zenstackhq/server: [${level}] ${getMessage()}${error ? `
|
|
23
|
+
${error}` : ""}`);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
__name(log, "log");
|
|
27
|
+
function registerCustomSerializers() {
|
|
28
|
+
SuperJSON.registerCustom({
|
|
29
|
+
isApplicable: /* @__PURE__ */ __name((v) => Decimal.isDecimal(v), "isApplicable"),
|
|
30
|
+
serialize: /* @__PURE__ */ __name((v) => v.toJSON(), "serialize"),
|
|
31
|
+
deserialize: /* @__PURE__ */ __name((v) => new Decimal(v), "deserialize")
|
|
32
|
+
}, "Decimal");
|
|
33
|
+
if (globalThis.Buffer) {
|
|
34
|
+
SuperJSON.registerCustom({
|
|
35
|
+
isApplicable: /* @__PURE__ */ __name((v) => Buffer.isBuffer(v), "isApplicable"),
|
|
36
|
+
serialize: /* @__PURE__ */ __name((v) => v.toString("base64"), "serialize"),
|
|
37
|
+
deserialize: /* @__PURE__ */ __name((v) => Buffer.from(v, "base64"), "deserialize")
|
|
38
|
+
}, "Bytes");
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
__name(registerCustomSerializers, "registerCustomSerializers");
|
|
42
|
+
|
|
43
|
+
// src/api/rpc/index.ts
|
|
44
|
+
registerCustomSerializers();
|
|
45
|
+
var RPCApiHandler = class {
|
|
46
|
+
static {
|
|
47
|
+
__name(this, "RPCApiHandler");
|
|
48
|
+
}
|
|
49
|
+
options;
|
|
50
|
+
constructor(options) {
|
|
51
|
+
this.options = options;
|
|
52
|
+
}
|
|
53
|
+
get schema() {
|
|
54
|
+
return this.options.schema;
|
|
55
|
+
}
|
|
56
|
+
async handleRequest({ client, method, path, query, requestBody }) {
|
|
57
|
+
const parts = path.split("/").filter((p) => !!p);
|
|
58
|
+
const op = parts.pop();
|
|
59
|
+
let model = parts.pop();
|
|
60
|
+
if (parts.length !== 0 || !op || !model) {
|
|
61
|
+
return this.makeBadInputErrorResponse("invalid request path");
|
|
62
|
+
}
|
|
63
|
+
model = lowerCaseFirst(model);
|
|
64
|
+
method = method.toUpperCase();
|
|
65
|
+
let args;
|
|
66
|
+
let resCode = 200;
|
|
67
|
+
switch (op) {
|
|
68
|
+
case "create":
|
|
69
|
+
case "createMany":
|
|
70
|
+
case "createManyAndReturn":
|
|
71
|
+
case "upsert":
|
|
72
|
+
if (method !== "POST") {
|
|
73
|
+
return this.makeBadInputErrorResponse("invalid request method, only POST is supported");
|
|
74
|
+
}
|
|
75
|
+
if (!requestBody) {
|
|
76
|
+
return this.makeBadInputErrorResponse("missing request body");
|
|
77
|
+
}
|
|
78
|
+
args = requestBody;
|
|
79
|
+
resCode = 201;
|
|
80
|
+
break;
|
|
81
|
+
case "findFirst":
|
|
82
|
+
case "findUnique":
|
|
83
|
+
case "findMany":
|
|
84
|
+
case "aggregate":
|
|
85
|
+
case "groupBy":
|
|
86
|
+
case "count":
|
|
87
|
+
if (method !== "GET") {
|
|
88
|
+
return this.makeBadInputErrorResponse("invalid request method, only GET is supported");
|
|
89
|
+
}
|
|
90
|
+
try {
|
|
91
|
+
args = query?.["q"] ? this.unmarshalQ(query["q"], query["meta"]) : {};
|
|
92
|
+
} catch {
|
|
93
|
+
return this.makeBadInputErrorResponse('invalid "q" query parameter');
|
|
94
|
+
}
|
|
95
|
+
break;
|
|
96
|
+
case "update":
|
|
97
|
+
case "updateMany":
|
|
98
|
+
case "updateManyAndReturn":
|
|
99
|
+
if (method !== "PUT" && method !== "PATCH") {
|
|
100
|
+
return this.makeBadInputErrorResponse("invalid request method, only PUT or PATCH are supported");
|
|
101
|
+
}
|
|
102
|
+
if (!requestBody) {
|
|
103
|
+
return this.makeBadInputErrorResponse("missing request body");
|
|
104
|
+
}
|
|
105
|
+
args = requestBody;
|
|
106
|
+
break;
|
|
107
|
+
case "delete":
|
|
108
|
+
case "deleteMany":
|
|
109
|
+
if (method !== "DELETE") {
|
|
110
|
+
return this.makeBadInputErrorResponse("invalid request method, only DELETE is supported");
|
|
111
|
+
}
|
|
112
|
+
try {
|
|
113
|
+
args = query?.["q"] ? this.unmarshalQ(query["q"], query["meta"]) : {};
|
|
114
|
+
} catch (err) {
|
|
115
|
+
return this.makeBadInputErrorResponse(err instanceof Error ? err.message : 'invalid "q" query parameter');
|
|
116
|
+
}
|
|
117
|
+
break;
|
|
118
|
+
default:
|
|
119
|
+
return this.makeBadInputErrorResponse("invalid operation: " + op);
|
|
120
|
+
}
|
|
121
|
+
const { result: processedArgs, error } = await this.processRequestPayload(args);
|
|
122
|
+
if (error) {
|
|
123
|
+
return this.makeBadInputErrorResponse(error);
|
|
124
|
+
}
|
|
125
|
+
try {
|
|
126
|
+
if (!this.isValidModel(client, model)) {
|
|
127
|
+
return this.makeBadInputErrorResponse(`unknown model name: ${model}`);
|
|
128
|
+
}
|
|
129
|
+
log(this.options.log, "debug", () => `handling "${model}.${op}" request with args: ${safeJSONStringify(processedArgs)}`);
|
|
130
|
+
const clientResult = await client[model][op](processedArgs);
|
|
131
|
+
let responseBody = {
|
|
132
|
+
data: clientResult
|
|
133
|
+
};
|
|
134
|
+
if (clientResult) {
|
|
135
|
+
const { json, meta } = SuperJSON2.serialize(clientResult);
|
|
136
|
+
responseBody = {
|
|
137
|
+
data: json
|
|
138
|
+
};
|
|
139
|
+
if (meta) {
|
|
140
|
+
responseBody.meta = {
|
|
141
|
+
serialization: meta
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
const response = {
|
|
146
|
+
status: resCode,
|
|
147
|
+
body: responseBody
|
|
148
|
+
};
|
|
149
|
+
log(this.options.log, "debug", () => `sending response for "${model}.${op}" request: ${safeJSONStringify(response)}`);
|
|
150
|
+
return response;
|
|
151
|
+
} catch (err) {
|
|
152
|
+
log(this.options.log, "error", `error occurred when handling "${model}.${op}" request`, err);
|
|
153
|
+
if (err instanceof ZenStackError) {
|
|
154
|
+
return this.makeZenStackErrorResponse(err);
|
|
155
|
+
} else {
|
|
156
|
+
return this.makeGenericErrorResponse(err);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
isValidModel(client, model) {
|
|
161
|
+
return Object.keys(client.$schema.models).some((m) => lowerCaseFirst(m) === lowerCaseFirst(model));
|
|
162
|
+
}
|
|
163
|
+
makeBadInputErrorResponse(message) {
|
|
164
|
+
const resp = {
|
|
165
|
+
status: 400,
|
|
166
|
+
body: {
|
|
167
|
+
error: {
|
|
168
|
+
message
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
log(this.options.log, "debug", () => `sending error response: ${safeJSONStringify(resp)}`);
|
|
173
|
+
return resp;
|
|
174
|
+
}
|
|
175
|
+
makeGenericErrorResponse(err) {
|
|
176
|
+
const resp = {
|
|
177
|
+
status: 500,
|
|
178
|
+
body: {
|
|
179
|
+
error: {
|
|
180
|
+
message: err.message || "unknown error"
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
};
|
|
184
|
+
log(this.options.log, "debug", () => `sending error response: ${safeJSONStringify(resp)}`);
|
|
185
|
+
return resp;
|
|
186
|
+
}
|
|
187
|
+
makeZenStackErrorResponse(err) {
|
|
188
|
+
let status = 400;
|
|
189
|
+
const error = {
|
|
190
|
+
message: err.message
|
|
191
|
+
};
|
|
192
|
+
if (err.cause && err.cause instanceof Error) {
|
|
193
|
+
error.cause = err.cause.message;
|
|
194
|
+
}
|
|
195
|
+
if (err instanceof NotFoundError) {
|
|
196
|
+
status = 404;
|
|
197
|
+
error.model = err.model;
|
|
198
|
+
} else if (err instanceof InputValidationError) {
|
|
199
|
+
status = 422;
|
|
200
|
+
error.rejectedByValidation = true;
|
|
201
|
+
error.model = err.model;
|
|
202
|
+
} else if (err instanceof RejectedByPolicyError) {
|
|
203
|
+
status = 403;
|
|
204
|
+
error.rejectedByPolicy = true;
|
|
205
|
+
error.rejectReason = err.reason;
|
|
206
|
+
error.model = err.model;
|
|
207
|
+
}
|
|
208
|
+
const resp = {
|
|
209
|
+
status,
|
|
210
|
+
body: {
|
|
211
|
+
error
|
|
212
|
+
}
|
|
213
|
+
};
|
|
214
|
+
log(this.options.log, "debug", () => `sending error response: ${safeJSONStringify(resp)}`);
|
|
215
|
+
return resp;
|
|
216
|
+
}
|
|
217
|
+
async processRequestPayload(args) {
|
|
218
|
+
const { meta, ...rest } = args;
|
|
219
|
+
if (meta?.serialization) {
|
|
220
|
+
try {
|
|
221
|
+
args = SuperJSON2.deserialize({
|
|
222
|
+
json: rest,
|
|
223
|
+
meta: meta.serialization
|
|
224
|
+
});
|
|
225
|
+
} catch (err) {
|
|
226
|
+
return {
|
|
227
|
+
result: void 0,
|
|
228
|
+
error: `failed to deserialize request payload: ${err.message}`
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
return {
|
|
233
|
+
result: args,
|
|
234
|
+
error: void 0
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
unmarshalQ(value, meta) {
|
|
238
|
+
let parsedValue;
|
|
239
|
+
try {
|
|
240
|
+
parsedValue = JSON.parse(value);
|
|
241
|
+
} catch {
|
|
242
|
+
throw new Error('invalid "q" query parameter');
|
|
243
|
+
}
|
|
244
|
+
if (meta) {
|
|
245
|
+
let parsedMeta;
|
|
246
|
+
try {
|
|
247
|
+
parsedMeta = JSON.parse(meta);
|
|
248
|
+
} catch {
|
|
249
|
+
throw new Error('invalid "meta" query parameter');
|
|
250
|
+
}
|
|
251
|
+
if (parsedMeta.serialization) {
|
|
252
|
+
return SuperJSON2.deserialize({
|
|
253
|
+
json: parsedValue,
|
|
254
|
+
meta: parsedMeta.serialization
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
return parsedValue;
|
|
259
|
+
}
|
|
260
|
+
};
|
|
261
|
+
export {
|
|
262
|
+
RPCApiHandler
|
|
263
|
+
};
|
|
264
|
+
//# sourceMappingURL=api.js.map
|