omni-rest 0.4.5 → 0.4.7
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/adapters/express.d.mts +1 -1
- package/dist/adapters/express.d.ts +1 -1
- package/dist/adapters/express.js +76 -10
- package/dist/adapters/express.js.map +1 -1
- package/dist/adapters/express.mjs +76 -10
- package/dist/adapters/express.mjs.map +1 -1
- package/dist/adapters/fastify.d.mts +1 -1
- package/dist/adapters/fastify.d.ts +1 -1
- package/dist/adapters/fastify.js +76 -10
- package/dist/adapters/fastify.js.map +1 -1
- package/dist/adapters/fastify.mjs +76 -10
- package/dist/adapters/fastify.mjs.map +1 -1
- package/dist/adapters/nextjs.d.mts +1 -1
- package/dist/adapters/nextjs.d.ts +1 -1
- package/dist/adapters/nextjs.js +76 -10
- package/dist/adapters/nextjs.js.map +1 -1
- package/dist/adapters/nextjs.mjs +76 -10
- package/dist/adapters/nextjs.mjs.map +1 -1
- package/dist/index.d.mts +19 -8
- package/dist/index.d.ts +19 -8
- package/dist/index.js +76 -10
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +76 -10
- package/dist/index.mjs.map +1 -1
- package/dist/{types-BURph8D5.d.mts → types-_OVcoAZ9.d.mts} +18 -0
- package/dist/{types-BURph8D5.d.ts → types-_OVcoAZ9.d.ts} +18 -0
- package/package.json +1 -1
package/dist/adapters/express.js
CHANGED
|
@@ -168,6 +168,48 @@ function buildQuery(searchParams, defaultLimit = 20, maxLimit = 100, modelFields
|
|
|
168
168
|
}
|
|
169
169
|
for (const [key, value] of searchParams.entries()) {
|
|
170
170
|
if (RESERVED_KEYS.has(key)) continue;
|
|
171
|
+
const dotIndex = key.indexOf(".");
|
|
172
|
+
if (dotIndex > 0 && key.indexOf(".", dotIndex + 1) === -1 && modelFields) {
|
|
173
|
+
const relationName = key.slice(0, dotIndex);
|
|
174
|
+
const fieldPart = key.slice(dotIndex + 1);
|
|
175
|
+
const relationMeta = modelFields.find(
|
|
176
|
+
(f) => f.name === relationName && f.isRelation
|
|
177
|
+
);
|
|
178
|
+
if (relationMeta) {
|
|
179
|
+
const sortedOps2 = Object.keys(FILTER_OPERATORS).sort(
|
|
180
|
+
(a, b) => b.length - a.length
|
|
181
|
+
);
|
|
182
|
+
let fieldName = fieldPart;
|
|
183
|
+
let fieldFilter;
|
|
184
|
+
let opMatched = false;
|
|
185
|
+
for (const suffix of sortedOps2) {
|
|
186
|
+
if (fieldPart.endsWith(suffix)) {
|
|
187
|
+
fieldName = fieldPart.slice(0, -suffix.length);
|
|
188
|
+
const prismaOp = FILTER_OPERATORS[suffix];
|
|
189
|
+
let parsedValue = value;
|
|
190
|
+
if (prismaOp === "in" || prismaOp === "notIn") {
|
|
191
|
+
parsedValue = value.split(",").map((v) => v.trim());
|
|
192
|
+
} else if (!isNaN(Number(parsedValue)) && typeof parsedValue === "string") {
|
|
193
|
+
parsedValue = Number(parsedValue);
|
|
194
|
+
}
|
|
195
|
+
const extra = suffix === "_icontains" ? { mode: "insensitive" } : {};
|
|
196
|
+
fieldFilter = { [prismaOp]: parsedValue, ...extra };
|
|
197
|
+
opMatched = true;
|
|
198
|
+
break;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
if (!opMatched) {
|
|
202
|
+
let parsedValue = value;
|
|
203
|
+
if (value === "true") parsedValue = true;
|
|
204
|
+
else if (value === "false") parsedValue = false;
|
|
205
|
+
else if (!isNaN(Number(value)) && value !== "") parsedValue = Number(value);
|
|
206
|
+
fieldFilter = parsedValue;
|
|
207
|
+
}
|
|
208
|
+
const nested = { [fieldName]: fieldFilter };
|
|
209
|
+
where[relationName] = relationMeta.isList ? { some: nested } : nested;
|
|
210
|
+
continue;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
171
213
|
let matched = false;
|
|
172
214
|
const sortedOps = Object.keys(FILTER_OPERATORS).sort(
|
|
173
215
|
(a, b) => b.length - a.length
|
|
@@ -230,7 +272,8 @@ function createRouter(prisma, options = {}) {
|
|
|
230
272
|
maxLimit = 100,
|
|
231
273
|
softDelete = false,
|
|
232
274
|
softDeleteField,
|
|
233
|
-
envelope = true
|
|
275
|
+
envelope = true,
|
|
276
|
+
fieldGuards = {}
|
|
234
277
|
} = options;
|
|
235
278
|
const models = getModels(prisma);
|
|
236
279
|
const modelMap = buildModelMap(models, allow);
|
|
@@ -267,7 +310,8 @@ function createRouter(prisma, options = {}) {
|
|
|
267
310
|
operation,
|
|
268
311
|
softDelete,
|
|
269
312
|
softDeleteField,
|
|
270
|
-
envelope
|
|
313
|
+
envelope,
|
|
314
|
+
fieldGuards[meta.routeName]
|
|
271
315
|
);
|
|
272
316
|
} catch (e) {
|
|
273
317
|
return handlePrismaError(e);
|
|
@@ -283,7 +327,7 @@ function createRouter(prisma, options = {}) {
|
|
|
283
327
|
}
|
|
284
328
|
return { handle, modelMap, models };
|
|
285
329
|
}
|
|
286
|
-
async function executeOperation(prisma, meta, method, id, body, searchParams, defaultLimit, maxLimit, operation, softDelete = false, softDeleteField, envelope = true) {
|
|
330
|
+
async function executeOperation(prisma, meta, method, id, body, searchParams, defaultLimit, maxLimit, operation, softDelete = false, softDeleteField, envelope = true, fg) {
|
|
287
331
|
const delegate = getDelegate(prisma, meta);
|
|
288
332
|
const { where, orderBy, skip, take, include, select } = buildQuery(
|
|
289
333
|
searchParams,
|
|
@@ -294,6 +338,7 @@ async function executeOperation(prisma, meta, method, id, body, searchParams, de
|
|
|
294
338
|
const includeArg = Object.keys(include).length > 0 ? include : void 0;
|
|
295
339
|
const selectArg = select && Object.keys(select).length > 0 ? select : void 0;
|
|
296
340
|
const projection = selectArg ? { select: selectArg } : includeArg ? { include: includeArg } : {};
|
|
341
|
+
const safeBody = sanitizeBody(body, fg);
|
|
297
342
|
if (method === "PATCH" && operation === "bulk-update") {
|
|
298
343
|
if (!Array.isArray(body) || body.length === 0) {
|
|
299
344
|
return {
|
|
@@ -358,17 +403,18 @@ async function executeOperation(prisma, meta, method, id, body, searchParams, de
|
|
|
358
403
|
delegate.findMany({ where: listWhere, orderBy, skip, take, ...projection }),
|
|
359
404
|
delegate.count({ where: listWhere })
|
|
360
405
|
]);
|
|
406
|
+
const safeData = data.map((r) => stripResponse(r, fg));
|
|
361
407
|
if (!envelope) {
|
|
362
408
|
return {
|
|
363
409
|
status: 200,
|
|
364
|
-
data,
|
|
410
|
+
data: safeData,
|
|
365
411
|
headers: { "X-Total-Count": String(total) }
|
|
366
412
|
};
|
|
367
413
|
}
|
|
368
414
|
return {
|
|
369
415
|
status: 200,
|
|
370
416
|
data: {
|
|
371
|
-
data,
|
|
417
|
+
data: safeData,
|
|
372
418
|
meta: {
|
|
373
419
|
total,
|
|
374
420
|
page: Math.floor(skip / take) + 1,
|
|
@@ -386,18 +432,18 @@ async function executeOperation(prisma, meta, method, id, body, searchParams, de
|
|
|
386
432
|
if (!record) {
|
|
387
433
|
return { status: 404, data: { error: `${meta.name} with id "${id}" not found.` } };
|
|
388
434
|
}
|
|
389
|
-
return { status: 200, data: record };
|
|
435
|
+
return { status: 200, data: stripResponse(record, fg) };
|
|
390
436
|
}
|
|
391
437
|
if (method === "POST" && !id) {
|
|
392
|
-
const record = await delegate.create({ data:
|
|
393
|
-
return { status: 201, data: record };
|
|
438
|
+
const record = await delegate.create({ data: safeBody });
|
|
439
|
+
return { status: 201, data: stripResponse(record, fg) };
|
|
394
440
|
}
|
|
395
441
|
if ((method === "PUT" || method === "PATCH") && id) {
|
|
396
442
|
const record = await delegate.update({
|
|
397
443
|
where: { [meta.idField]: coerceId(id) },
|
|
398
|
-
data:
|
|
444
|
+
data: safeBody
|
|
399
445
|
});
|
|
400
|
-
return { status: 200, data: record };
|
|
446
|
+
return { status: 200, data: stripResponse(record, fg) };
|
|
401
447
|
}
|
|
402
448
|
if (method === "DELETE" && id) {
|
|
403
449
|
const softDeleteInfo = softDelete ? detectSoftDeleteField(meta.fields, softDeleteField) : null;
|
|
@@ -415,6 +461,26 @@ async function executeOperation(prisma, meta, method, id, body, searchParams, de
|
|
|
415
461
|
}
|
|
416
462
|
return { status: 405, data: { error: `Method ${method} not allowed.` } };
|
|
417
463
|
}
|
|
464
|
+
function stripResponse(record, fg) {
|
|
465
|
+
if (!fg || !record || typeof record !== "object") return record;
|
|
466
|
+
const blocked = /* @__PURE__ */ new Set([...fg.hidden ?? [], ...fg.writeOnly ?? []]);
|
|
467
|
+
if (blocked.size === 0) return record;
|
|
468
|
+
const out = {};
|
|
469
|
+
for (const [k, v] of Object.entries(record)) {
|
|
470
|
+
if (!blocked.has(k)) out[k] = v;
|
|
471
|
+
}
|
|
472
|
+
return out;
|
|
473
|
+
}
|
|
474
|
+
function sanitizeBody(body, fg) {
|
|
475
|
+
if (!fg || !body || typeof body !== "object" || Array.isArray(body)) return body;
|
|
476
|
+
const readOnly = new Set(fg.readOnly ?? []);
|
|
477
|
+
if (readOnly.size === 0) return body;
|
|
478
|
+
const out = {};
|
|
479
|
+
for (const [k, v] of Object.entries(body)) {
|
|
480
|
+
if (!readOnly.has(k)) out[k] = v;
|
|
481
|
+
}
|
|
482
|
+
return out;
|
|
483
|
+
}
|
|
418
484
|
function coerceId(id) {
|
|
419
485
|
const n = Number(id);
|
|
420
486
|
return isNaN(n) ? id : n;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/introspect.ts","../../src/query-builder.ts","../../src/middleware-helpers.ts","../../src/router.ts","../../src/adapters/express.ts"],"names":["f","id"],"mappings":"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","file":"express.js","sourcesContent":["import type { ModelMeta, FieldMeta } from \"./types\";\r\n\r\n/**\r\n * Reads Prisma's DMMF (Data Model Meta Format) at runtime\r\n * and returns structured metadata for every model in your schema.\r\n *\r\n * No file reading. No code generation. Pure runtime introspection.\r\n */\r\nexport function getModels(prisma?: any): ModelMeta[] {\r\n let raw: any[] | undefined;\r\n\r\n // Try to get from PrismaClient instance first (new v5 format)\r\n if (prisma?._runtimeDataModel?.models) {\r\n const modelsObj = prisma._runtimeDataModel.models;\r\n // Convert object to array, adding name from key\r\n raw = Object.entries(modelsObj).map(([name, model]: [string, any]) => ({\r\n name,\r\n ...model,\r\n fields: (model.fields || []).map((f: any) => ({\r\n ...f,\r\n relationName: f.kind === \"object\" ? f.name : undefined,\r\n })),\r\n }));\r\n }\r\n\r\n // Fallback to DMMF from @prisma/client module — use plain require so\r\n // vi.mock(\"@prisma/client\") can intercept it in tests. The CLI handles\r\n // the global-install path resolution separately before calling getModels.\r\n if (!raw) {\r\n try {\r\n // eslint-disable-next-line @typescript-eslint/no-var-requires\r\n const prismaModule = require(\"@prisma/client\");\r\n const dmmfModels = prismaModule?.Prisma?.dmmf?.datamodel?.models;\r\n if (dmmfModels) {\r\n raw = dmmfModels;\r\n }\r\n } catch {\r\n // @prisma/client not available or not generated — handled below\r\n }\r\n }\r\n\r\n if (!raw) {\r\n throw new Error(\r\n \"[omni-rest] Could not find Prisma DMMF. Ensure Prisma client is generated and you're passing a PrismaClient instance to omni-rest.\"\r\n );\r\n }\r\n\r\n if (!Array.isArray(raw)) {\r\n throw new Error(\r\n `[omni-rest] Expected models to be an array, got ${typeof raw}. Debug: prisma._runtimeDataModel.models=${!!prisma?._runtimeDataModel?.models}, raw value=${JSON.stringify(raw).slice(0, 100)}`\r\n );\r\n }\r\n\r\n return raw.map((model: any) => {\r\n const fields: FieldMeta[] = model.fields.map((f: any) => ({\r\n name: f.name,\r\n type: f.type,\r\n isId: f.isId,\r\n isRequired: f.isRequired,\r\n isList: f.isList,\r\n isRelation: !!f.relationName,\r\n hasDefaultValue: !!f.hasDefaultValue || !!f.default,\r\n isUpdatedAt: !!f.isUpdatedAt,\r\n }));\r\n\r\n const idField =\r\n model.fields.find((f: any) => f.isId)?.name ?? \"id\";\r\n\r\n return {\r\n name: model.name,\r\n routeName: toRouteName(model.name),\r\n fields,\r\n idField,\r\n };\r\n });\r\n}\r\n\r\n/**\r\n * Converts a Prisma model name to a URL-safe route segment.\r\n * \"UserProfile\" → \"userprofile\"\r\n * \"OrderItem\" → \"orderitem\"\r\n */\r\nexport function toRouteName(modelName: string): string {\r\n return modelName.toLowerCase();\r\n}\r\n\r\n/**\r\n * Returns a map of routeName → ModelMeta for O(1) lookups.\r\n */\r\nexport function buildModelMap(\r\n models: ModelMeta[],\r\n allowList?: string[]\r\n): Record<string, ModelMeta> {\r\n const filtered = allowList\r\n ? models.filter((m) => allowList.includes(m.routeName))\r\n : models;\r\n\r\n return Object.fromEntries(filtered.map((m) => [m.routeName, m]));\r\n}\r\n\r\n/**\r\n * Auto-detects the soft-delete field for a model.\r\n * Returns the field name and the value to set, or null if not found.\r\n *\r\n * Priority:\r\n * 1. Explicit `softDeleteField` option\r\n * 2. A field named \"deletedAt\" with type DateTime\r\n * 3. A field named \"isActive\" with type Boolean\r\n */\r\nexport function detectSoftDeleteField(\r\n fields: FieldMeta[],\r\n explicitField?: string\r\n): { field: string; value: Date | boolean } | null {\r\n if (explicitField) {\r\n const f = fields.find((f) => f.name === explicitField);\r\n if (!f) return null;\r\n // Infer value from type\r\n const value = f.type === \"Boolean\" ? false : new Date();\r\n return { field: explicitField, value };\r\n }\r\n\r\n const deletedAt = fields.find((f) => f.name === \"deletedAt\" && f.type === \"DateTime\");\r\n if (deletedAt) return { field: \"deletedAt\", value: new Date() };\r\n\r\n const isActive = fields.find((f) => f.name === \"isActive\" && f.type === \"Boolean\");\r\n if (isActive) return { field: \"isActive\", value: false };\r\n\r\n return null;\r\n}\r\n\r\n/**\r\n * Gets the Prisma client delegate for a model.\r\n * prisma[\"userProfile\"] or prisma[\"user\"] — handles camelCase.\r\n */\r\nexport function getDelegate(prisma: any, meta: ModelMeta): any {\r\n // Prisma client properties are camelCase of model name\r\n // \"UserProfile\" → \"userProfile\"\r\n const key =\r\n meta.name.charAt(0).toLowerCase() + meta.name.slice(1);\r\n const delegate = prisma[key];\r\n\r\n if (!delegate) {\r\n throw new Error(\r\n `Could not find Prisma delegate for model \"${meta.name}\". ` +\r\n `Expected prisma.${key} to exist.`\r\n );\r\n }\r\n\r\n return delegate;\r\n}","import type { ParsedQuery, FieldMeta } from \"./types\";\r\n\r\n/**\r\n * Supported filter suffixes and their Prisma equivalents.\r\n *\r\n * Usage in URL:\r\n * ?name_contains=john\r\n * ?age_gte=18\r\n * ?status_in=active,inactive\r\n * ?email_not=test@test.com\r\n */\r\nconst FILTER_OPERATORS: Record<string, string> = {\r\n _gte: \"gte\",\r\n _lte: \"lte\",\r\n _gt: \"gt\",\r\n _lt: \"lt\",\r\n _contains: \"contains\",\r\n _icontains: \"contains\", // case-insensitive version (mode: insensitive)\r\n _startsWith: \"startsWith\",\r\n _endsWith: \"endsWith\",\r\n _in: \"in\",\r\n _notIn: \"notIn\",\r\n _not: \"not\",\r\n};\r\n\r\n/**\r\n * Reserved query keys — these are NOT treated as filters.\r\n */\r\nconst RESERVED_KEYS = new Set([\r\n \"page\",\r\n \"limit\",\r\n \"sort\",\r\n \"include\",\r\n \"select\",\r\n \"fields\",\r\n \"search\",\r\n]);\r\n\r\n/**\r\n * Parses URLSearchParams into a full Prisma query object.\r\n *\r\n * Supports:\r\n * Filtering → ?name=John ?age_gte=18 ?status_in=a,b\r\n * Sorting → ?sort=createdAt:desc or ?sort=name:asc\r\n * Pagination → ?page=2&limit=10\r\n * Relations → ?include=posts,profile\r\n * Fields → ?select=id,name,email or ?fields=id,name,email\r\n * Search → ?search=eng (queries all String fields with OR)\r\n */\r\nexport function buildQuery(\r\n searchParams: URLSearchParams,\r\n defaultLimit = 20,\r\n maxLimit = 100,\r\n modelFields?: FieldMeta[]\r\n): ParsedQuery {\r\n const where: Record<string, any> = {};\r\n const orderBy: Record<string, any> = {};\r\n let include: Record<string, boolean> = {};\r\n let select: Record<string, boolean> | null = null;\r\n\r\n // ─── Pagination ────────────────────────────────────────────────────────────\r\n const page = Math.max(1, parseInt(searchParams.get(\"page\") ?? \"1\"));\r\n const rawLimit = parseInt(searchParams.get(\"limit\") ?? String(defaultLimit));\r\n const take = Math.min(rawLimit, maxLimit);\r\n const skip = (page - 1) * take;\r\n\r\n // ─── Sort ──────────────────────────────────────────────────────────────────\r\n const sortParam = searchParams.get(\"sort\");\r\n if (sortParam) {\r\n // Supports multiple sorts: ?sort=name:asc,createdAt:desc,_count.posts:desc\r\n for (const part of sortParam.split(\",\")) {\r\n const [field, dir] = part.trim().split(\":\");\r\n if (!field) continue;\r\n const direction = dir === \"desc\" ? \"desc\" : \"asc\";\r\n\r\n // _count.relation:dir → { relation: { _count: dir } }\r\n if (field.startsWith(\"_count.\")) {\r\n const relation = field.slice(\"_count.\".length);\r\n if (relation) {\r\n orderBy[relation] = { _count: direction };\r\n }\r\n } else {\r\n orderBy[field] = direction;\r\n }\r\n }\r\n }\r\n\r\n // ─── Include (relations) ───────────────────────────────────────────────────\r\n const includeParam = searchParams.get(\"include\");\r\n if (includeParam) {\r\n for (const rel of includeParam.split(\",\")) {\r\n if (rel.trim()) include[rel.trim()] = true;\r\n }\r\n }\r\n\r\n // ─── Select (fields) — ?select= or ?fields= alias ────────────────────────\r\n const selectParam = searchParams.get(\"select\") ?? searchParams.get(\"fields\");\r\n if (selectParam) {\r\n select = {};\r\n for (const field of selectParam.split(\",\")) {\r\n if (field.trim()) select[field.trim()] = true;\r\n }\r\n }\r\n\r\n // ─── Global search (?search=) ─────────────────────────────────────────────\r\n const searchValue = searchParams.get(\"search\");\r\n if (searchValue && modelFields) {\r\n const stringFields = modelFields.filter(\r\n (f) => f.type === \"String\" && !f.isRelation\r\n );\r\n if (stringFields.length > 0) {\r\n const orClauses = stringFields.map((f) => ({\r\n [f.name]: { contains: searchValue, mode: \"insensitive\" },\r\n }));\r\n // Merge with any existing where via AND so other filters still apply\r\n where[\"OR\"] = orClauses;\r\n }\r\n }\r\n\r\n // ─── Filters ───────────────────────────────────────────────────────────────\r\n for (const [key, value] of searchParams.entries()) {\r\n if (RESERVED_KEYS.has(key)) continue;\r\n\r\n // Check operator suffixes (longest match first)\r\n let matched = false;\r\n const sortedOps = Object.keys(FILTER_OPERATORS).sort(\r\n (a, b) => b.length - a.length\r\n );\r\n\r\n for (const suffix of sortedOps) {\r\n if (key.endsWith(suffix)) {\r\n const field = key.slice(0, -suffix.length);\r\n const prismaOp = FILTER_OPERATORS[suffix];\r\n\r\n let parsedValue: any = value;\r\n\r\n // Parse arrays\r\n if (prismaOp === \"in\" || prismaOp === \"notIn\") {\r\n parsedValue = value.split(\",\").map((v) => v.trim());\r\n }\r\n\r\n // Attempt numeric coercion\r\n if (!isNaN(Number(parsedValue)) && typeof parsedValue === \"string\") {\r\n parsedValue = Number(parsedValue);\r\n }\r\n\r\n // Case-insensitive flag\r\n const extra = suffix === \"_icontains\" ? { mode: \"insensitive\" } : {};\r\n\r\n where[field] = { [prismaOp]: parsedValue, ...extra };\r\n matched = true;\r\n break;\r\n }\r\n }\r\n\r\n // No operator — exact match\r\n if (!matched) {\r\n let parsedValue: any = value;\r\n\r\n // Coerce booleans\r\n if (value === \"true\") parsedValue = true;\r\n else if (value === \"false\") parsedValue = false;\r\n // Coerce numbers\r\n else if (!isNaN(Number(value)) && value !== \"\") {\r\n parsedValue = Number(value);\r\n }\r\n\r\n where[key] = parsedValue;\r\n }\r\n }\r\n\r\n return { where, orderBy, skip, take, include, select };\r\n}","import type { GuardMap, HookFn, HookContext } from \"./types\";\r\n\r\n/**\r\n * Runs the guard for the given model+method combo.\r\n * Returns an error string if blocked, null if allowed.\r\n */\r\nexport async function runGuard(\r\n guards: GuardMap,\r\n model: string,\r\n method: string,\r\n ctx: { id?: string | null; body?: any }\r\n): Promise<string | null> {\r\n const modelGuards = guards[model];\r\n if (!modelGuards) return null;\r\n\r\n const fn = modelGuards[method as keyof typeof modelGuards];\r\n if (!fn) return null;\r\n\r\n return fn({ ...ctx, method });\r\n}\r\n\r\n/**\r\n * Runs a lifecycle hook (beforeOperation / afterOperation).\r\n * Silently swallows errors so hooks never crash the main flow.\r\n */\r\nexport async function runHook(\r\n hook: HookFn | undefined,\r\n ctx: HookContext\r\n): Promise<void> {\r\n if (!hook) return;\r\n try {\r\n await hook(ctx);\r\n } catch (e) {\r\n // Hooks should not crash the request\r\n console.error(\"[omni-rest] Hook error:\", e);\r\n }\r\n}","import { PrismaClient } from \"@prisma/client\";\r\nimport { getModels, buildModelMap, getDelegate, detectSoftDeleteField } from \"./introspect\";\r\nimport { buildQuery } from \"./query-builder\";\r\nimport { runGuard, runHook } from \"./middleware-helpers\";\r\nimport type {\r\n PrismaRestOptions,\r\n HandlerResult,\r\n RouterInstance,\r\n ModelMeta,\r\n} from \"./types\";\r\n\r\n/**\r\n * Creates a framework-agnostic CRUD router powered by Prisma DMMF.\r\n *\r\n * All adapters (Express, Next.js, Fastify) use this under the hood.\r\n */\r\nexport function createRouter(\r\n prisma: PrismaClient,\r\n options: PrismaRestOptions = {}\r\n): RouterInstance {\r\n const {\r\n allow,\r\n guards = {},\r\n beforeOperation,\r\n afterOperation,\r\n defaultLimit = 20,\r\n maxLimit = 100,\r\n softDelete = false,\r\n softDeleteField,\r\n envelope = true,\r\n } = options;\r\n\r\n // Introspect schema once at startup\r\n const models = getModels(prisma);\r\n const modelMap = buildModelMap(models, allow);\r\n\r\n async function handle(\r\n method: string,\r\n modelName: string,\r\n id: string | null,\r\n body: any,\r\n searchParams: URLSearchParams,\r\n operation?: string\r\n ): Promise<HandlerResult> {\r\n // ── 1. Resolve model ───────────────────────────────────────────────────\r\n const meta = modelMap[modelName.toLowerCase()];\r\n if (!meta) {\r\n return {\r\n status: 404,\r\n data: {\r\n error: `Model \"${modelName}\" not found or not exposed.`,\r\n available: Object.keys(modelMap),\r\n },\r\n };\r\n }\r\n\r\n // ── 2. Guard check ─────────────────────────────────────────────────────\r\n const guardError = await runGuard(guards, meta.routeName, method, {\r\n id,\r\n body,\r\n });\r\n if (guardError) {\r\n return { status: 403, data: { error: guardError } };\r\n }\r\n\r\n // ── 3. Before hook ─────────────────────────────────────────────────────\r\n await runHook(beforeOperation, { model: meta.name, method, id, body });\r\n\r\n // ── 4. Execute operation ───────────────────────────────────────────────\r\n let result: HandlerResult;\r\n\r\n try {\r\n result = await executeOperation(\r\n prisma,\r\n meta,\r\n method,\r\n id,\r\n body,\r\n searchParams,\r\n defaultLimit,\r\n maxLimit,\r\n operation,\r\n softDelete,\r\n softDeleteField,\r\n envelope\r\n );\r\n } catch (e: any) {\r\n return handlePrismaError(e);\r\n }\r\n\r\n // ── 5. After hook ──────────────────────────────────────────────────────\r\n await runHook(afterOperation, {\r\n model: meta.name,\r\n method,\r\n id,\r\n body,\r\n result: result.data,\r\n });\r\n\r\n return result;\r\n }\r\n\r\n return { handle, modelMap, models };\r\n}\r\n\r\n// ─── Operation executor ────────────────────────────────────────────────────────\r\n\r\nasync function executeOperation(\r\n prisma: PrismaClient,\r\n meta: ModelMeta,\r\n method: string,\r\n id: string | null,\r\n body: any,\r\n searchParams: URLSearchParams,\r\n defaultLimit: number,\r\n maxLimit: number,\r\n operation?: string,\r\n softDelete = false,\r\n softDeleteField?: string,\r\n envelope = true\r\n): Promise<HandlerResult> {\r\n const delegate = getDelegate(prisma, meta);\r\n const { where, orderBy, skip, take, include, select } = buildQuery(\r\n searchParams,\r\n defaultLimit,\r\n maxLimit,\r\n meta.fields\r\n );\r\n\r\n // Build include/select args (mutually exclusive in Prisma)\r\n const includeArg =\r\n Object.keys(include).length > 0 ? include : undefined;\r\n const selectArg =\r\n select && Object.keys(select).length > 0 ? select : undefined;\r\n const projection = selectArg ? { select: selectArg } : includeArg ? { include: includeArg } : {};\r\n\r\n // ── PATCH /model/bulk/update ────────────────────────────────────────────────\r\n if (method === \"PATCH\" && operation === \"bulk-update\") {\r\n if (!Array.isArray(body) || body.length === 0) {\r\n return {\r\n status: 400,\r\n data: { error: \"Request body must be a non-empty array of update records\" },\r\n };\r\n }\r\n\r\n // Validate that each item has an id\r\n for (const item of body) {\r\n if (!item[meta.idField]) {\r\n return {\r\n status: 400,\r\n data: { error: `Each record must have an ${meta.idField} field` },\r\n };\r\n }\r\n }\r\n\r\n // Execute updates in parallel\r\n const results = await Promise.all(\r\n body.map((item) => {\r\n const id = item[meta.idField];\r\n const updateData = { ...item };\r\n delete updateData[meta.idField]; // Remove id from update data\r\n\r\n return delegate.update({\r\n where: { [meta.idField]: coerceId(id) },\r\n data: updateData,\r\n ...projection,\r\n });\r\n })\r\n );\r\n\r\n return {\r\n status: 200,\r\n data: {\r\n updated: results.length,\r\n records: results,\r\n },\r\n };\r\n }\r\n\r\n // ── DELETE /model/bulk/delete ──────────────────────────────────────────────\r\n if (method === \"DELETE\" && operation === \"bulk-delete\") {\r\n if (!Array.isArray(body) || body.length === 0) {\r\n return {\r\n status: 400,\r\n data: { error: \"Request body must be a non-empty array of IDs\" },\r\n };\r\n }\r\n\r\n // Handle both array of IDs and array of objects with id field\r\n const ids = body.map((item: any) =>\r\n typeof item === \"object\" ? item[meta.idField] : item\r\n );\r\n\r\n // Use deleteMany with a where clause\r\n const result = await delegate.deleteMany({\r\n where: {\r\n [meta.idField]: { in: ids.map(coerceId) },\r\n },\r\n });\r\n\r\n return {\r\n status: 200,\r\n data: {\r\n deleted: result.count,\r\n },\r\n };\r\n }\r\n\r\n // ── GET /model ─────────────────────────────────────────────────────────────\r\n if (method === \"GET\" && !id) {\r\n // Exclude soft-deleted records from list when soft delete is active\r\n const softDeleteInfo = softDelete\r\n ? detectSoftDeleteField(meta.fields, softDeleteField)\r\n : null;\r\n const listWhere = softDeleteInfo\r\n ? { ...where, [softDeleteInfo.field]: softDeleteInfo.field === \"isActive\" ? true : null }\r\n : where;\r\n\r\n const [data, total] = await (prisma as any).$transaction([\r\n delegate.findMany({ where: listWhere, orderBy, skip, take, ...projection }),\r\n delegate.count({ where: listWhere }),\r\n ]);\r\n\r\n if (!envelope) {\r\n return {\r\n status: 200,\r\n data,\r\n headers: { \"X-Total-Count\": String(total) },\r\n };\r\n }\r\n\r\n return {\r\n status: 200,\r\n data: {\r\n data,\r\n meta: {\r\n total,\r\n page: Math.floor(skip / take) + 1,\r\n limit: take,\r\n totalPages: Math.ceil(total / take),\r\n },\r\n },\r\n };\r\n }\r\n\r\n // ── GET /model/:id ─────────────────────────────────────────────────────────\r\n if (method === \"GET\" && id) {\r\n const record = await delegate.findUnique({\r\n where: { [meta.idField]: coerceId(id) },\r\n ...projection,\r\n });\r\n\r\n if (!record) {\r\n return { status: 404, data: { error: `${meta.name} with id \"${id}\" not found.` } };\r\n }\r\n\r\n return { status: 200, data: record };\r\n }\r\n\r\n // ── POST /model ────────────────────────────────────────────────────────────\r\n if (method === \"POST\" && !id) {\r\n const record = await delegate.create({ data: body });\r\n return { status: 201, data: record };\r\n }\r\n\r\n // ── PUT /model/:id ─────────────────────────────────────────────────────────\r\n if ((method === \"PUT\" || method === \"PATCH\") && id) {\r\n const record = await delegate.update({\r\n where: { [meta.idField]: coerceId(id) },\r\n data: body,\r\n });\r\n return { status: 200, data: record };\r\n }\r\n\r\n // ── DELETE /model/:id ──────────────────────────────────────────────────────\r\n if (method === \"DELETE\" && id) {\r\n const softDeleteInfo = softDelete\r\n ? detectSoftDeleteField(meta.fields, softDeleteField)\r\n : null;\r\n\r\n if (softDeleteInfo) {\r\n const record = await delegate.update({\r\n where: { [meta.idField]: coerceId(id) },\r\n data: { [softDeleteInfo.field]: softDeleteInfo.value },\r\n });\r\n return { status: 200, data: record };\r\n }\r\n\r\n await delegate.delete({\r\n where: { [meta.idField]: coerceId(id) },\r\n });\r\n return { status: 204, data: null };\r\n }\r\n\r\n return { status: 405, data: { error: `Method ${method} not allowed.` } };\r\n}\r\n\r\n// ─── Helpers ──────────────────────────────────────────────────────────────────\r\n\r\n/**\r\n * Coerces an ID string to number when possible (common with Int PKs).\r\n */\r\nfunction coerceId(id: string): string | number {\r\n const n = Number(id);\r\n return isNaN(n) ? id : n;\r\n}\r\n\r\n/**\r\n * Maps Prisma error codes to meaningful HTTP responses.\r\n */\r\nfunction handlePrismaError(e: any): HandlerResult {\r\n const code = e?.code;\r\n\r\n if (code === \"P2025\") {\r\n return { status: 404, data: { error: \"Record not found.\" } };\r\n }\r\n if (code === \"P2002\") {\r\n const fields = e?.meta?.target ?? \"unknown fields\";\r\n return {\r\n status: 409,\r\n data: { error: `Unique constraint failed on: ${fields}` },\r\n };\r\n }\r\n if (code === \"P2003\") {\r\n return { status: 400, data: { error: \"Foreign key constraint failed.\" } };\r\n }\r\n if (code === \"P2014\") {\r\n return { status: 400, data: { error: \"Relation violation.\" } };\r\n }\r\n\r\n return { status: 500, data: { error: e?.message ?? \"Internal server error.\" } };\r\n}","import { PrismaClient } from \"@prisma/client\";\r\nimport { createRouter } from \"../router\";\r\nimport type { PrismaRestOptions } from \"../types\";\r\n\r\n/**\r\n * Express adapter for omni-rest.\r\n *\r\n * @example\r\n * ```ts\r\n * import express from \"express\";\r\n * import { PrismaClient } from \"@prisma/client\";\r\n * import { expressAdapter } from \"omni-rest/express\";\r\n *\r\n * const app = express();\r\n * const prisma = new PrismaClient();\r\n *\r\n * app.use(express.json());\r\n * app.use(\"/api\", expressAdapter(prisma, {\r\n * allow: [\"department\", \"category\", \"city\"],\r\n * }));\r\n *\r\n * // Auto-generates:\r\n * // GET /api/department\r\n * // POST /api/department\r\n * // GET /api/department/:id\r\n * // PUT /api/department/:id\r\n * // PATCH /api/department/:id\r\n * // DELETE /api/department/:id\r\n * // PATCH /api/department/bulk/update\r\n * // DELETE /api/department/bulk/delete\r\n * ```\r\n */\r\nexport function expressAdapter(\r\n prisma: PrismaClient,\r\n options: PrismaRestOptions = {}\r\n) {\r\n // Lazy require so express stays optional peer dep\r\n // eslint-disable-next-line @typescript-eslint/no-var-requires\r\n const { Router } = require(\"express\");\r\n const router = Router();\r\n const { handle } = createRouter(prisma, options);\r\n\r\n // PATCH + DELETE /model/bulk/update and /model/bulk/delete\r\n router.patch(\"/:model/bulk/update\", async (req: any, res: any) => {\r\n try {\r\n const { status, data, headers } = await handle(\r\n \"PATCH\",\r\n req.params.model,\r\n null,\r\n req.body ?? [],\r\n new URLSearchParams(\r\n Object.entries(req.query as Record<string, string>)\r\n .map(([k, v]) => `${k}=${v}`)\r\n .join(\"&\")\r\n ),\r\n \"bulk-update\"\r\n );\r\n if (headers) Object.entries(headers).forEach(([k, v]) => res.setHeader(k, v));\r\n if (status === 204) return res.sendStatus(204);\r\n return res.status(status).json(data);\r\n } catch (e: any) {\r\n return res.status(500).json({ error: e.message });\r\n }\r\n });\r\n\r\n router.delete(\"/:model/bulk/delete\", async (req: any, res: any) => {\r\n try {\r\n const { status, data, headers } = await handle(\r\n \"DELETE\",\r\n req.params.model,\r\n null,\r\n req.body ?? [],\r\n new URLSearchParams(\r\n Object.entries(req.query as Record<string, string>)\r\n .map(([k, v]) => `${k}=${v}`)\r\n .join(\"&\")\r\n ),\r\n \"bulk-delete\"\r\n );\r\n if (headers) Object.entries(headers).forEach(([k, v]) => res.setHeader(k, v));\r\n if (status === 204) return res.sendStatus(204);\r\n return res.status(status).json(data);\r\n } catch (e: any) {\r\n return res.status(500).json({ error: e.message });\r\n }\r\n });\r\n\r\n // GET + POST /model\r\n router.route(\"/:model\").get(handler).post(handler);\r\n\r\n // GET + PUT + PATCH + DELETE /model/:id\r\n router\r\n .route(\"/:model/:id\")\r\n .get(handler)\r\n .put(handler)\r\n .patch(handler)\r\n .delete(handler);\r\n\r\n async function handler(req: any, res: any) {\r\n try {\r\n const { status, data, headers } = await handle(\r\n req.method,\r\n req.params.model,\r\n req.params.id ?? null,\r\n req.body ?? {},\r\n new URLSearchParams(\r\n Object.entries(req.query as Record<string, string>)\r\n .map(([k, v]) => `${k}=${v}`)\r\n .join(\"&\")\r\n )\r\n );\r\n\r\n if (headers) Object.entries(headers).forEach(([k, v]) => res.setHeader(k, v));\r\n if (status === 204) return res.sendStatus(204);\r\n return res.status(status).json(data);\r\n } catch (e: any) {\r\n return res.status(500).json({ error: e.message });\r\n }\r\n }\r\n\r\n return router;\r\n}\r\n\r\n/**\r\n * Express error-handling middleware that maps Prisma error codes to clean\r\n * JSON responses. Mount it after all routes to catch Prisma errors from\r\n * both omni-rest and your own custom routes.\r\n *\r\n * @example\r\n * ```ts\r\n * import { expressAdapter, omniRestErrorHandler } from \"omni-rest/express\";\r\n *\r\n * app.use(\"/api\", expressAdapter(prisma));\r\n * app.use(\"/custom\", myCustomRoutes);\r\n * app.use(omniRestErrorHandler());\r\n * ```\r\n */\r\nexport function omniRestErrorHandler() {\r\n // Standard Express 4-argument error middleware\r\n return function (err: any, _req: any, res: any, next: any) {\r\n const code = err?.code;\r\n\r\n if (code === \"P2025\") {\r\n return res.status(404).json({ error: \"Record not found.\" });\r\n }\r\n if (code === \"P2002\") {\r\n const fields = err?.meta?.target ?? \"unknown fields\";\r\n return res.status(409).json({ error: `Unique constraint failed on: ${fields}` });\r\n }\r\n if (code === \"P2003\") {\r\n return res.status(400).json({ error: \"Foreign key constraint failed.\" });\r\n }\r\n if (code === \"P2014\") {\r\n return res.status(400).json({ error: \"Relation violation.\" });\r\n }\r\n\r\n // Not a known Prisma error — pass through to next error handler\r\n if (!code) {\r\n return next(err);\r\n }\r\n\r\n return res.status(500).json({ error: err?.message ?? \"Internal server error.\" });\r\n };\r\n}"]}
|
|
1
|
+
{"version":3,"sources":["../../src/introspect.ts","../../src/query-builder.ts","../../src/middleware-helpers.ts","../../src/router.ts","../../src/adapters/express.ts"],"names":["f","sortedOps","id"],"mappings":"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","file":"express.js","sourcesContent":["import type { ModelMeta, FieldMeta } from \"./types\";\r\n\r\n/**\r\n * Reads Prisma's DMMF (Data Model Meta Format) at runtime\r\n * and returns structured metadata for every model in your schema.\r\n *\r\n * No file reading. No code generation. Pure runtime introspection.\r\n */\r\nexport function getModels(prisma?: any): ModelMeta[] {\r\n let raw: any[] | undefined;\r\n\r\n // Try to get from PrismaClient instance first (new v5 format)\r\n if (prisma?._runtimeDataModel?.models) {\r\n const modelsObj = prisma._runtimeDataModel.models;\r\n // Convert object to array, adding name from key\r\n raw = Object.entries(modelsObj).map(([name, model]: [string, any]) => ({\r\n name,\r\n ...model,\r\n fields: (model.fields || []).map((f: any) => ({\r\n ...f,\r\n relationName: f.kind === \"object\" ? f.name : undefined,\r\n })),\r\n }));\r\n }\r\n\r\n // Fallback to DMMF from @prisma/client module — use plain require so\r\n // vi.mock(\"@prisma/client\") can intercept it in tests. The CLI handles\r\n // the global-install path resolution separately before calling getModels.\r\n if (!raw) {\r\n try {\r\n // eslint-disable-next-line @typescript-eslint/no-var-requires\r\n const prismaModule = require(\"@prisma/client\");\r\n const dmmfModels = prismaModule?.Prisma?.dmmf?.datamodel?.models;\r\n if (dmmfModels) {\r\n raw = dmmfModels;\r\n }\r\n } catch {\r\n // @prisma/client not available or not generated — handled below\r\n }\r\n }\r\n\r\n if (!raw) {\r\n throw new Error(\r\n \"[omni-rest] Could not find Prisma DMMF. Ensure Prisma client is generated and you're passing a PrismaClient instance to omni-rest.\"\r\n );\r\n }\r\n\r\n if (!Array.isArray(raw)) {\r\n throw new Error(\r\n `[omni-rest] Expected models to be an array, got ${typeof raw}. Debug: prisma._runtimeDataModel.models=${!!prisma?._runtimeDataModel?.models}, raw value=${JSON.stringify(raw).slice(0, 100)}`\r\n );\r\n }\r\n\r\n return raw.map((model: any) => {\r\n const fields: FieldMeta[] = model.fields.map((f: any) => ({\r\n name: f.name,\r\n type: f.type,\r\n isId: f.isId,\r\n isRequired: f.isRequired,\r\n isList: f.isList,\r\n isRelation: !!f.relationName,\r\n hasDefaultValue: !!f.hasDefaultValue || !!f.default,\r\n isUpdatedAt: !!f.isUpdatedAt,\r\n }));\r\n\r\n const idField =\r\n model.fields.find((f: any) => f.isId)?.name ?? \"id\";\r\n\r\n return {\r\n name: model.name,\r\n routeName: toRouteName(model.name),\r\n fields,\r\n idField,\r\n };\r\n });\r\n}\r\n\r\n/**\r\n * Converts a Prisma model name to a URL-safe route segment.\r\n * \"UserProfile\" → \"userprofile\"\r\n * \"OrderItem\" → \"orderitem\"\r\n */\r\nexport function toRouteName(modelName: string): string {\r\n return modelName.toLowerCase();\r\n}\r\n\r\n/**\r\n * Returns a map of routeName → ModelMeta for O(1) lookups.\r\n */\r\nexport function buildModelMap(\r\n models: ModelMeta[],\r\n allowList?: string[]\r\n): Record<string, ModelMeta> {\r\n const filtered = allowList\r\n ? models.filter((m) => allowList.includes(m.routeName))\r\n : models;\r\n\r\n return Object.fromEntries(filtered.map((m) => [m.routeName, m]));\r\n}\r\n\r\n/**\r\n * Auto-detects the soft-delete field for a model.\r\n * Returns the field name and the value to set, or null if not found.\r\n *\r\n * Priority:\r\n * 1. Explicit `softDeleteField` option\r\n * 2. A field named \"deletedAt\" with type DateTime\r\n * 3. A field named \"isActive\" with type Boolean\r\n */\r\nexport function detectSoftDeleteField(\r\n fields: FieldMeta[],\r\n explicitField?: string\r\n): { field: string; value: Date | boolean } | null {\r\n if (explicitField) {\r\n const f = fields.find((f) => f.name === explicitField);\r\n if (!f) return null;\r\n // Infer value from type\r\n const value = f.type === \"Boolean\" ? false : new Date();\r\n return { field: explicitField, value };\r\n }\r\n\r\n const deletedAt = fields.find((f) => f.name === \"deletedAt\" && f.type === \"DateTime\");\r\n if (deletedAt) return { field: \"deletedAt\", value: new Date() };\r\n\r\n const isActive = fields.find((f) => f.name === \"isActive\" && f.type === \"Boolean\");\r\n if (isActive) return { field: \"isActive\", value: false };\r\n\r\n return null;\r\n}\r\n\r\n/**\r\n * Gets the Prisma client delegate for a model.\r\n * prisma[\"userProfile\"] or prisma[\"user\"] — handles camelCase.\r\n */\r\nexport function getDelegate(prisma: any, meta: ModelMeta): any {\r\n // Prisma client properties are camelCase of model name\r\n // \"UserProfile\" → \"userProfile\"\r\n const key =\r\n meta.name.charAt(0).toLowerCase() + meta.name.slice(1);\r\n const delegate = prisma[key];\r\n\r\n if (!delegate) {\r\n throw new Error(\r\n `Could not find Prisma delegate for model \"${meta.name}\". ` +\r\n `Expected prisma.${key} to exist.`\r\n );\r\n }\r\n\r\n return delegate;\r\n}","import type { ParsedQuery, FieldMeta } from \"./types\";\r\n\r\n/**\r\n * Supported filter suffixes and their Prisma equivalents.\r\n *\r\n * Usage in URL:\r\n * ?name_contains=john\r\n * ?age_gte=18\r\n * ?status_in=active,inactive\r\n * ?email_not=test@test.com\r\n */\r\nconst FILTER_OPERATORS: Record<string, string> = {\r\n _gte: \"gte\",\r\n _lte: \"lte\",\r\n _gt: \"gt\",\r\n _lt: \"lt\",\r\n _contains: \"contains\",\r\n _icontains: \"contains\", // case-insensitive version (mode: insensitive)\r\n _startsWith: \"startsWith\",\r\n _endsWith: \"endsWith\",\r\n _in: \"in\",\r\n _notIn: \"notIn\",\r\n _not: \"not\",\r\n};\r\n\r\n/**\r\n * Reserved query keys — these are NOT treated as filters.\r\n */\r\nconst RESERVED_KEYS = new Set([\r\n \"page\",\r\n \"limit\",\r\n \"sort\",\r\n \"include\",\r\n \"select\",\r\n \"fields\",\r\n \"search\",\r\n]);\r\n\r\n/**\r\n * Parses URLSearchParams into a full Prisma query object.\r\n *\r\n * Supports:\r\n * Filtering → ?name=John ?age_gte=18 ?status_in=a,b\r\n * Relation filter → ?employees.isActive=true ?author.name_contains=john\r\n * Sorting → ?sort=createdAt:desc or ?sort=name:asc\r\n * Pagination → ?page=2&limit=10\r\n * Relations → ?include=posts,profile\r\n * Fields → ?select=id,name,email or ?fields=id,name,email\r\n * Search → ?search=eng (queries all String fields with OR)\r\n *\r\n * Dot-notation relation filtering:\r\n * ?relation.field=value → isList → { relation: { some: { field: value } } }\r\n * → singular → { relation: { field: value } }\r\n * Operator suffixes work inside dot notation:\r\n * ?author.name_contains=john → { author: { name: { contains: \"john\" } } }\r\n *\r\n * NOTE: Only one level of nesting is supported (relation.field).\r\n * Deeply nested filters (relation.nested.field) are not supported and will\r\n * be treated as a regular (non-relation) filter key.\r\n */\r\nexport function buildQuery(\r\n searchParams: URLSearchParams,\r\n defaultLimit = 20,\r\n maxLimit = 100,\r\n modelFields?: FieldMeta[]\r\n): ParsedQuery {\r\n const where: Record<string, any> = {};\r\n const orderBy: Record<string, any> = {};\r\n let include: Record<string, boolean> = {};\r\n let select: Record<string, boolean> | null = null;\r\n\r\n // ─── Pagination ────────────────────────────────────────────────────────────\r\n const page = Math.max(1, parseInt(searchParams.get(\"page\") ?? \"1\"));\r\n const rawLimit = parseInt(searchParams.get(\"limit\") ?? String(defaultLimit));\r\n const take = Math.min(rawLimit, maxLimit);\r\n const skip = (page - 1) * take;\r\n\r\n // ─── Sort ──────────────────────────────────────────────────────────────────\r\n const sortParam = searchParams.get(\"sort\");\r\n if (sortParam) {\r\n // Supports multiple sorts: ?sort=name:asc,createdAt:desc,_count.posts:desc\r\n for (const part of sortParam.split(\",\")) {\r\n const [field, dir] = part.trim().split(\":\");\r\n if (!field) continue;\r\n const direction = dir === \"desc\" ? \"desc\" : \"asc\";\r\n\r\n // _count.relation:dir → { relation: { _count: dir } }\r\n if (field.startsWith(\"_count.\")) {\r\n const relation = field.slice(\"_count.\".length);\r\n if (relation) {\r\n orderBy[relation] = { _count: direction };\r\n }\r\n } else {\r\n orderBy[field] = direction;\r\n }\r\n }\r\n }\r\n\r\n // ─── Include (relations) ───────────────────────────────────────────────────\r\n const includeParam = searchParams.get(\"include\");\r\n if (includeParam) {\r\n for (const rel of includeParam.split(\",\")) {\r\n if (rel.trim()) include[rel.trim()] = true;\r\n }\r\n }\r\n\r\n // ─── Select (fields) — ?select= or ?fields= alias ────────────────────────\r\n const selectParam = searchParams.get(\"select\") ?? searchParams.get(\"fields\");\r\n if (selectParam) {\r\n select = {};\r\n for (const field of selectParam.split(\",\")) {\r\n if (field.trim()) select[field.trim()] = true;\r\n }\r\n }\r\n\r\n // ─── Global search (?search=) ─────────────────────────────────────────────\r\n const searchValue = searchParams.get(\"search\");\r\n if (searchValue && modelFields) {\r\n const stringFields = modelFields.filter(\r\n (f) => f.type === \"String\" && !f.isRelation\r\n );\r\n if (stringFields.length > 0) {\r\n const orClauses = stringFields.map((f) => ({\r\n [f.name]: { contains: searchValue, mode: \"insensitive\" },\r\n }));\r\n // Merge with any existing where via AND so other filters still apply\r\n where[\"OR\"] = orClauses;\r\n }\r\n }\r\n\r\n // ─── Filters ───────────────────────────────────────────────────────────────\r\n for (const [key, value] of searchParams.entries()) {\r\n if (RESERVED_KEYS.has(key)) continue;\r\n\r\n // ── Dot-notation relation filter (?relation.field[_op]=value) ──────────\r\n // Only handle a single dot (one level deep). Deeper nesting falls through.\r\n const dotIndex = key.indexOf(\".\");\r\n if (dotIndex > 0 && key.indexOf(\".\", dotIndex + 1) === -1 && modelFields) {\r\n const relationName = key.slice(0, dotIndex);\r\n const fieldPart = key.slice(dotIndex + 1); // e.g. \"isActive\" or \"name_contains\"\r\n\r\n const relationMeta = modelFields.find(\r\n (f) => f.name === relationName && f.isRelation\r\n );\r\n\r\n if (relationMeta) {\r\n // Parse the field part for operator suffix\r\n const sortedOps = Object.keys(FILTER_OPERATORS).sort(\r\n (a, b) => b.length - a.length\r\n );\r\n let fieldName = fieldPart;\r\n let fieldFilter: any;\r\n\r\n let opMatched = false;\r\n for (const suffix of sortedOps) {\r\n if (fieldPart.endsWith(suffix)) {\r\n fieldName = fieldPart.slice(0, -suffix.length);\r\n const prismaOp = FILTER_OPERATORS[suffix];\r\n let parsedValue: any = value;\r\n\r\n if (prismaOp === \"in\" || prismaOp === \"notIn\") {\r\n parsedValue = value.split(\",\").map((v) => v.trim());\r\n } else if (!isNaN(Number(parsedValue)) && typeof parsedValue === \"string\") {\r\n parsedValue = Number(parsedValue);\r\n }\r\n\r\n const extra = suffix === \"_icontains\" ? { mode: \"insensitive\" } : {};\r\n fieldFilter = { [prismaOp]: parsedValue, ...extra };\r\n opMatched = true;\r\n break;\r\n }\r\n }\r\n\r\n if (!opMatched) {\r\n // Exact match — coerce value\r\n let parsedValue: any = value;\r\n if (value === \"true\") parsedValue = true;\r\n else if (value === \"false\") parsedValue = false;\r\n else if (!isNaN(Number(value)) && value !== \"\") parsedValue = Number(value);\r\n fieldFilter = parsedValue;\r\n }\r\n\r\n // isList relation → some: { field: filter }\r\n // singular relation → direct: { field: filter }\r\n const nested = { [fieldName]: fieldFilter };\r\n where[relationName] = relationMeta.isList\r\n ? { some: nested }\r\n : nested;\r\n\r\n continue; // skip regular filter processing for this key\r\n }\r\n }\r\n\r\n // ── Regular filters ────────────────────────────────────────────────────\r\n // Check operator suffixes (longest match first)\r\n let matched = false;\r\n const sortedOps = Object.keys(FILTER_OPERATORS).sort(\r\n (a, b) => b.length - a.length\r\n );\r\n\r\n for (const suffix of sortedOps) {\r\n if (key.endsWith(suffix)) {\r\n const field = key.slice(0, -suffix.length);\r\n const prismaOp = FILTER_OPERATORS[suffix];\r\n\r\n let parsedValue: any = value;\r\n\r\n // Parse arrays\r\n if (prismaOp === \"in\" || prismaOp === \"notIn\") {\r\n parsedValue = value.split(\",\").map((v) => v.trim());\r\n }\r\n\r\n // Attempt numeric coercion\r\n if (!isNaN(Number(parsedValue)) && typeof parsedValue === \"string\") {\r\n parsedValue = Number(parsedValue);\r\n }\r\n\r\n // Case-insensitive flag\r\n const extra = suffix === \"_icontains\" ? { mode: \"insensitive\" } : {};\r\n\r\n where[field] = { [prismaOp]: parsedValue, ...extra };\r\n matched = true;\r\n break;\r\n }\r\n }\r\n\r\n // No operator — exact match\r\n if (!matched) {\r\n let parsedValue: any = value;\r\n\r\n // Coerce booleans\r\n if (value === \"true\") parsedValue = true;\r\n else if (value === \"false\") parsedValue = false;\r\n // Coerce numbers\r\n else if (!isNaN(Number(value)) && value !== \"\") {\r\n parsedValue = Number(value);\r\n }\r\n\r\n where[key] = parsedValue;\r\n }\r\n }\r\n\r\n return { where, orderBy, skip, take, include, select };\r\n}","import type { GuardMap, HookFn, HookContext } from \"./types\";\r\n\r\n/**\r\n * Runs the guard for the given model+method combo.\r\n * Returns an error string if blocked, null if allowed.\r\n */\r\nexport async function runGuard(\r\n guards: GuardMap,\r\n model: string,\r\n method: string,\r\n ctx: { id?: string | null; body?: any }\r\n): Promise<string | null> {\r\n const modelGuards = guards[model];\r\n if (!modelGuards) return null;\r\n\r\n const fn = modelGuards[method as keyof typeof modelGuards];\r\n if (!fn) return null;\r\n\r\n return fn({ ...ctx, method });\r\n}\r\n\r\n/**\r\n * Runs a lifecycle hook (beforeOperation / afterOperation).\r\n * Silently swallows errors so hooks never crash the main flow.\r\n */\r\nexport async function runHook(\r\n hook: HookFn | undefined,\r\n ctx: HookContext\r\n): Promise<void> {\r\n if (!hook) return;\r\n try {\r\n await hook(ctx);\r\n } catch (e) {\r\n // Hooks should not crash the request\r\n console.error(\"[omni-rest] Hook error:\", e);\r\n }\r\n}","import { PrismaClient } from \"@prisma/client\";\r\nimport { getModels, buildModelMap, getDelegate, detectSoftDeleteField } from \"./introspect\";\r\nimport { buildQuery } from \"./query-builder\";\r\nimport { runGuard, runHook } from \"./middleware-helpers\";\r\nimport type {\r\n PrismaRestOptions,\r\n HandlerResult,\r\n RouterInstance,\r\n ModelMeta,\r\n FieldGuardConfig,\r\n} from \"./types\";\r\n\r\n/**\r\n * Creates a framework-agnostic CRUD router powered by Prisma DMMF.\r\n *\r\n * All adapters (Express, Next.js, Fastify) use this under the hood.\r\n */\r\nexport function createRouter(\r\n prisma: PrismaClient,\r\n options: PrismaRestOptions = {}\r\n): RouterInstance {\r\n const {\r\n allow,\r\n guards = {},\r\n beforeOperation,\r\n afterOperation,\r\n defaultLimit = 20,\r\n maxLimit = 100,\r\n softDelete = false,\r\n softDeleteField,\r\n envelope = true,\r\n fieldGuards = {},\r\n } = options;\r\n\r\n // Introspect schema once at startup\r\n const models = getModels(prisma);\r\n const modelMap = buildModelMap(models, allow);\r\n\r\n async function handle(\r\n method: string,\r\n modelName: string,\r\n id: string | null,\r\n body: any,\r\n searchParams: URLSearchParams,\r\n operation?: string\r\n ): Promise<HandlerResult> {\r\n // ── 1. Resolve model ───────────────────────────────────────────────────\r\n const meta = modelMap[modelName.toLowerCase()];\r\n if (!meta) {\r\n return {\r\n status: 404,\r\n data: {\r\n error: `Model \"${modelName}\" not found or not exposed.`,\r\n available: Object.keys(modelMap),\r\n },\r\n };\r\n }\r\n\r\n // ── 2. Guard check ─────────────────────────────────────────────────────\r\n const guardError = await runGuard(guards, meta.routeName, method, {\r\n id,\r\n body,\r\n });\r\n if (guardError) {\r\n return { status: 403, data: { error: guardError } };\r\n }\r\n\r\n // ── 3. Before hook ─────────────────────────────────────────────────────\r\n await runHook(beforeOperation, { model: meta.name, method, id, body });\r\n\r\n // ── 4. Execute operation ───────────────────────────────────────────────\r\n let result: HandlerResult;\r\n\r\n try {\r\n result = await executeOperation(\r\n prisma,\r\n meta,\r\n method,\r\n id,\r\n body,\r\n searchParams,\r\n defaultLimit,\r\n maxLimit,\r\n operation,\r\n softDelete,\r\n softDeleteField,\r\n envelope,\r\n fieldGuards[meta.routeName]\r\n );\r\n } catch (e: any) {\r\n return handlePrismaError(e);\r\n }\r\n\r\n // ── 5. After hook ──────────────────────────────────────────────────────\r\n await runHook(afterOperation, {\r\n model: meta.name,\r\n method,\r\n id,\r\n body,\r\n result: result.data,\r\n });\r\n\r\n return result;\r\n }\r\n\r\n return { handle, modelMap, models };\r\n}\r\n\r\n// ─── Operation executor ────────────────────────────────────────────────────────\r\n\r\nasync function executeOperation(\r\n prisma: PrismaClient,\r\n meta: ModelMeta,\r\n method: string,\r\n id: string | null,\r\n body: any,\r\n searchParams: URLSearchParams,\r\n defaultLimit: number,\r\n maxLimit: number,\r\n operation?: string,\r\n softDelete = false,\r\n softDeleteField?: string,\r\n envelope = true,\r\n fg?: FieldGuardConfig\r\n): Promise<HandlerResult> {\r\n const delegate = getDelegate(prisma, meta);\r\n const { where, orderBy, skip, take, include, select } = buildQuery(\r\n searchParams,\r\n defaultLimit,\r\n maxLimit,\r\n meta.fields\r\n );\r\n\r\n // Build include/select args (mutually exclusive in Prisma)\r\n const includeArg =\r\n Object.keys(include).length > 0 ? include : undefined;\r\n const selectArg =\r\n select && Object.keys(select).length > 0 ? select : undefined;\r\n const projection = selectArg ? { select: selectArg } : includeArg ? { include: includeArg } : {};\r\n\r\n // Sanitize write body — strip readOnly fields before passing to Prisma\r\n const safeBody = sanitizeBody(body, fg);\r\n\r\n // ── PATCH /model/bulk/update ────────────────────────────────────────────────\r\n if (method === \"PATCH\" && operation === \"bulk-update\") {\r\n if (!Array.isArray(body) || body.length === 0) {\r\n return {\r\n status: 400,\r\n data: { error: \"Request body must be a non-empty array of update records\" },\r\n };\r\n }\r\n\r\n // Validate that each item has an id\r\n for (const item of body) {\r\n if (!item[meta.idField]) {\r\n return {\r\n status: 400,\r\n data: { error: `Each record must have an ${meta.idField} field` },\r\n };\r\n }\r\n }\r\n\r\n // Execute updates in parallel\r\n const results = await Promise.all(\r\n body.map((item) => {\r\n const id = item[meta.idField];\r\n const updateData = { ...item };\r\n delete updateData[meta.idField]; // Remove id from update data\r\n\r\n return delegate.update({\r\n where: { [meta.idField]: coerceId(id) },\r\n data: updateData,\r\n ...projection,\r\n });\r\n })\r\n );\r\n\r\n return {\r\n status: 200,\r\n data: {\r\n updated: results.length,\r\n records: results,\r\n },\r\n };\r\n }\r\n\r\n // ── DELETE /model/bulk/delete ──────────────────────────────────────────────\r\n if (method === \"DELETE\" && operation === \"bulk-delete\") {\r\n if (!Array.isArray(body) || body.length === 0) {\r\n return {\r\n status: 400,\r\n data: { error: \"Request body must be a non-empty array of IDs\" },\r\n };\r\n }\r\n\r\n // Handle both array of IDs and array of objects with id field\r\n const ids = body.map((item: any) =>\r\n typeof item === \"object\" ? item[meta.idField] : item\r\n );\r\n\r\n // Use deleteMany with a where clause\r\n const result = await delegate.deleteMany({\r\n where: {\r\n [meta.idField]: { in: ids.map(coerceId) },\r\n },\r\n });\r\n\r\n return {\r\n status: 200,\r\n data: {\r\n deleted: result.count,\r\n },\r\n };\r\n }\r\n\r\n // ── GET /model ─────────────────────────────────────────────────────────────\r\n if (method === \"GET\" && !id) {\r\n const softDeleteInfo = softDelete\r\n ? detectSoftDeleteField(meta.fields, softDeleteField)\r\n : null;\r\n const listWhere = softDeleteInfo\r\n ? { ...where, [softDeleteInfo.field]: softDeleteInfo.field === \"isActive\" ? true : null }\r\n : where;\r\n\r\n const [data, total] = await (prisma as any).$transaction([\r\n delegate.findMany({ where: listWhere, orderBy, skip, take, ...projection }),\r\n delegate.count({ where: listWhere }),\r\n ]);\r\n\r\n const safeData = (data as any[]).map((r: any) => stripResponse(r, fg));\r\n\r\n if (!envelope) {\r\n return {\r\n status: 200,\r\n data: safeData,\r\n headers: { \"X-Total-Count\": String(total) },\r\n };\r\n }\r\n\r\n return {\r\n status: 200,\r\n data: {\r\n data: safeData,\r\n meta: {\r\n total,\r\n page: Math.floor(skip / take) + 1,\r\n limit: take,\r\n totalPages: Math.ceil(total / take),\r\n },\r\n },\r\n };\r\n }\r\n\r\n // ── GET /model/:id ─────────────────────────────────────────────────────────\r\n if (method === \"GET\" && id) {\r\n const record = await delegate.findUnique({\r\n where: { [meta.idField]: coerceId(id) },\r\n ...projection,\r\n });\r\n\r\n if (!record) {\r\n return { status: 404, data: { error: `${meta.name} with id \"${id}\" not found.` } };\r\n }\r\n\r\n return { status: 200, data: stripResponse(record, fg) };\r\n }\r\n\r\n // ── POST /model ────────────────────────────────────────────────────────────\r\n if (method === \"POST\" && !id) {\r\n const record = await delegate.create({ data: safeBody });\r\n return { status: 201, data: stripResponse(record, fg) };\r\n }\r\n\r\n // ── PUT /model/:id ─────────────────────────────────────────────────────────\r\n if ((method === \"PUT\" || method === \"PATCH\") && id) {\r\n const record = await delegate.update({\r\n where: { [meta.idField]: coerceId(id) },\r\n data: safeBody,\r\n });\r\n return { status: 200, data: stripResponse(record, fg) };\r\n }\r\n\r\n // ── DELETE /model/:id ──────────────────────────────────────────────────────\r\n if (method === \"DELETE\" && id) {\r\n const softDeleteInfo = softDelete\r\n ? detectSoftDeleteField(meta.fields, softDeleteField)\r\n : null;\r\n\r\n if (softDeleteInfo) {\r\n const record = await delegate.update({\r\n where: { [meta.idField]: coerceId(id) },\r\n data: { [softDeleteInfo.field]: softDeleteInfo.value },\r\n });\r\n return { status: 200, data: record };\r\n }\r\n\r\n await delegate.delete({\r\n where: { [meta.idField]: coerceId(id) },\r\n });\r\n return { status: 204, data: null };\r\n }\r\n\r\n return { status: 405, data: { error: `Method ${method} not allowed.` } };\r\n}\r\n\r\n// ─── Helpers ──────────────────────────────────────────────────────────────────\r\n\r\n/**\r\n * Strips hidden and writeOnly fields from a response record.\r\n * Works on plain objects; passes through null/non-objects unchanged.\r\n */\r\nfunction stripResponse(record: any, fg?: FieldGuardConfig): any {\r\n if (!fg || !record || typeof record !== \"object\") return record;\r\n const blocked = new Set([...(fg.hidden ?? []), ...(fg.writeOnly ?? [])]);\r\n if (blocked.size === 0) return record;\r\n const out: Record<string, any> = {};\r\n for (const [k, v] of Object.entries(record)) {\r\n if (!blocked.has(k)) out[k] = v;\r\n }\r\n return out;\r\n}\r\n\r\n/**\r\n * Strips readOnly fields from a write body before passing to Prisma.\r\n */\r\nfunction sanitizeBody(body: any, fg?: FieldGuardConfig): any {\r\n if (!fg || !body || typeof body !== \"object\" || Array.isArray(body)) return body;\r\n const readOnly = new Set(fg.readOnly ?? []);\r\n if (readOnly.size === 0) return body;\r\n const out: Record<string, any> = {};\r\n for (const [k, v] of Object.entries(body)) {\r\n if (!readOnly.has(k)) out[k] = v;\r\n }\r\n return out;\r\n}\r\n\r\n/**\r\n * Coerces an ID string to number when possible (common with Int PKs).\r\n */\r\nfunction coerceId(id: string): string | number {\r\n const n = Number(id);\r\n return isNaN(n) ? id : n;\r\n}\r\n\r\n/**\r\n * Maps Prisma error codes to meaningful HTTP responses.\r\n */\r\nfunction handlePrismaError(e: any): HandlerResult {\r\n const code = e?.code;\r\n\r\n if (code === \"P2025\") {\r\n return { status: 404, data: { error: \"Record not found.\" } };\r\n }\r\n if (code === \"P2002\") {\r\n const fields = e?.meta?.target ?? \"unknown fields\";\r\n return {\r\n status: 409,\r\n data: { error: `Unique constraint failed on: ${fields}` },\r\n };\r\n }\r\n if (code === \"P2003\") {\r\n return { status: 400, data: { error: \"Foreign key constraint failed.\" } };\r\n }\r\n if (code === \"P2014\") {\r\n return { status: 400, data: { error: \"Relation violation.\" } };\r\n }\r\n\r\n return { status: 500, data: { error: e?.message ?? \"Internal server error.\" } };\r\n}","import { PrismaClient } from \"@prisma/client\";\r\nimport { createRouter } from \"../router\";\r\nimport type { PrismaRestOptions } from \"../types\";\r\n\r\n/**\r\n * Express adapter for omni-rest.\r\n *\r\n * @example\r\n * ```ts\r\n * import express from \"express\";\r\n * import { PrismaClient } from \"@prisma/client\";\r\n * import { expressAdapter } from \"omni-rest/express\";\r\n *\r\n * const app = express();\r\n * const prisma = new PrismaClient();\r\n *\r\n * app.use(express.json());\r\n * app.use(\"/api\", expressAdapter(prisma, {\r\n * allow: [\"department\", \"category\", \"city\"],\r\n * }));\r\n *\r\n * // Auto-generates:\r\n * // GET /api/department\r\n * // POST /api/department\r\n * // GET /api/department/:id\r\n * // PUT /api/department/:id\r\n * // PATCH /api/department/:id\r\n * // DELETE /api/department/:id\r\n * // PATCH /api/department/bulk/update\r\n * // DELETE /api/department/bulk/delete\r\n * ```\r\n */\r\nexport function expressAdapter(\r\n prisma: PrismaClient,\r\n options: PrismaRestOptions = {}\r\n) {\r\n // Lazy require so express stays optional peer dep\r\n // eslint-disable-next-line @typescript-eslint/no-var-requires\r\n const { Router } = require(\"express\");\r\n const router = Router();\r\n const { handle } = createRouter(prisma, options);\r\n\r\n // PATCH + DELETE /model/bulk/update and /model/bulk/delete\r\n router.patch(\"/:model/bulk/update\", async (req: any, res: any) => {\r\n try {\r\n const { status, data, headers } = await handle(\r\n \"PATCH\",\r\n req.params.model,\r\n null,\r\n req.body ?? [],\r\n new URLSearchParams(\r\n Object.entries(req.query as Record<string, string>)\r\n .map(([k, v]) => `${k}=${v}`)\r\n .join(\"&\")\r\n ),\r\n \"bulk-update\"\r\n );\r\n if (headers) Object.entries(headers).forEach(([k, v]) => res.setHeader(k, v));\r\n if (status === 204) return res.sendStatus(204);\r\n return res.status(status).json(data);\r\n } catch (e: any) {\r\n return res.status(500).json({ error: e.message });\r\n }\r\n });\r\n\r\n router.delete(\"/:model/bulk/delete\", async (req: any, res: any) => {\r\n try {\r\n const { status, data, headers } = await handle(\r\n \"DELETE\",\r\n req.params.model,\r\n null,\r\n req.body ?? [],\r\n new URLSearchParams(\r\n Object.entries(req.query as Record<string, string>)\r\n .map(([k, v]) => `${k}=${v}`)\r\n .join(\"&\")\r\n ),\r\n \"bulk-delete\"\r\n );\r\n if (headers) Object.entries(headers).forEach(([k, v]) => res.setHeader(k, v));\r\n if (status === 204) return res.sendStatus(204);\r\n return res.status(status).json(data);\r\n } catch (e: any) {\r\n return res.status(500).json({ error: e.message });\r\n }\r\n });\r\n\r\n // GET + POST /model\r\n router.route(\"/:model\").get(handler).post(handler);\r\n\r\n // GET + PUT + PATCH + DELETE /model/:id\r\n router\r\n .route(\"/:model/:id\")\r\n .get(handler)\r\n .put(handler)\r\n .patch(handler)\r\n .delete(handler);\r\n\r\n async function handler(req: any, res: any) {\r\n try {\r\n const { status, data, headers } = await handle(\r\n req.method,\r\n req.params.model,\r\n req.params.id ?? null,\r\n req.body ?? {},\r\n new URLSearchParams(\r\n Object.entries(req.query as Record<string, string>)\r\n .map(([k, v]) => `${k}=${v}`)\r\n .join(\"&\")\r\n )\r\n );\r\n\r\n if (headers) Object.entries(headers).forEach(([k, v]) => res.setHeader(k, v));\r\n if (status === 204) return res.sendStatus(204);\r\n return res.status(status).json(data);\r\n } catch (e: any) {\r\n return res.status(500).json({ error: e.message });\r\n }\r\n }\r\n\r\n return router;\r\n}\r\n\r\n/**\r\n * Express error-handling middleware that maps Prisma error codes to clean\r\n * JSON responses. Mount it after all routes to catch Prisma errors from\r\n * both omni-rest and your own custom routes.\r\n *\r\n * @example\r\n * ```ts\r\n * import { expressAdapter, omniRestErrorHandler } from \"omni-rest/express\";\r\n *\r\n * app.use(\"/api\", expressAdapter(prisma));\r\n * app.use(\"/custom\", myCustomRoutes);\r\n * app.use(omniRestErrorHandler());\r\n * ```\r\n */\r\nexport function omniRestErrorHandler() {\r\n // Standard Express 4-argument error middleware\r\n return function (err: any, _req: any, res: any, next: any) {\r\n const code = err?.code;\r\n\r\n if (code === \"P2025\") {\r\n return res.status(404).json({ error: \"Record not found.\" });\r\n }\r\n if (code === \"P2002\") {\r\n const fields = err?.meta?.target ?? \"unknown fields\";\r\n return res.status(409).json({ error: `Unique constraint failed on: ${fields}` });\r\n }\r\n if (code === \"P2003\") {\r\n return res.status(400).json({ error: \"Foreign key constraint failed.\" });\r\n }\r\n if (code === \"P2014\") {\r\n return res.status(400).json({ error: \"Relation violation.\" });\r\n }\r\n\r\n // Not a known Prisma error — pass through to next error handler\r\n if (!code) {\r\n return next(err);\r\n }\r\n\r\n return res.status(500).json({ error: err?.message ?? \"Internal server error.\" });\r\n };\r\n}"]}
|
|
@@ -166,6 +166,48 @@ function buildQuery(searchParams, defaultLimit = 20, maxLimit = 100, modelFields
|
|
|
166
166
|
}
|
|
167
167
|
for (const [key, value] of searchParams.entries()) {
|
|
168
168
|
if (RESERVED_KEYS.has(key)) continue;
|
|
169
|
+
const dotIndex = key.indexOf(".");
|
|
170
|
+
if (dotIndex > 0 && key.indexOf(".", dotIndex + 1) === -1 && modelFields) {
|
|
171
|
+
const relationName = key.slice(0, dotIndex);
|
|
172
|
+
const fieldPart = key.slice(dotIndex + 1);
|
|
173
|
+
const relationMeta = modelFields.find(
|
|
174
|
+
(f) => f.name === relationName && f.isRelation
|
|
175
|
+
);
|
|
176
|
+
if (relationMeta) {
|
|
177
|
+
const sortedOps2 = Object.keys(FILTER_OPERATORS).sort(
|
|
178
|
+
(a, b) => b.length - a.length
|
|
179
|
+
);
|
|
180
|
+
let fieldName = fieldPart;
|
|
181
|
+
let fieldFilter;
|
|
182
|
+
let opMatched = false;
|
|
183
|
+
for (const suffix of sortedOps2) {
|
|
184
|
+
if (fieldPart.endsWith(suffix)) {
|
|
185
|
+
fieldName = fieldPart.slice(0, -suffix.length);
|
|
186
|
+
const prismaOp = FILTER_OPERATORS[suffix];
|
|
187
|
+
let parsedValue = value;
|
|
188
|
+
if (prismaOp === "in" || prismaOp === "notIn") {
|
|
189
|
+
parsedValue = value.split(",").map((v) => v.trim());
|
|
190
|
+
} else if (!isNaN(Number(parsedValue)) && typeof parsedValue === "string") {
|
|
191
|
+
parsedValue = Number(parsedValue);
|
|
192
|
+
}
|
|
193
|
+
const extra = suffix === "_icontains" ? { mode: "insensitive" } : {};
|
|
194
|
+
fieldFilter = { [prismaOp]: parsedValue, ...extra };
|
|
195
|
+
opMatched = true;
|
|
196
|
+
break;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
if (!opMatched) {
|
|
200
|
+
let parsedValue = value;
|
|
201
|
+
if (value === "true") parsedValue = true;
|
|
202
|
+
else if (value === "false") parsedValue = false;
|
|
203
|
+
else if (!isNaN(Number(value)) && value !== "") parsedValue = Number(value);
|
|
204
|
+
fieldFilter = parsedValue;
|
|
205
|
+
}
|
|
206
|
+
const nested = { [fieldName]: fieldFilter };
|
|
207
|
+
where[relationName] = relationMeta.isList ? { some: nested } : nested;
|
|
208
|
+
continue;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
169
211
|
let matched = false;
|
|
170
212
|
const sortedOps = Object.keys(FILTER_OPERATORS).sort(
|
|
171
213
|
(a, b) => b.length - a.length
|
|
@@ -228,7 +270,8 @@ function createRouter(prisma, options = {}) {
|
|
|
228
270
|
maxLimit = 100,
|
|
229
271
|
softDelete = false,
|
|
230
272
|
softDeleteField,
|
|
231
|
-
envelope = true
|
|
273
|
+
envelope = true,
|
|
274
|
+
fieldGuards = {}
|
|
232
275
|
} = options;
|
|
233
276
|
const models = getModels(prisma);
|
|
234
277
|
const modelMap = buildModelMap(models, allow);
|
|
@@ -265,7 +308,8 @@ function createRouter(prisma, options = {}) {
|
|
|
265
308
|
operation,
|
|
266
309
|
softDelete,
|
|
267
310
|
softDeleteField,
|
|
268
|
-
envelope
|
|
311
|
+
envelope,
|
|
312
|
+
fieldGuards[meta.routeName]
|
|
269
313
|
);
|
|
270
314
|
} catch (e) {
|
|
271
315
|
return handlePrismaError(e);
|
|
@@ -281,7 +325,7 @@ function createRouter(prisma, options = {}) {
|
|
|
281
325
|
}
|
|
282
326
|
return { handle, modelMap, models };
|
|
283
327
|
}
|
|
284
|
-
async function executeOperation(prisma, meta, method, id, body, searchParams, defaultLimit, maxLimit, operation, softDelete = false, softDeleteField, envelope = true) {
|
|
328
|
+
async function executeOperation(prisma, meta, method, id, body, searchParams, defaultLimit, maxLimit, operation, softDelete = false, softDeleteField, envelope = true, fg) {
|
|
285
329
|
const delegate = getDelegate(prisma, meta);
|
|
286
330
|
const { where, orderBy, skip, take, include, select } = buildQuery(
|
|
287
331
|
searchParams,
|
|
@@ -292,6 +336,7 @@ async function executeOperation(prisma, meta, method, id, body, searchParams, de
|
|
|
292
336
|
const includeArg = Object.keys(include).length > 0 ? include : void 0;
|
|
293
337
|
const selectArg = select && Object.keys(select).length > 0 ? select : void 0;
|
|
294
338
|
const projection = selectArg ? { select: selectArg } : includeArg ? { include: includeArg } : {};
|
|
339
|
+
const safeBody = sanitizeBody(body, fg);
|
|
295
340
|
if (method === "PATCH" && operation === "bulk-update") {
|
|
296
341
|
if (!Array.isArray(body) || body.length === 0) {
|
|
297
342
|
return {
|
|
@@ -356,17 +401,18 @@ async function executeOperation(prisma, meta, method, id, body, searchParams, de
|
|
|
356
401
|
delegate.findMany({ where: listWhere, orderBy, skip, take, ...projection }),
|
|
357
402
|
delegate.count({ where: listWhere })
|
|
358
403
|
]);
|
|
404
|
+
const safeData = data.map((r) => stripResponse(r, fg));
|
|
359
405
|
if (!envelope) {
|
|
360
406
|
return {
|
|
361
407
|
status: 200,
|
|
362
|
-
data,
|
|
408
|
+
data: safeData,
|
|
363
409
|
headers: { "X-Total-Count": String(total) }
|
|
364
410
|
};
|
|
365
411
|
}
|
|
366
412
|
return {
|
|
367
413
|
status: 200,
|
|
368
414
|
data: {
|
|
369
|
-
data,
|
|
415
|
+
data: safeData,
|
|
370
416
|
meta: {
|
|
371
417
|
total,
|
|
372
418
|
page: Math.floor(skip / take) + 1,
|
|
@@ -384,18 +430,18 @@ async function executeOperation(prisma, meta, method, id, body, searchParams, de
|
|
|
384
430
|
if (!record) {
|
|
385
431
|
return { status: 404, data: { error: `${meta.name} with id "${id}" not found.` } };
|
|
386
432
|
}
|
|
387
|
-
return { status: 200, data: record };
|
|
433
|
+
return { status: 200, data: stripResponse(record, fg) };
|
|
388
434
|
}
|
|
389
435
|
if (method === "POST" && !id) {
|
|
390
|
-
const record = await delegate.create({ data:
|
|
391
|
-
return { status: 201, data: record };
|
|
436
|
+
const record = await delegate.create({ data: safeBody });
|
|
437
|
+
return { status: 201, data: stripResponse(record, fg) };
|
|
392
438
|
}
|
|
393
439
|
if ((method === "PUT" || method === "PATCH") && id) {
|
|
394
440
|
const record = await delegate.update({
|
|
395
441
|
where: { [meta.idField]: coerceId(id) },
|
|
396
|
-
data:
|
|
442
|
+
data: safeBody
|
|
397
443
|
});
|
|
398
|
-
return { status: 200, data: record };
|
|
444
|
+
return { status: 200, data: stripResponse(record, fg) };
|
|
399
445
|
}
|
|
400
446
|
if (method === "DELETE" && id) {
|
|
401
447
|
const softDeleteInfo = softDelete ? detectSoftDeleteField(meta.fields, softDeleteField) : null;
|
|
@@ -413,6 +459,26 @@ async function executeOperation(prisma, meta, method, id, body, searchParams, de
|
|
|
413
459
|
}
|
|
414
460
|
return { status: 405, data: { error: `Method ${method} not allowed.` } };
|
|
415
461
|
}
|
|
462
|
+
function stripResponse(record, fg) {
|
|
463
|
+
if (!fg || !record || typeof record !== "object") return record;
|
|
464
|
+
const blocked = /* @__PURE__ */ new Set([...fg.hidden ?? [], ...fg.writeOnly ?? []]);
|
|
465
|
+
if (blocked.size === 0) return record;
|
|
466
|
+
const out = {};
|
|
467
|
+
for (const [k, v] of Object.entries(record)) {
|
|
468
|
+
if (!blocked.has(k)) out[k] = v;
|
|
469
|
+
}
|
|
470
|
+
return out;
|
|
471
|
+
}
|
|
472
|
+
function sanitizeBody(body, fg) {
|
|
473
|
+
if (!fg || !body || typeof body !== "object" || Array.isArray(body)) return body;
|
|
474
|
+
const readOnly = new Set(fg.readOnly ?? []);
|
|
475
|
+
if (readOnly.size === 0) return body;
|
|
476
|
+
const out = {};
|
|
477
|
+
for (const [k, v] of Object.entries(body)) {
|
|
478
|
+
if (!readOnly.has(k)) out[k] = v;
|
|
479
|
+
}
|
|
480
|
+
return out;
|
|
481
|
+
}
|
|
416
482
|
function coerceId(id) {
|
|
417
483
|
const n = Number(id);
|
|
418
484
|
return isNaN(n) ? id : n;
|