prisma-generator-express 1.12.0 → 1.13.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.
Files changed (67) hide show
  1. package/README.md +51 -3
  2. package/dist/generator.js +2 -12
  3. package/dist/generator.js.map +1 -1
  4. package/dist/helpers/generateAggregate.js +13 -13
  5. package/dist/helpers/generateAggregate.js.map +1 -1
  6. package/dist/helpers/generateCount.js +12 -13
  7. package/dist/helpers/generateCount.js.map +1 -1
  8. package/dist/helpers/generateCreate.js +13 -15
  9. package/dist/helpers/generateCreate.js.map +1 -1
  10. package/dist/helpers/generateCreateMany.js +13 -15
  11. package/dist/helpers/generateCreateMany.js.map +1 -1
  12. package/dist/helpers/generateDelete.js +12 -15
  13. package/dist/helpers/generateDelete.js.map +1 -1
  14. package/dist/helpers/generateDeleteMany.js +13 -14
  15. package/dist/helpers/generateDeleteMany.js.map +1 -1
  16. package/dist/helpers/generateFindFirst.js +10 -15
  17. package/dist/helpers/generateFindFirst.js.map +1 -1
  18. package/dist/helpers/generateFindMany.js +10 -15
  19. package/dist/helpers/generateFindMany.js.map +1 -1
  20. package/dist/helpers/generateFindUnique.js +10 -15
  21. package/dist/helpers/generateFindUnique.js.map +1 -1
  22. package/dist/helpers/generateGroupBy.js +12 -13
  23. package/dist/helpers/generateGroupBy.js.map +1 -1
  24. package/dist/helpers/generateRouteFile.js +68 -35
  25. package/dist/helpers/generateRouteFile.js.map +1 -1
  26. package/dist/helpers/generateUpdate.js +12 -15
  27. package/dist/helpers/generateUpdate.js.map +1 -1
  28. package/dist/helpers/generateUpdateMany.js +12 -15
  29. package/dist/helpers/generateUpdateMany.js.map +1 -1
  30. package/dist/helpers/generateUpsert.js +12 -15
  31. package/dist/helpers/generateUpsert.js.map +1 -1
  32. package/dist/utils/copyFiles.js +26 -0
  33. package/dist/utils/copyFiles.js.map +1 -0
  34. package/package.json +4 -1
  35. package/src/copy/createOutputValidatorMiddleware.ts +44 -0
  36. package/src/copy/createValidatorMiddleware.ts +55 -0
  37. package/src/copy/encodeQueryParams.spec.ts +303 -0
  38. package/src/copy/encodeQueryParams.ts +44 -0
  39. package/src/copy/misc.spec.ts +62 -0
  40. package/src/copy/misc.ts +25 -0
  41. package/src/copy/parseQueryParams.spec.ts +187 -0
  42. package/src/copy/parseQueryParams.ts +42 -0
  43. package/src/copy/routeConfig.ts +34 -0
  44. package/src/copy/transformZod.spec.ts +556 -0
  45. package/src/copy/transformZod.ts +119 -0
  46. package/src/generator.ts +3 -13
  47. package/src/helpers/generateAggregate.ts +13 -13
  48. package/src/helpers/generateCount.ts +12 -13
  49. package/src/helpers/generateCreate.ts +14 -15
  50. package/src/helpers/generateCreateMany.ts +13 -15
  51. package/src/helpers/generateDelete.ts +13 -15
  52. package/src/helpers/generateDeleteMany.ts +14 -14
  53. package/src/helpers/generateFindFirst.ts +10 -15
  54. package/src/helpers/generateFindMany.ts +10 -15
  55. package/src/helpers/generateFindUnique.ts +10 -15
  56. package/src/helpers/generateGroupBy.ts +12 -13
  57. package/src/helpers/generateRouteFile.ts +68 -35
  58. package/src/helpers/generateUpdate.ts +13 -15
  59. package/src/helpers/generateUpdateMany.ts +13 -15
  60. package/src/helpers/generateUpsert.ts +13 -15
  61. package/src/utils/copyFiles.ts +27 -0
  62. package/dist/helpers/generateQsParser.js +0 -56
  63. package/dist/helpers/generateQsParser.js.map +0 -1
  64. package/dist/helpers/generateRouteConfigType.js +0 -34
  65. package/dist/helpers/generateRouteConfigType.js.map +0 -1
  66. package/src/helpers/generateQsParser.ts +0 -51
  67. package/src/helpers/generateRouteConfigType.ts +0 -29
@@ -0,0 +1,556 @@
1
+ import { z, ZodError } from 'zod'
2
+ import { allow, flattenObject, forbid } from './transformZod'
3
+
4
+ describe('Cryptocurrency Schema Validation', () => {
5
+ const cryptoSchema = z.object({
6
+ transactions: z
7
+ .array(
8
+ z.object({
9
+ amount: z.number(),
10
+ currency: z.string(),
11
+ date: z.string().optional(),
12
+ details: z
13
+ .object({
14
+ fee: z.number().optional(),
15
+ type: z.string(),
16
+ })
17
+ .optional(),
18
+ }),
19
+ )
20
+ .optional(),
21
+ wallet: z.object({
22
+ id: z.string(),
23
+ owner: z.object({
24
+ name: z.string(),
25
+ age: z.number().optional(),
26
+ }),
27
+ }),
28
+ })
29
+
30
+ const allowedFields = [
31
+ 'wallet.id',
32
+ 'wallet.owner.name',
33
+ 'transactions.amount',
34
+ 'transactions.currency',
35
+ ]
36
+ const forbiddenFields = ['wallet.owner.age', 'transactions.details.fee']
37
+
38
+ describe('Allow Function', () => {
39
+ it('should validate data with only allowed fields', () => {
40
+ const inputData = {
41
+ transactions: [
42
+ {
43
+ amount: 100,
44
+ currency: 'BTC',
45
+ },
46
+ ],
47
+ wallet: {
48
+ id: 'wallet123',
49
+ owner: {
50
+ name: 'Alice',
51
+ },
52
+ },
53
+ }
54
+
55
+ try {
56
+ const result = allow(cryptoSchema, allowedFields).safeParse(inputData)
57
+
58
+ expect(result.success).toBe(true)
59
+ } catch (error) {
60
+ throw error
61
+ }
62
+ })
63
+
64
+ it('should return an error for data with disallowed fields', () => {
65
+ const inputData = {
66
+ transactions: [
67
+ {
68
+ amount: 100,
69
+ currency: 'BTC',
70
+ details: {
71
+ fee: 0.01,
72
+ type: 'transfer',
73
+ },
74
+ },
75
+ ],
76
+ wallet: {
77
+ id: 'wallet123',
78
+ owner: {
79
+ name: 'Alice',
80
+ age: 30,
81
+ },
82
+ },
83
+ }
84
+
85
+ try {
86
+ const result = allow(cryptoSchema, allowedFields).safeParse(inputData)
87
+
88
+ expect(result.success).toBe(false)
89
+ } catch (error) {
90
+ expect(error).toBeInstanceOf(ZodError)
91
+ }
92
+ })
93
+ })
94
+
95
+ describe('Forbid Function', () => {
96
+ it('should successfully parse when forbidden fields are absent', () => {
97
+ const inputData = {
98
+ transactions: [
99
+ {
100
+ amount: 200,
101
+ currency: 'ETH',
102
+ },
103
+ ],
104
+ wallet: {
105
+ id: 'wallet456',
106
+ owner: {
107
+ name: 'Bob',
108
+ },
109
+ },
110
+ }
111
+
112
+ try {
113
+ const result = forbid(cryptoSchema, forbiddenFields).safeParse(
114
+ inputData,
115
+ )
116
+
117
+ expect(result.success).toBe(true)
118
+ } catch (error) {
119
+ throw error
120
+ }
121
+ })
122
+
123
+ it('should return an error when forbidden fields are present', () => {
124
+ const inputData = {
125
+ transactions: [
126
+ {
127
+ amount: 200,
128
+ currency: 'ETH',
129
+ details: {
130
+ fee: 0.02,
131
+ type: 'exchange',
132
+ },
133
+ },
134
+ ],
135
+ wallet: {
136
+ id: 'wallet456',
137
+ owner: {
138
+ name: 'Bob',
139
+ age: 40,
140
+ },
141
+ },
142
+ }
143
+
144
+ try {
145
+ const result = forbid(cryptoSchema, forbiddenFields).safeParse(
146
+ inputData,
147
+ )
148
+
149
+ expect(result.success).toBe(false)
150
+ } catch (error) {
151
+ expect(error).toBeInstanceOf(ZodError)
152
+ }
153
+ })
154
+ })
155
+
156
+ it('should handle deeply nested structures correctly', () => {
157
+ const inputData = {
158
+ transactions: [
159
+ {
160
+ amount: 300,
161
+ currency: 'XRP',
162
+ details: {
163
+ fee: 0.03,
164
+ type: 'purchase',
165
+ },
166
+ },
167
+ ],
168
+ wallet: {
169
+ id: 'wallet789',
170
+ owner: {
171
+ name: 'Charlie',
172
+ age: 50,
173
+ },
174
+ },
175
+ }
176
+
177
+ try {
178
+ const result = forbid(cryptoSchema, forbiddenFields).safeParse(inputData)
179
+ expect(result.success).toBe(false)
180
+ } catch (error) {
181
+ expect(error).toBeInstanceOf(ZodError)
182
+ }
183
+ })
184
+
185
+ it('should not error for absent optional fields', () => {
186
+ const inputData = {
187
+ transactions: [],
188
+ wallet: {
189
+ id: 'wallet012',
190
+ owner: {
191
+ name: 'Dave',
192
+ },
193
+ },
194
+ }
195
+
196
+ try {
197
+ const result = allow(cryptoSchema, allowedFields).safeParse(inputData)
198
+ expect(result.success).toBe(true)
199
+ } catch (error) {
200
+ throw error
201
+ }
202
+ })
203
+
204
+ it('should flag all fields when all are forbidden', () => {
205
+ const inputData = {
206
+ transactions: [
207
+ {
208
+ amount: 400,
209
+ currency: 'LTC',
210
+ details: {
211
+ fee: 0.04,
212
+ type: 'withdrawal',
213
+ },
214
+ },
215
+ ],
216
+ wallet: {
217
+ id: 'wallet345',
218
+ owner: {
219
+ name: 'Eve',
220
+ age: 60,
221
+ },
222
+ },
223
+ }
224
+ const allForbidden = [
225
+ 'wallet.id',
226
+ 'wallet.owner.name',
227
+ 'wallet.owner.age',
228
+ 'transactions.amount',
229
+ 'transactions.currency',
230
+ 'transactions.details.fee',
231
+ ]
232
+
233
+ try {
234
+ const result = forbid(cryptoSchema, allForbidden).safeParse(inputData)
235
+ expect(result.success).toBe(false)
236
+ } catch (error) {
237
+ expect(error).toBeInstanceOf(ZodError)
238
+ }
239
+ })
240
+
241
+ it('should handle combination of allowed and forbidden correctly', () => {
242
+ const inputData = {
243
+ transactions: [
244
+ {
245
+ amount: 500,
246
+ currency: 'ADA',
247
+ details: {
248
+ fee: 0.05,
249
+ type: 'deposit',
250
+ },
251
+ },
252
+ ],
253
+ wallet: {
254
+ id: 'wallet678',
255
+ owner: {
256
+ name: 'Frank',
257
+ age: 70,
258
+ },
259
+ },
260
+ }
261
+
262
+ try {
263
+ const resultAllow = allow(cryptoSchema, [
264
+ 'wallet.owner.name',
265
+ 'transactions.currency',
266
+ ]).safeParse(inputData)
267
+ expect(resultAllow.success).toBe(false)
268
+ } catch (error) {
269
+ expect(error).toBeInstanceOf(ZodError)
270
+ }
271
+
272
+ try {
273
+ const resultForbid = forbid(cryptoSchema, forbiddenFields).safeParse(
274
+ inputData,
275
+ )
276
+
277
+ expect(resultForbid.success).toBe(false)
278
+ } catch (error) {
279
+ expect(error).toBeInstanceOf(ZodError)
280
+ }
281
+ })
282
+
283
+ describe('Additional Cryptocurrency Schema Validation', () => {
284
+ it('should return an error for invalid input types', () => {
285
+ const inputData = {
286
+ transactions: [
287
+ {
288
+ amount: '100', // Invalid type: should be a number
289
+ currency: 'BTC',
290
+ },
291
+ ],
292
+ wallet: {
293
+ id: 123, // Invalid type: should be a string
294
+ owner: {
295
+ name: 'Alice',
296
+ },
297
+ },
298
+ }
299
+
300
+ try {
301
+ const result = allow(cryptoSchema, allowedFields).safeParse(inputData)
302
+ expect(result.success).toBe(false)
303
+ } catch (error) {
304
+ expect(error).toBeInstanceOf(ZodError)
305
+ }
306
+ })
307
+
308
+ it('should handle deeply nested arrays correctly', () => {
309
+ const inputData = {
310
+ transactions: [
311
+ {
312
+ amount: 300,
313
+ currency: 'XRP',
314
+ details: {
315
+ fee: 0.03,
316
+ type: 'purchase',
317
+ },
318
+ },
319
+ ],
320
+ wallet: {
321
+ id: 'wallet789',
322
+ owner: {
323
+ name: 'Charlie',
324
+ age: 50,
325
+ },
326
+ },
327
+ }
328
+
329
+ try {
330
+ const result = forbid(cryptoSchema, forbiddenFields).safeParse(
331
+ inputData,
332
+ )
333
+
334
+ expect(result.success).toBe(false)
335
+ } catch (error) {
336
+ expect(error).toBeInstanceOf(ZodError)
337
+ }
338
+ })
339
+
340
+ it('should correctly handle mixed allowed and forbidden paths', () => {
341
+ const inputData = {
342
+ transactions: [
343
+ {
344
+ amount: 200,
345
+ currency: 'ETH',
346
+ details: {
347
+ fee: 0.02,
348
+ type: 'exchange',
349
+ },
350
+ },
351
+ ],
352
+ wallet: {
353
+ id: 'wallet456',
354
+ owner: {
355
+ name: 'Bob',
356
+ age: 40,
357
+ },
358
+ },
359
+ }
360
+
361
+ try {
362
+ const resultAllow = allow(cryptoSchema, [
363
+ 'wallet.owner.name',
364
+ ]).safeParse(inputData)
365
+
366
+ expect(resultAllow.success).toBe(false)
367
+ } catch (error) {
368
+ expect(error).toBeInstanceOf(ZodError)
369
+ }
370
+
371
+ try {
372
+ const resultForbid = forbid(cryptoSchema, [
373
+ 'wallet.owner.age',
374
+ ]).safeParse(inputData)
375
+
376
+ expect(resultForbid.success).toBe(false)
377
+ } catch (error) {
378
+ expect(error).toBeInstanceOf(ZodError)
379
+ }
380
+ })
381
+
382
+ it('should handle empty input objects gracefully', () => {
383
+ const optionalCryptoSchema = z.object({
384
+ transactions: z
385
+ .array(
386
+ z.object({
387
+ amount: z.number().optional(),
388
+ currency: z.string().optional(),
389
+ date: z.string().optional(),
390
+ details: z
391
+ .object({
392
+ fee: z.number().optional(),
393
+ type: z.string().optional(),
394
+ })
395
+ .optional(),
396
+ }),
397
+ )
398
+ .optional(),
399
+ wallet: z
400
+ .object({
401
+ id: z.string().optional(),
402
+ owner: z
403
+ .object({
404
+ name: z.string().optional(),
405
+ age: z.number().optional(),
406
+ })
407
+ .optional(),
408
+ })
409
+ .optional(),
410
+ })
411
+
412
+ const inputData = {}
413
+
414
+ try {
415
+ const result = allow(optionalCryptoSchema, allowedFields).safeParse(
416
+ inputData,
417
+ )
418
+ expect(result.success).toBe(true)
419
+ } catch (error) {
420
+ console.error(
421
+ 'Error in empty input objects test:',
422
+ JSON.stringify(error, null, 2),
423
+ )
424
+ throw error
425
+ }
426
+ })
427
+
428
+ it('should handle edge cases correctly', () => {
429
+ const inputData = {
430
+ transactions: [
431
+ {
432
+ amount: null,
433
+ currency: undefined,
434
+ },
435
+ ],
436
+ wallet: {
437
+ id: 'walletEdge',
438
+ owner: {
439
+ name: null,
440
+ age: undefined,
441
+ },
442
+ },
443
+ }
444
+
445
+ try {
446
+ const result = forbid(cryptoSchema, forbiddenFields).safeParse(
447
+ inputData,
448
+ )
449
+ expect(result.success).toBe(false)
450
+ } catch (error) {
451
+ console.error(
452
+ 'Error in edge cases test:',
453
+ JSON.stringify(error, null, 2),
454
+ )
455
+ expect(error).toBeInstanceOf(ZodError)
456
+ }
457
+ })
458
+ })
459
+
460
+ it('should skip keys with undefined values in optional fields', () => {
461
+ const inputData = {
462
+ wallet: {
463
+ owner: {
464
+ age: undefined, // Optional key with undefined value
465
+ },
466
+ },
467
+ }
468
+
469
+ const schema = z.object({
470
+ wallet: z.object({
471
+ owner: z.object({
472
+ age: z.number().optional(),
473
+ }),
474
+ }),
475
+ })
476
+
477
+ const result = flattenObject(inputData, '', schema)
478
+ expect(result).toEqual({})
479
+ })
480
+
481
+ it('should handle deeply nested objects correctly', () => {
482
+ const inputData = {
483
+ wallet: {
484
+ owner: {
485
+ details: {
486
+ address: {
487
+ city: 'New York',
488
+ },
489
+ },
490
+ },
491
+ },
492
+ }
493
+
494
+ const schema = z.object({
495
+ wallet: z.object({
496
+ owner: z.object({
497
+ details: z.object({
498
+ address: z.object({
499
+ city: z.string(),
500
+ }),
501
+ }),
502
+ }),
503
+ }),
504
+ })
505
+
506
+ const result = flattenObject(inputData, '', schema)
507
+ expect(result).toEqual({
508
+ 'wallet.owner.details.address.city': 'New York',
509
+ })
510
+ })
511
+
512
+ it('should add key-value pairs to the result object', () => {
513
+ const inputData = {
514
+ wallet: {
515
+ id: 'wallet123',
516
+ },
517
+ }
518
+
519
+ const schema = z.object({
520
+ wallet: z.object({
521
+ id: z.string(),
522
+ }),
523
+ })
524
+
525
+ const result = flattenObject(inputData, '', schema)
526
+ expect(result).toEqual({
527
+ 'wallet.id': 'wallet123',
528
+ })
529
+ })
530
+
531
+ it('should handle ZodUnion with ZodLazy correctly', () => {
532
+ const lazySchema = z.lazy(() => z.string())
533
+ const unionSchema = z.union([lazySchema, z.number()])
534
+
535
+ const inputData = {
536
+ wallet: {
537
+ details: 'lazy value',
538
+ },
539
+ }
540
+
541
+ const schema = z.object({
542
+ wallet: z.object({
543
+ details: unionSchema,
544
+ }),
545
+ })
546
+
547
+ try {
548
+ const result = flattenObject(inputData, '', schema)
549
+ expect(result).toEqual({
550
+ 'wallet.details': 'lazy value',
551
+ })
552
+ } catch (error) {
553
+ expect(error).toBeInstanceOf(ZodError)
554
+ }
555
+ })
556
+ })
@@ -0,0 +1,119 @@
1
+ import { get } from 'lodash'
2
+ import {
3
+ z,
4
+ ZodEffects,
5
+ ZodError,
6
+ ZodIssue,
7
+ ZodIssueCode,
8
+ ZodObject,
9
+ ZodTypeAny,
10
+ } from 'zod'
11
+
12
+ export function allow<T extends z.ZodTypeAny>(
13
+ schema: T,
14
+ allowedPaths: string[],
15
+ ): ZodEffects<T, any, any> {
16
+ const rootSchema = schema instanceof z.ZodObject ? schema : undefined
17
+
18
+ return schema.transform((data) => {
19
+ const flatData = flattenObject(data, '', rootSchema)
20
+ const disallowedPaths: string[] = []
21
+
22
+ for (const key of Object.keys(flatData)) {
23
+ if (
24
+ allowedPaths.every(
25
+ (path) => !(key.startsWith(path) || path.startsWith(key)),
26
+ )
27
+ ) {
28
+ disallowedPaths.push(key)
29
+ }
30
+ }
31
+
32
+ if (disallowedPaths.length > 0) {
33
+ const errors: ZodIssue[] = []
34
+ for (const path of disallowedPaths) {
35
+ errors.push({
36
+ code: ZodIssueCode.custom,
37
+ message: `Field '${path}' is not allowed.`,
38
+ path: path.split('.'),
39
+ })
40
+ }
41
+ throw new ZodError(errors)
42
+ }
43
+ return data
44
+ }) as ZodEffects<T, any, any>
45
+ }
46
+
47
+ export function forbid<T extends z.ZodTypeAny>(
48
+ schema: T,
49
+ forbiddenPaths: string[],
50
+ ): ZodEffects<T, any, any> {
51
+ return schema.transform((data) => {
52
+ const forbiddenMatches: string[] = []
53
+
54
+ for (const forbiddenPath of forbiddenPaths) {
55
+ const value = get(data, forbiddenPath)
56
+
57
+ if (value !== undefined) {
58
+ forbiddenMatches.push(forbiddenPath)
59
+ }
60
+ }
61
+
62
+ if (forbiddenMatches.length > 0) {
63
+ const errors: ZodIssue[] = []
64
+ for (const path of forbiddenMatches) {
65
+ errors.push({
66
+ code: ZodIssueCode.custom,
67
+ message: `Field '${path}' is forbidden.`,
68
+ path: [path],
69
+ })
70
+ }
71
+ throw new ZodError(errors)
72
+ }
73
+ return data
74
+ }) as ZodEffects<T, any, any>
75
+ }
76
+
77
+ function isJsonLikeUnion(schemaPart: ZodTypeAny): boolean {
78
+ if (schemaPart instanceof z.ZodOptional) {
79
+ schemaPart = schemaPart.unwrap()
80
+ }
81
+ return (
82
+ schemaPart instanceof z.ZodUnion &&
83
+ schemaPart.options.some((option: ZodTypeAny) => option instanceof z.ZodLazy)
84
+ )
85
+ }
86
+
87
+ export function flattenObject(
88
+ obj: Record<string, any>,
89
+ prefix = '',
90
+ schema?: ZodObject<any>,
91
+ ): Record<string, any> {
92
+ const result: Record<string, any> = {}
93
+
94
+ for (const key of Object.keys(obj)) {
95
+ const pre = prefix.length ? `${prefix}.` : ''
96
+ const currentSchema = schema?.shape[key]
97
+
98
+ if (currentSchema instanceof z.ZodOptional && obj[key] === undefined) {
99
+ continue
100
+ }
101
+
102
+ if (currentSchema && isJsonLikeUnion(currentSchema)) {
103
+ result[`${pre}${key}`] = obj[key]
104
+ } else if (
105
+ typeof obj[key] === 'object' &&
106
+ obj[key] !== null &&
107
+ currentSchema instanceof ZodObject
108
+ ) {
109
+ Object.assign(
110
+ result,
111
+ flattenObject(obj[key], `${pre}${key}`, currentSchema),
112
+ )
113
+ } else {
114
+ result[`${pre}${key}`] = obj[key]
115
+ }
116
+ }
117
+
118
+ return result
119
+ }
package/src/generator.ts CHANGED
@@ -17,8 +17,8 @@ import { generateDeleteManyFunction } from './helpers/generateDeleteMany'
17
17
  import { generateAggregateFunction } from './helpers/generateAggregate'
18
18
  import { generateCountFunction } from './helpers/generateCount'
19
19
  import { generateGroupByFunction } from './helpers/generateGroupBy'
20
- import { generateRouteConfigType } from './helpers/generateRouteConfigType'
21
- import { generateParseQueryParams } from './helpers/generateQsParser'
20
+
21
+ import { copyFiles } from './utils/copyFiles'
22
22
 
23
23
  const { version } = require('../package.json')
24
24
 
@@ -173,16 +173,6 @@ generatorHandler({
173
173
  })
174
174
  }
175
175
 
176
- await writeFileSafely({
177
- content: generateRouteConfigType(),
178
- options,
179
- operation: 'RouteConfig',
180
- })
181
-
182
- await writeFileSafely({
183
- content: generateParseQueryParams(),
184
- options,
185
- operation: 'ParseQueryParams',
186
- })
176
+ await copyFiles(options)
187
177
  },
188
178
  })