omni-rest 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,424 @@
1
+ import { Prisma } from '@prisma/client';
2
+
3
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
4
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
5
+ }) : x)(function(x) {
6
+ if (typeof require !== "undefined") return require.apply(this, arguments);
7
+ throw Error('Dynamic require of "' + x + '" is not supported');
8
+ });
9
+ function getModels(prisma) {
10
+ let raw;
11
+ if (prisma?._runtimeDataModel?.models) {
12
+ const modelsObj = prisma._runtimeDataModel.models;
13
+ raw = Object.entries(modelsObj).map(([name, model]) => ({
14
+ name,
15
+ ...model,
16
+ fields: (model.fields || []).map((f) => ({
17
+ ...f,
18
+ relationName: f.kind === "object" ? f.name : void 0
19
+ }))
20
+ }));
21
+ }
22
+ if (!raw) {
23
+ const dmmfModels = Prisma?.dmmf?.datamodel?.models || __require("@prisma/client")?.Prisma?.dmmf?.datamodel?.models;
24
+ if (dmmfModels) {
25
+ raw = dmmfModels;
26
+ }
27
+ }
28
+ if (!raw) {
29
+ throw new Error(
30
+ "[omni-rest] Could not find Prisma DMMF. Ensure Prisma client is generated and you're passing a PrismaClient instance to omni-rest."
31
+ );
32
+ }
33
+ if (!Array.isArray(raw)) {
34
+ throw new Error(
35
+ `[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)}`
36
+ );
37
+ }
38
+ return raw.map((model) => {
39
+ const fields = model.fields.map((f) => ({
40
+ name: f.name,
41
+ type: f.type,
42
+ isId: f.isId,
43
+ isRequired: f.isRequired,
44
+ isList: f.isList,
45
+ isRelation: !!f.relationName
46
+ }));
47
+ const idField = model.fields.find((f) => f.isId)?.name ?? "id";
48
+ return {
49
+ name: model.name,
50
+ routeName: toRouteName(model.name),
51
+ fields,
52
+ idField
53
+ };
54
+ });
55
+ }
56
+ function toRouteName(modelName) {
57
+ return modelName.toLowerCase();
58
+ }
59
+ function buildModelMap(models, allowList) {
60
+ const filtered = allowList ? models.filter((m) => allowList.includes(m.routeName)) : models;
61
+ return Object.fromEntries(filtered.map((m) => [m.routeName, m]));
62
+ }
63
+ function getDelegate(prisma, meta) {
64
+ const key = meta.name.charAt(0).toLowerCase() + meta.name.slice(1);
65
+ const delegate = prisma[key];
66
+ if (!delegate) {
67
+ throw new Error(
68
+ `Could not find Prisma delegate for model "${meta.name}". Expected prisma.${key} to exist.`
69
+ );
70
+ }
71
+ return delegate;
72
+ }
73
+
74
+ // src/query-builder.ts
75
+ var FILTER_OPERATORS = {
76
+ _gte: "gte",
77
+ _lte: "lte",
78
+ _gt: "gt",
79
+ _lt: "lt",
80
+ _contains: "contains",
81
+ _icontains: "contains",
82
+ // case-insensitive version (mode: insensitive)
83
+ _startsWith: "startsWith",
84
+ _endsWith: "endsWith",
85
+ _in: "in",
86
+ _notIn: "notIn",
87
+ _not: "not"
88
+ };
89
+ var RESERVED_KEYS = /* @__PURE__ */ new Set([
90
+ "page",
91
+ "limit",
92
+ "sort",
93
+ "include",
94
+ "select"
95
+ ]);
96
+ function buildQuery(searchParams, defaultLimit = 20, maxLimit = 100) {
97
+ const where = {};
98
+ const orderBy = {};
99
+ let include = {};
100
+ let select = null;
101
+ const page = Math.max(1, parseInt(searchParams.get("page") ?? "1"));
102
+ const rawLimit = parseInt(searchParams.get("limit") ?? String(defaultLimit));
103
+ const take = Math.min(rawLimit, maxLimit);
104
+ const skip = (page - 1) * take;
105
+ const sortParam = searchParams.get("sort");
106
+ if (sortParam) {
107
+ for (const part of sortParam.split(",")) {
108
+ const [field, dir] = part.trim().split(":");
109
+ if (field) {
110
+ orderBy[field] = dir === "desc" ? "desc" : "asc";
111
+ }
112
+ }
113
+ }
114
+ const includeParam = searchParams.get("include");
115
+ if (includeParam) {
116
+ for (const rel of includeParam.split(",")) {
117
+ if (rel.trim()) include[rel.trim()] = true;
118
+ }
119
+ }
120
+ const selectParam = searchParams.get("select");
121
+ if (selectParam) {
122
+ select = {};
123
+ for (const field of selectParam.split(",")) {
124
+ if (field.trim()) select[field.trim()] = true;
125
+ }
126
+ }
127
+ for (const [key, value] of searchParams.entries()) {
128
+ if (RESERVED_KEYS.has(key)) continue;
129
+ let matched = false;
130
+ const sortedOps = Object.keys(FILTER_OPERATORS).sort(
131
+ (a, b) => b.length - a.length
132
+ );
133
+ for (const suffix of sortedOps) {
134
+ if (key.endsWith(suffix)) {
135
+ const field = key.slice(0, -suffix.length);
136
+ const prismaOp = FILTER_OPERATORS[suffix];
137
+ let parsedValue = value;
138
+ if (prismaOp === "in" || prismaOp === "notIn") {
139
+ parsedValue = value.split(",").map((v) => v.trim());
140
+ }
141
+ if (!isNaN(Number(parsedValue)) && typeof parsedValue === "string") {
142
+ parsedValue = Number(parsedValue);
143
+ }
144
+ const extra = suffix === "_icontains" ? { mode: "insensitive" } : {};
145
+ where[field] = { [prismaOp]: parsedValue, ...extra };
146
+ matched = true;
147
+ break;
148
+ }
149
+ }
150
+ if (!matched) {
151
+ let parsedValue = value;
152
+ if (value === "true") parsedValue = true;
153
+ else if (value === "false") parsedValue = false;
154
+ else if (!isNaN(Number(value)) && value !== "") {
155
+ parsedValue = Number(value);
156
+ }
157
+ where[key] = parsedValue;
158
+ }
159
+ }
160
+ return { where, orderBy, skip, take, include, select };
161
+ }
162
+
163
+ // src/middleware.ts
164
+ async function runGuard(guards, model, method, ctx) {
165
+ const modelGuards = guards[model];
166
+ if (!modelGuards) return null;
167
+ const fn = modelGuards[method];
168
+ if (!fn) return null;
169
+ return fn({ ...ctx, method });
170
+ }
171
+ async function runHook(hook, ctx) {
172
+ if (!hook) return;
173
+ try {
174
+ await hook(ctx);
175
+ } catch (e) {
176
+ console.error("[omni-rest] Hook error:", e);
177
+ }
178
+ }
179
+
180
+ // src/router.ts
181
+ function createRouter(prisma, options = {}) {
182
+ const {
183
+ allow,
184
+ guards = {},
185
+ beforeOperation,
186
+ afterOperation,
187
+ defaultLimit = 20,
188
+ maxLimit = 100
189
+ } = options;
190
+ const models = getModels(prisma);
191
+ const modelMap = buildModelMap(models, allow);
192
+ async function handle(method, modelName, id, body, searchParams, operation) {
193
+ const meta = modelMap[modelName.toLowerCase()];
194
+ if (!meta) {
195
+ return {
196
+ status: 404,
197
+ data: {
198
+ error: `Model "${modelName}" not found or not exposed.`,
199
+ available: Object.keys(modelMap)
200
+ }
201
+ };
202
+ }
203
+ const guardError = await runGuard(guards, meta.routeName, method, {
204
+ id,
205
+ body
206
+ });
207
+ if (guardError) {
208
+ return { status: 403, data: { error: guardError } };
209
+ }
210
+ await runHook(beforeOperation, { model: meta.name, method, id, body });
211
+ let result;
212
+ try {
213
+ result = await executeOperation(
214
+ prisma,
215
+ meta,
216
+ method,
217
+ id,
218
+ body,
219
+ searchParams,
220
+ defaultLimit,
221
+ maxLimit,
222
+ operation
223
+ );
224
+ } catch (e) {
225
+ return handlePrismaError(e);
226
+ }
227
+ await runHook(afterOperation, {
228
+ model: meta.name,
229
+ method,
230
+ id,
231
+ body,
232
+ result: result.data
233
+ });
234
+ return result;
235
+ }
236
+ return { handle, modelMap, models };
237
+ }
238
+ async function executeOperation(prisma, meta, method, id, body, searchParams, defaultLimit, maxLimit, operation) {
239
+ const delegate = getDelegate(prisma, meta);
240
+ const { where, orderBy, skip, take, include, select } = buildQuery(
241
+ searchParams,
242
+ defaultLimit,
243
+ maxLimit
244
+ );
245
+ const includeArg = Object.keys(include).length > 0 ? include : void 0;
246
+ const selectArg = select && Object.keys(select).length > 0 ? select : void 0;
247
+ const projection = selectArg ? { select: selectArg } : includeArg ? { include: includeArg } : {};
248
+ if (method === "PATCH" && operation === "bulk-update") {
249
+ if (!Array.isArray(body) || body.length === 0) {
250
+ return {
251
+ status: 400,
252
+ data: { error: "Request body must be a non-empty array of update records" }
253
+ };
254
+ }
255
+ for (const item of body) {
256
+ if (!item[meta.idField]) {
257
+ return {
258
+ status: 400,
259
+ data: { error: `Each record must have an ${meta.idField} field` }
260
+ };
261
+ }
262
+ }
263
+ const results = await Promise.all(
264
+ body.map((item) => {
265
+ const id2 = item[meta.idField];
266
+ const updateData = { ...item };
267
+ delete updateData[meta.idField];
268
+ return delegate.update({
269
+ where: { [meta.idField]: coerceId(id2) },
270
+ data: updateData,
271
+ ...projection
272
+ });
273
+ })
274
+ );
275
+ return {
276
+ status: 200,
277
+ data: {
278
+ updated: results.length,
279
+ records: results
280
+ }
281
+ };
282
+ }
283
+ if (method === "DELETE" && operation === "bulk-delete") {
284
+ if (!Array.isArray(body) || body.length === 0) {
285
+ return {
286
+ status: 400,
287
+ data: { error: "Request body must be a non-empty array of IDs" }
288
+ };
289
+ }
290
+ const ids = body.map(
291
+ (item) => typeof item === "object" ? item[meta.idField] : item
292
+ );
293
+ const result = await delegate.deleteMany({
294
+ where: {
295
+ [meta.idField]: { in: ids.map(coerceId) }
296
+ }
297
+ });
298
+ return {
299
+ status: 200,
300
+ data: {
301
+ deleted: result.count
302
+ }
303
+ };
304
+ }
305
+ if (method === "GET" && !id) {
306
+ const [data, total] = await prisma.$transaction([
307
+ delegate.findMany({ where, orderBy, skip, take, ...projection }),
308
+ delegate.count({ where })
309
+ ]);
310
+ return {
311
+ status: 200,
312
+ data: {
313
+ data,
314
+ meta: {
315
+ total,
316
+ page: Math.floor(skip / take) + 1,
317
+ limit: take,
318
+ totalPages: Math.ceil(total / take)
319
+ }
320
+ }
321
+ };
322
+ }
323
+ if (method === "GET" && id) {
324
+ const record = await delegate.findUnique({
325
+ where: { [meta.idField]: coerceId(id) },
326
+ ...projection
327
+ });
328
+ if (!record) {
329
+ return { status: 404, data: { error: `${meta.name} with id "${id}" not found.` } };
330
+ }
331
+ return { status: 200, data: record };
332
+ }
333
+ if (method === "POST" && !id) {
334
+ const record = await delegate.create({ data: body });
335
+ return { status: 201, data: record };
336
+ }
337
+ if ((method === "PUT" || method === "PATCH") && id) {
338
+ const record = await delegate.update({
339
+ where: { [meta.idField]: coerceId(id) },
340
+ data: body
341
+ });
342
+ return { status: 200, data: record };
343
+ }
344
+ if (method === "DELETE" && id) {
345
+ await delegate.delete({
346
+ where: { [meta.idField]: coerceId(id) }
347
+ });
348
+ return { status: 204, data: null };
349
+ }
350
+ return { status: 405, data: { error: `Method ${method} not allowed.` } };
351
+ }
352
+ function coerceId(id) {
353
+ const n = Number(id);
354
+ return isNaN(n) ? id : n;
355
+ }
356
+ function handlePrismaError(e) {
357
+ const code = e?.code;
358
+ if (code === "P2025") {
359
+ return { status: 404, data: { error: "Record not found." } };
360
+ }
361
+ if (code === "P2002") {
362
+ const fields = e?.meta?.target ?? "unknown fields";
363
+ return {
364
+ status: 409,
365
+ data: { error: `Unique constraint failed on: ${fields}` }
366
+ };
367
+ }
368
+ if (code === "P2003") {
369
+ return { status: 400, data: { error: "Foreign key constraint failed." } };
370
+ }
371
+ if (code === "P2014") {
372
+ return { status: 400, data: { error: "Relation violation." } };
373
+ }
374
+ return { status: 500, data: { error: e?.message ?? "Internal server error." } };
375
+ }
376
+
377
+ // src/adapters/nextjs.ts
378
+ function nextjsAdapter(prisma, options = {}) {
379
+ const { handle } = createRouter(prisma, options);
380
+ return async function handler(req, context) {
381
+ const segments = context.params.prismaRest ?? [];
382
+ const [modelName, ...pathSegments] = segments;
383
+ if (!modelName) {
384
+ return Response.json(
385
+ { error: "No model specified in path." },
386
+ { status: 400 }
387
+ );
388
+ }
389
+ const url = new URL(req.url);
390
+ let body = {};
391
+ if (req.method !== "GET" && req.method !== "DELETE") {
392
+ try {
393
+ body = await req.json();
394
+ } catch {
395
+ body = {};
396
+ }
397
+ }
398
+ let operation;
399
+ let id = null;
400
+ if (pathSegments[0] === "bulk" && pathSegments[1] === "update") {
401
+ operation = "bulk-update";
402
+ } else if (pathSegments[0] === "bulk" && pathSegments[1] === "delete") {
403
+ operation = "bulk-delete";
404
+ } else {
405
+ id = pathSegments[0] ?? null;
406
+ }
407
+ const { status, data } = await handle(
408
+ req.method,
409
+ modelName,
410
+ id,
411
+ body,
412
+ url.searchParams,
413
+ operation
414
+ );
415
+ if (status === 204) {
416
+ return new Response(null, { status: 204 });
417
+ }
418
+ return Response.json(data, { status });
419
+ };
420
+ }
421
+
422
+ export { nextjsAdapter };
423
+ //# sourceMappingURL=nextjs.mjs.map
424
+ //# sourceMappingURL=nextjs.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/introspect.ts","../../src/query-builder.ts","../../src/middleware.ts","../../src/router.ts","../../src/adapters/nextjs.ts"],"names":["id"],"mappings":"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","file":"nextjs.mjs","sourcesContent":["import { Prisma } from \"@prisma/client\";\r\nimport 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\r\n if (!raw) {\r\n const dmmfModels =\r\n (Prisma as any)?.dmmf?.datamodel?.models ||\r\n (require(\"@prisma/client\")?.Prisma as any)?.dmmf?.datamodel?.models;\r\n\r\n if (dmmfModels) {\r\n raw = dmmfModels;\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 }));\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 * 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 } 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]);\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\r\n */\r\nexport function buildQuery(\r\n searchParams: URLSearchParams,\r\n defaultLimit = 20,\r\n maxLimit = 100\r\n): ParsedQuery {\r\n const where: Record<string, any> = {};\r\n const orderBy: Record<string, \"asc\" | \"desc\"> = {};\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\r\n for (const part of sortParam.split(\",\")) {\r\n const [field, dir] = part.trim().split(\":\");\r\n if (field) {\r\n orderBy[field] = (dir === \"desc\" ? \"desc\" : \"asc\");\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) ──────────────────────────────────────────────────────\r\n const selectParam = searchParams.get(\"select\");\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 // ─── 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 } from \"./introspect\";\r\nimport { buildQuery } from \"./query-builder\";\r\nimport { runGuard, runHook } from \"./middleware\";\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 } = 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 );\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): 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 );\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 const [data, total] = await (prisma as any).$transaction([\r\n delegate.findMany({ where, orderBy, skip, take, ...projection }),\r\n delegate.count({ where }),\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 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 * Next.js App Router adapter for omni-rest.\r\n *\r\n * @example\r\n * ```ts\r\n * // app/api/[...prismaRest]/route.ts\r\n * import { PrismaClient } from \"@prisma/client\";\r\n * import { nextjsAdapter } from \"omni-rest/nextjs\";\r\n *\r\n * const prisma = new PrismaClient();\r\n * const handler = nextjsAdapter(prisma, {\r\n * allow: [\"department\", \"category\"],\r\n * });\r\n *\r\n * export const GET = handler;\r\n * export const POST = handler;\r\n * export const PUT = handler;\r\n * export const PATCH = handler;\r\n * export const DELETE = handler;\r\n * ```\r\n *\r\n * This covers:\r\n * GET /api/department\r\n * POST /api/department\r\n * GET /api/department/5\r\n * PUT /api/department/5\r\n * PATCH /api/department/5\r\n * DELETE /api/department/5\r\n * PATCH /api/department/bulk/update\r\n * DELETE /api/department/bulk/delete\r\n */\r\nexport function nextjsAdapter(\r\n prisma: PrismaClient,\r\n options: PrismaRestOptions = {}\r\n) {\r\n const { handle } = createRouter(prisma, options);\r\n\r\n return async function handler(\r\n req: Request,\r\n context: { params: { prismaRest: string[] } }\r\n ): Promise<Response> {\r\n const segments = context.params.prismaRest ?? [];\r\n const [modelName, ...pathSegments] = segments;\r\n\r\n if (!modelName) {\r\n return Response.json(\r\n { error: \"No model specified in path.\" },\r\n { status: 400 }\r\n );\r\n }\r\n\r\n const url = new URL(req.url);\r\n\r\n let body: any = {};\r\n if (req.method !== \"GET\" && req.method !== \"DELETE\") {\r\n try {\r\n body = await req.json();\r\n } catch {\r\n body = {};\r\n }\r\n }\r\n\r\n // Detect bulk operations\r\n let operation: string | undefined;\r\n let id: string | null = null;\r\n\r\n if (pathSegments[0] === \"bulk\" && pathSegments[1] === \"update\") {\r\n operation = \"bulk-update\";\r\n } else if (pathSegments[0] === \"bulk\" && pathSegments[1] === \"delete\") {\r\n operation = \"bulk-delete\";\r\n } else {\r\n id = pathSegments[0] ?? null;\r\n }\r\n\r\n const { status, data } = await handle(\r\n req.method,\r\n modelName,\r\n id,\r\n body,\r\n url.searchParams,\r\n operation\r\n );\r\n\r\n if (status === 204) {\r\n return new Response(null, { status: 204 });\r\n }\r\n\r\n return Response.json(data, { status });\r\n };\r\n}"]}
package/dist/cli.d.mts ADDED
@@ -0,0 +1 @@
1
+ #!/usr/bin/env node
package/dist/cli.d.ts ADDED
@@ -0,0 +1 @@
1
+ #!/usr/bin/env node