omni-rest 0.3.3 → 0.4.2

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/index.d.mts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { PrismaClient } from '@prisma/client';
2
- import { P as PrismaRestOptions, R as RouterInstance, M as ModelMeta, a as ParsedQuery, G as GuardMap, H as HookFn, b as HookContext } from './types-jvppYvku.mjs';
3
- export { F as FieldMeta, c as GuardFn, d as HandlerResult } from './types-jvppYvku.mjs';
2
+ import { P as PrismaRestOptions, R as RouterInstance, M as ModelMeta, F as FieldMeta, a as ParsedQuery, G as GuardMap, H as HookFn, b as HookContext } from './types-Dhk8VaWX.mjs';
3
+ export { c as GuardFn, d as HandlerResult } from './types-Dhk8VaWX.mjs';
4
4
  export { expressAdapter } from './adapters/express.mjs';
5
5
  export { nextjsAdapter } from './adapters/nextjs.mjs';
6
6
  export { fastifyAdapter } from './adapters/fastify.mjs';
@@ -43,9 +43,10 @@ declare function getDelegate(prisma: any, meta: ModelMeta): any;
43
43
  * Sorting → ?sort=createdAt:desc or ?sort=name:asc
44
44
  * Pagination → ?page=2&limit=10
45
45
  * Relations → ?include=posts,profile
46
- * Fields → ?select=id,name,email
46
+ * Fields → ?select=id,name,email or ?fields=id,name,email
47
+ * Search → ?search=eng (queries all String fields with OR)
47
48
  */
48
- declare function buildQuery(searchParams: URLSearchParams, defaultLimit?: number, maxLimit?: number): ParsedQuery;
49
+ declare function buildQuery(searchParams: URLSearchParams, defaultLimit?: number, maxLimit?: number, modelFields?: FieldMeta[]): ParsedQuery;
49
50
 
50
51
  /**
51
52
  * Runs the guard for the given model+method combo.
@@ -129,6 +130,22 @@ declare function generateOpenApiSpec(prisma: any, options?: {
129
130
  }[];
130
131
  }): object;
131
132
 
133
+ /**
134
+ * The shared config format written by the backend CLI and consumed by omni-rest-client.
135
+ * No Prisma dependency on the consumer side — just plain JSON.
136
+ */
137
+ interface OmniRestConfig {
138
+ version: string;
139
+ generatedAt: string;
140
+ models: ModelMeta[];
141
+ zodSchemas: string;
142
+ }
143
+ /**
144
+ * Generates the omni-rest.config.json content from a Prisma client instance.
145
+ * This is the bridge between the backend (Prisma) and the client CLI (no Prisma).
146
+ */
147
+ declare function generateConfig(prisma: any): OmniRestConfig;
148
+
132
149
  /**
133
150
  * Koa adapter for omni-rest.
134
151
  * Allows using omni-rest within a Koa application using @koa/router.
@@ -213,4 +230,4 @@ declare const hapiAdapter: {
213
230
  */
214
231
  declare function nestjsController(prisma: PrismaClient, options?: PrismaRestOptions, prefix?: string): any;
215
232
 
216
- export { GuardMap, HookContext, HookFn, ModelMeta, ParsedQuery, PrismaRestOptions, RouterInstance, buildModelMap, buildQuery, buildRuntimeSchemas, createRouter, generateOpenApiSpec, generateZodSchemas, getDelegate, getModels, hapiAdapter, koaAdapter, nestjsController, runGuard, runHook, toRouteName, validateBody, withValidation };
233
+ export { FieldMeta, GuardMap, HookContext, HookFn, ModelMeta, type OmniRestConfig, ParsedQuery, PrismaRestOptions, RouterInstance, buildModelMap, buildQuery, buildRuntimeSchemas, createRouter, generateConfig, generateOpenApiSpec, generateZodSchemas, getDelegate, getModels, hapiAdapter, koaAdapter, nestjsController, runGuard, runHook, toRouteName, validateBody, withValidation };
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { PrismaClient } from '@prisma/client';
2
- import { P as PrismaRestOptions, R as RouterInstance, M as ModelMeta, a as ParsedQuery, G as GuardMap, H as HookFn, b as HookContext } from './types-jvppYvku.js';
3
- export { F as FieldMeta, c as GuardFn, d as HandlerResult } from './types-jvppYvku.js';
2
+ import { P as PrismaRestOptions, R as RouterInstance, M as ModelMeta, F as FieldMeta, a as ParsedQuery, G as GuardMap, H as HookFn, b as HookContext } from './types-Dhk8VaWX.js';
3
+ export { c as GuardFn, d as HandlerResult } from './types-Dhk8VaWX.js';
4
4
  export { expressAdapter } from './adapters/express.js';
5
5
  export { nextjsAdapter } from './adapters/nextjs.js';
6
6
  export { fastifyAdapter } from './adapters/fastify.js';
@@ -43,9 +43,10 @@ declare function getDelegate(prisma: any, meta: ModelMeta): any;
43
43
  * Sorting → ?sort=createdAt:desc or ?sort=name:asc
44
44
  * Pagination → ?page=2&limit=10
45
45
  * Relations → ?include=posts,profile
46
- * Fields → ?select=id,name,email
46
+ * Fields → ?select=id,name,email or ?fields=id,name,email
47
+ * Search → ?search=eng (queries all String fields with OR)
47
48
  */
48
- declare function buildQuery(searchParams: URLSearchParams, defaultLimit?: number, maxLimit?: number): ParsedQuery;
49
+ declare function buildQuery(searchParams: URLSearchParams, defaultLimit?: number, maxLimit?: number, modelFields?: FieldMeta[]): ParsedQuery;
49
50
 
50
51
  /**
51
52
  * Runs the guard for the given model+method combo.
@@ -129,6 +130,22 @@ declare function generateOpenApiSpec(prisma: any, options?: {
129
130
  }[];
130
131
  }): object;
131
132
 
133
+ /**
134
+ * The shared config format written by the backend CLI and consumed by omni-rest-client.
135
+ * No Prisma dependency on the consumer side — just plain JSON.
136
+ */
137
+ interface OmniRestConfig {
138
+ version: string;
139
+ generatedAt: string;
140
+ models: ModelMeta[];
141
+ zodSchemas: string;
142
+ }
143
+ /**
144
+ * Generates the omni-rest.config.json content from a Prisma client instance.
145
+ * This is the bridge between the backend (Prisma) and the client CLI (no Prisma).
146
+ */
147
+ declare function generateConfig(prisma: any): OmniRestConfig;
148
+
132
149
  /**
133
150
  * Koa adapter for omni-rest.
134
151
  * Allows using omni-rest within a Koa application using @koa/router.
@@ -213,4 +230,4 @@ declare const hapiAdapter: {
213
230
  */
214
231
  declare function nestjsController(prisma: PrismaClient, options?: PrismaRestOptions, prefix?: string): any;
215
232
 
216
- export { GuardMap, HookContext, HookFn, ModelMeta, ParsedQuery, PrismaRestOptions, RouterInstance, buildModelMap, buildQuery, buildRuntimeSchemas, createRouter, generateOpenApiSpec, generateZodSchemas, getDelegate, getModels, hapiAdapter, koaAdapter, nestjsController, runGuard, runHook, toRouteName, validateBody, withValidation };
233
+ export { FieldMeta, GuardMap, HookContext, HookFn, ModelMeta, type OmniRestConfig, ParsedQuery, PrismaRestOptions, RouterInstance, buildModelMap, buildQuery, buildRuntimeSchemas, createRouter, generateConfig, generateOpenApiSpec, generateZodSchemas, getDelegate, getModels, hapiAdapter, koaAdapter, nestjsController, runGuard, runHook, toRouteName, validateBody, withValidation };
package/dist/index.js CHANGED
@@ -79,6 +79,19 @@ function buildModelMap(models, allowList) {
79
79
  const filtered = allowList ? models.filter((m) => allowList.includes(m.routeName)) : models;
80
80
  return Object.fromEntries(filtered.map((m) => [m.routeName, m]));
81
81
  }
82
+ function detectSoftDeleteField(fields, explicitField) {
83
+ if (explicitField) {
84
+ const f = fields.find((f2) => f2.name === explicitField);
85
+ if (!f) return null;
86
+ const value = f.type === "Boolean" ? false : /* @__PURE__ */ new Date();
87
+ return { field: explicitField, value };
88
+ }
89
+ const deletedAt = fields.find((f) => f.name === "deletedAt" && f.type === "DateTime");
90
+ if (deletedAt) return { field: "deletedAt", value: /* @__PURE__ */ new Date() };
91
+ const isActive = fields.find((f) => f.name === "isActive" && f.type === "Boolean");
92
+ if (isActive) return { field: "isActive", value: false };
93
+ return null;
94
+ }
82
95
  function getDelegate(prisma, meta) {
83
96
  const key = meta.name.charAt(0).toLowerCase() + meta.name.slice(1);
84
97
  const delegate = prisma[key];
@@ -110,9 +123,11 @@ var RESERVED_KEYS = /* @__PURE__ */ new Set([
110
123
  "limit",
111
124
  "sort",
112
125
  "include",
113
- "select"
126
+ "select",
127
+ "fields",
128
+ "search"
114
129
  ]);
115
- function buildQuery(searchParams, defaultLimit = 20, maxLimit = 100) {
130
+ function buildQuery(searchParams, defaultLimit = 20, maxLimit = 100, modelFields) {
116
131
  const where = {};
117
132
  const orderBy = {};
118
133
  let include = {};
@@ -136,13 +151,25 @@ function buildQuery(searchParams, defaultLimit = 20, maxLimit = 100) {
136
151
  if (rel.trim()) include[rel.trim()] = true;
137
152
  }
138
153
  }
139
- const selectParam = searchParams.get("select");
154
+ const selectParam = searchParams.get("select") ?? searchParams.get("fields");
140
155
  if (selectParam) {
141
156
  select = {};
142
157
  for (const field of selectParam.split(",")) {
143
158
  if (field.trim()) select[field.trim()] = true;
144
159
  }
145
160
  }
161
+ const searchValue = searchParams.get("search");
162
+ if (searchValue && modelFields) {
163
+ const stringFields = modelFields.filter(
164
+ (f) => f.type === "String" && !f.isRelation
165
+ );
166
+ if (stringFields.length > 0) {
167
+ const orClauses = stringFields.map((f) => ({
168
+ [f.name]: { contains: searchValue, mode: "insensitive" }
169
+ }));
170
+ where["OR"] = orClauses;
171
+ }
172
+ }
146
173
  for (const [key, value] of searchParams.entries()) {
147
174
  if (RESERVED_KEYS.has(key)) continue;
148
175
  let matched = false;
@@ -204,7 +231,9 @@ function createRouter(prisma, options = {}) {
204
231
  beforeOperation,
205
232
  afterOperation,
206
233
  defaultLimit = 20,
207
- maxLimit = 100
234
+ maxLimit = 100,
235
+ softDelete = false,
236
+ softDeleteField
208
237
  } = options;
209
238
  const models = getModels(prisma);
210
239
  const modelMap = buildModelMap(models, allow);
@@ -238,7 +267,9 @@ function createRouter(prisma, options = {}) {
238
267
  searchParams,
239
268
  defaultLimit,
240
269
  maxLimit,
241
- operation
270
+ operation,
271
+ softDelete,
272
+ softDeleteField
242
273
  );
243
274
  } catch (e) {
244
275
  return handlePrismaError(e);
@@ -254,12 +285,13 @@ function createRouter(prisma, options = {}) {
254
285
  }
255
286
  return { handle, modelMap, models };
256
287
  }
257
- async function executeOperation(prisma, meta, method, id, body, searchParams, defaultLimit, maxLimit, operation) {
288
+ async function executeOperation(prisma, meta, method, id, body, searchParams, defaultLimit, maxLimit, operation, softDelete = false, softDeleteField) {
258
289
  const delegate = getDelegate(prisma, meta);
259
290
  const { where, orderBy, skip, take, include, select } = buildQuery(
260
291
  searchParams,
261
292
  defaultLimit,
262
- maxLimit
293
+ maxLimit,
294
+ meta.fields
263
295
  );
264
296
  const includeArg = Object.keys(include).length > 0 ? include : void 0;
265
297
  const selectArg = select && Object.keys(select).length > 0 ? select : void 0;
@@ -322,9 +354,11 @@ async function executeOperation(prisma, meta, method, id, body, searchParams, de
322
354
  };
323
355
  }
324
356
  if (method === "GET" && !id) {
357
+ const softDeleteInfo = softDelete ? detectSoftDeleteField(meta.fields, softDeleteField) : null;
358
+ const listWhere = softDeleteInfo ? { ...where, [softDeleteInfo.field]: softDeleteInfo.field === "isActive" ? true : null } : where;
325
359
  const [data, total] = await prisma.$transaction([
326
- delegate.findMany({ where, orderBy, skip, take, ...projection }),
327
- delegate.count({ where })
360
+ delegate.findMany({ where: listWhere, orderBy, skip, take, ...projection }),
361
+ delegate.count({ where: listWhere })
328
362
  ]);
329
363
  return {
330
364
  status: 200,
@@ -361,6 +395,14 @@ async function executeOperation(prisma, meta, method, id, body, searchParams, de
361
395
  return { status: 200, data: record };
362
396
  }
363
397
  if (method === "DELETE" && id) {
398
+ const softDeleteInfo = softDelete ? detectSoftDeleteField(meta.fields, softDeleteField) : null;
399
+ if (softDeleteInfo) {
400
+ const record = await delegate.update({
401
+ where: { [meta.idField]: coerceId(id) },
402
+ data: { [softDeleteInfo.field]: softDeleteInfo.value }
403
+ });
404
+ return { status: 200, data: record };
405
+ }
364
406
  await delegate.delete({
365
407
  where: { [meta.idField]: coerceId(id) }
366
408
  });
@@ -864,6 +906,68 @@ function buildListParameters() {
864
906
  ];
865
907
  }
866
908
 
909
+ // src/config-generator.ts
910
+ function generateConfig(prisma) {
911
+ const models = getModels(prisma);
912
+ const PRISMA_TO_ZOD2 = {
913
+ String: "z.string()",
914
+ Int: "z.number().int()",
915
+ Float: "z.number()",
916
+ Decimal: "z.number()",
917
+ Boolean: "z.boolean()",
918
+ DateTime: "z.coerce.date()",
919
+ Json: "z.any()",
920
+ BigInt: "z.bigint()",
921
+ Bytes: "z.any()"
922
+ };
923
+ function fieldToZod2(field) {
924
+ if (field.isRelation) return null;
925
+ let zod = PRISMA_TO_ZOD2[field.type] ?? "z.any()";
926
+ if (!field.isRequired) zod = `${zod}.optional()`;
927
+ if (field.isList) zod = `z.array(${zod})`;
928
+ return zod;
929
+ }
930
+ const schemaBlocks = models.map((meta) => {
931
+ const createFields = meta.fields.filter((f) => !f.isRelation && !f.isId && !f.hasDefaultValue && !f.isUpdatedAt).map((f) => {
932
+ const z = fieldToZod2(f);
933
+ return z ? ` ${f.name}: ${z},` : null;
934
+ }).filter(Boolean).join("\n");
935
+ const updateFields = meta.fields.filter((f) => !f.isRelation && !f.isId && !f.isUpdatedAt).map((f) => {
936
+ const z = fieldToZod2(f);
937
+ if (!z) return null;
938
+ const opt = z.includes(".optional()") ? z : `${z}.optional()`;
939
+ return ` ${f.name}: ${opt},`;
940
+ }).filter(Boolean).join("\n");
941
+ return `
942
+ // \u2500\u2500\u2500 ${meta.name} \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
943
+
944
+ export const ${meta.name}CreateSchema = z.object({
945
+ ${createFields}
946
+ });
947
+
948
+ export const ${meta.name}UpdateSchema = z.object({
949
+ ${updateFields}
950
+ });
951
+
952
+ export type ${meta.name}Create = z.infer<typeof ${meta.name}CreateSchema>;
953
+ export type ${meta.name}Update = z.infer<typeof ${meta.name}UpdateSchema>;`.trim();
954
+ });
955
+ const zodSchemas = `/**
956
+ * Auto-generated Zod schemas from Prisma schema.
957
+ * Generated by omni-rest \u2014 do not edit manually.
958
+ */
959
+ import { z } from "zod";
960
+
961
+ ${schemaBlocks.join("\n\n")}
962
+ `;
963
+ return {
964
+ version: "1",
965
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
966
+ models,
967
+ zodSchemas
968
+ };
969
+ }
970
+
867
971
  // src/adapters/express.ts
868
972
  function expressAdapter(prisma, options = {}) {
869
973
  const { Router } = __require("express");
@@ -1328,6 +1432,7 @@ exports.buildRuntimeSchemas = buildRuntimeSchemas;
1328
1432
  exports.createRouter = createRouter;
1329
1433
  exports.expressAdapter = expressAdapter;
1330
1434
  exports.fastifyAdapter = fastifyAdapter;
1435
+ exports.generateConfig = generateConfig;
1331
1436
  exports.generateOpenApiSpec = generateOpenApiSpec;
1332
1437
  exports.generateZodSchemas = generateZodSchemas;
1333
1438
  exports.getDelegate = getDelegate;