prisma-generator-express 1.62.3 → 1.63.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,109 @@
1
+ export type GuardVariantResolutionInput =
2
+ | { kind: 'single' }
3
+ | {
4
+ kind: 'named'
5
+ keys: readonly string[]
6
+ caller?: string
7
+ reservedKeys: ReadonlySet<string>
8
+ }
9
+
10
+ export type GuardVariantResolution =
11
+ | { ok: true; key: string }
12
+ | {
13
+ ok: false
14
+ code:
15
+ | 'reserved-key'
16
+ | 'missing-caller'
17
+ | 'ambiguous-caller'
18
+ | 'unknown-caller'
19
+ caller?: string
20
+ key?: string
21
+ keys: readonly string[]
22
+ matches?: readonly string[]
23
+ }
24
+
25
+ function matchVariantPatterns(
26
+ keys: readonly string[],
27
+ caller: string,
28
+ ): string[] {
29
+ const callerParts = caller.split('/')
30
+ const matches: string[] = []
31
+
32
+ for (const pattern of keys) {
33
+ if (!pattern.includes(':')) continue
34
+
35
+ const patternParts = pattern.split('/')
36
+ if (patternParts.length !== callerParts.length) continue
37
+
38
+ let matchesCaller = true
39
+
40
+ for (let index = 0; index < patternParts.length; index++) {
41
+ if (patternParts[index].startsWith(':')) continue
42
+ if (patternParts[index] !== callerParts[index]) {
43
+ matchesCaller = false
44
+ break
45
+ }
46
+ }
47
+
48
+ if (matchesCaller) matches.push(pattern)
49
+ }
50
+
51
+ return matches
52
+ }
53
+
54
+ export function resolveGuardVariantKey(
55
+ input: GuardVariantResolutionInput,
56
+ ): GuardVariantResolution {
57
+ if (input.kind === 'single') {
58
+ return { ok: true, key: '_default' }
59
+ }
60
+
61
+ const { keys, caller, reservedKeys } = input
62
+
63
+ for (const key of keys) {
64
+ if (reservedKeys.has(key)) {
65
+ return {
66
+ ok: false,
67
+ code: 'reserved-key',
68
+ key,
69
+ keys,
70
+ }
71
+ }
72
+ }
73
+
74
+ const hasDefault = keys.includes('default')
75
+
76
+ if (typeof caller !== 'string') {
77
+ if (hasDefault) return { ok: true, key: 'default' }
78
+ return { ok: false, code: 'missing-caller', keys }
79
+ }
80
+
81
+ if (caller.trim().length === 0) {
82
+ if (hasDefault) return { ok: true, key: 'default' }
83
+ return { ok: false, code: 'unknown-caller', caller, keys }
84
+ }
85
+
86
+ if (keys.includes(caller)) {
87
+ return { ok: true, key: caller }
88
+ }
89
+
90
+ const matches = matchVariantPatterns(keys, caller)
91
+
92
+ if (matches.length === 1) {
93
+ return { ok: true, key: matches[0] }
94
+ }
95
+
96
+ if (matches.length > 1) {
97
+ return {
98
+ ok: false,
99
+ code: 'ambiguous-caller',
100
+ caller,
101
+ keys,
102
+ matches,
103
+ }
104
+ }
105
+
106
+ if (hasDefault) return { ok: true, key: 'default' }
107
+
108
+ return { ok: false, code: 'unknown-caller', caller, keys }
109
+ }
@@ -88,7 +88,7 @@ async function callShapeFn(
88
88
 
89
89
  async function resolveShape(
90
90
  input: unknown,
91
- caller: string | undefined,
91
+ resolvedKey: string | undefined,
92
92
  resolveContext: ContextResolver | undefined,
93
93
  ): Promise<Record<string, unknown> | null> {
94
94
  if (!input) return null
@@ -100,9 +100,8 @@ async function resolveShape(
100
100
  if (!isPlainObject(input)) return null
101
101
  if (isDirectGuardShape(input)) return input
102
102
 
103
- let entry: unknown = undefined
104
- if (caller !== undefined && caller in input) entry = input[caller]
105
- if (entry === undefined && 'default' in input) entry = input['default']
103
+ if (resolvedKey === undefined) return null
104
+ const entry = input[resolvedKey]
106
105
  if (entry === undefined) return null
107
106
 
108
107
  if (typeof entry === 'function') {
@@ -376,7 +375,7 @@ function applyForcedWhere(opts: WhereMergeOptions): void {
376
375
 
377
376
  export async function applyDroppedGuard(
378
377
  shape: unknown,
379
- caller: string | undefined,
378
+ resolvedKey: string | undefined,
380
379
  resolveContext: ContextResolver | undefined,
381
380
  opKind: OpKind,
382
381
  targets: {
@@ -386,7 +385,7 @@ export async function applyDroppedGuard(
386
385
  ensureReadTarget: () => Record<string, unknown>,
387
386
  ensureWriteTarget: () => Record<string, unknown>,
388
387
  ): Promise<void> {
389
- const resolved = await resolveShape(shape, caller, resolveContext)
388
+ const resolved = await resolveShape(shape, resolvedKey, resolveContext)
390
389
  if (!resolved) return
391
390
 
392
391
  const projection = buildDefaultProjectionBody(resolved)
@@ -1,4 +1,5 @@
1
1
  import type { Context } from 'hono'
2
+ import type { GuardVariantResolution } from './guardVariantRouting'
2
3
  import type {
3
4
  BaseOperationConfig,
4
5
  BaseRouteConfig,
@@ -35,6 +36,8 @@ export type HonoInternalVariables = {
35
36
  routeConfig?: { pagination?: PaginationConfig }
36
37
  guardShape?: Record<string, unknown>
37
38
  guardCaller?: string
39
+ guardVariantKey?: string
40
+ guardVariantFailure?: Extract<GuardVariantResolution, { ok: false }>
38
41
  resultData?: unknown
39
42
  resultStatus?: number
40
43
  }
@@ -58,10 +61,11 @@ export type HonoHookHandler<TEnv extends HonoEnvBase = HonoEnvBase> = HonoBefore
58
61
  export type OperationConfig<
59
62
  TShape = Record<string, unknown>,
60
63
  TEnv extends HonoEnvBase = HonoEnvBase,
61
- > = Omit<BaseOperationConfig<HonoBeforeHook<TEnv>, TShape>, 'before' | 'after'> & {
62
- before?: HonoBeforeHook<TEnv>[]
63
- after?: HonoAfterHook<TEnv>[]
64
- }
64
+ > = BaseOperationConfig<
65
+ HonoBeforeHook<TEnv>,
66
+ TShape,
67
+ HonoAfterHook<TEnv>
68
+ >
65
69
 
66
70
  export type UpdateEachConfig<TEnv extends HonoEnvBase = HonoEnvBase> = {
67
71
  before?: HonoBeforeHook<TEnv>[]
@@ -94,7 +98,13 @@ export type RouteConfig<
94
98
  TCtx = unknown,
95
99
  TEnv extends HonoEnvBase = HonoEnvBase,
96
100
  > = Omit<
97
- BaseRouteConfig<HonoBeforeHook<TEnv>, Context<GeneratedHonoEnv<TEnv>>, TShape, TCtx>,
101
+ BaseRouteConfig<
102
+ HonoBeforeHook<TEnv>,
103
+ Context<GeneratedHonoEnv<TEnv>>,
104
+ TShape,
105
+ TCtx,
106
+ HonoAfterHook<TEnv>
107
+ >,
98
108
  HonoOpKeys
99
109
  > & {
100
110
  findUnique?: OperationConfig<TShape, TEnv> | false
@@ -1,3 +1,10 @@
1
+ import { GUARD_SHAPE_CONFIG_KEYS } from './guardHelpers'
2
+ import { isPlainObject } from './misc'
3
+ import {
4
+ resolveGuardVariantKey,
5
+ type GuardVariantResolution,
6
+ } from './guardVariantRouting'
7
+
1
8
  export interface QueryBuilderConfig {
2
9
  enabled?: boolean
3
10
  port?: number
@@ -87,23 +94,43 @@ export type ProgressiveVariantConfig =
87
94
  | ManualProgressiveVariantConfig
88
95
  | AutoIncludeProgressiveVariantConfig
89
96
 
90
- export interface BaseOperationConfig<HookHandler, TShape = Record<string, unknown>> {
91
- before?: HookHandler[]
92
- after?: HookHandler[]
93
- shape?: TShape
97
+ export type VariantEntry<TShape, TBefore, TAfter> = {
98
+ shape: TShape
99
+ before?: TBefore[]
100
+ after?: TAfter[]
101
+ }
102
+
103
+ type OperationShapeConfig<TShape, TBefore, TAfter> =
104
+ | {
105
+ shape?: TShape
106
+ variants?: never
107
+ }
108
+ | {
109
+ shape?: never
110
+ variants: Record<string, VariantEntry<TShape, TBefore, TAfter>>
111
+ }
112
+
113
+ export type BaseOperationConfig<
114
+ TBefore,
115
+ TShape = Record<string, unknown>,
116
+ TAfter = TBefore,
117
+ > = OperationShapeConfig<TShape, TBefore, TAfter> & {
118
+ before?: TBefore[]
119
+ after?: TAfter[]
94
120
  pagination?: Partial<PaginationConfig>
95
121
  }
96
122
 
97
- export interface BaseUpdateEachConfig<HookHandler> {
98
- before?: HookHandler[]
99
- after?: HookHandler[]
123
+ export interface BaseUpdateEachConfig<TBefore, TAfter = TBefore> {
124
+ before?: TBefore[]
125
+ after?: TAfter[]
100
126
  }
101
127
 
102
128
  export interface BaseRouteConfig<
103
- HookHandler,
129
+ TBefore,
104
130
  RequestType,
105
131
  TShape = Record<string, unknown>,
106
132
  TCtx = unknown,
133
+ TAfter = TBefore,
107
134
  > {
108
135
  enableAll?: boolean
109
136
  addModelPrefix?: boolean
@@ -125,30 +152,196 @@ export interface BaseRouteConfig<
125
152
  resolveContext?: (request: RequestType) => TCtx | Promise<TCtx>
126
153
  queryBuilder?: QueryBuilderConfig | false
127
154
  pagination?: PaginationConfig
128
- findUnique?: BaseOperationConfig<HookHandler, TShape> | false
129
- findUniqueOrThrow?: BaseOperationConfig<HookHandler, TShape> | false
130
- findFirst?: BaseOperationConfig<HookHandler, TShape> | false
131
- findFirstOrThrow?: BaseOperationConfig<HookHandler, TShape> | false
132
- findMany?: BaseOperationConfig<HookHandler, TShape> | false
133
- findManyPaginated?: BaseOperationConfig<HookHandler, TShape> | false
134
- create?: BaseOperationConfig<HookHandler, TShape> | false
135
- createMany?: BaseOperationConfig<HookHandler, TShape> | false
136
- createManyAndReturn?: BaseOperationConfig<HookHandler, TShape> | false
137
- update?: BaseOperationConfig<HookHandler, TShape> | false
138
- updateMany?: BaseOperationConfig<HookHandler, TShape> | false
139
- updateManyAndReturn?: BaseOperationConfig<HookHandler, TShape> | false
140
- upsert?: BaseOperationConfig<HookHandler, TShape> | false
141
- delete?: BaseOperationConfig<HookHandler, TShape> | false
142
- deleteMany?: BaseOperationConfig<HookHandler, TShape> | false
143
- updateEach?: BaseUpdateEachConfig<HookHandler>
144
- aggregate?: BaseOperationConfig<HookHandler, TShape> | false
145
- count?: BaseOperationConfig<HookHandler, TShape> | false
146
- groupBy?: BaseOperationConfig<HookHandler, TShape> | false
155
+ findUnique?: BaseOperationConfig<TBefore, TShape, TAfter> | false
156
+ findUniqueOrThrow?: BaseOperationConfig<TBefore, TShape, TAfter> | false
157
+ findFirst?: BaseOperationConfig<TBefore, TShape, TAfter> | false
158
+ findFirstOrThrow?: BaseOperationConfig<TBefore, TShape, TAfter> | false
159
+ findMany?: BaseOperationConfig<TBefore, TShape, TAfter> | false
160
+ findManyPaginated?: BaseOperationConfig<TBefore, TShape, TAfter> | false
161
+ create?: BaseOperationConfig<TBefore, TShape, TAfter> | false
162
+ createMany?: BaseOperationConfig<TBefore, TShape, TAfter> | false
163
+ createManyAndReturn?: BaseOperationConfig<TBefore, TShape, TAfter> | false
164
+ update?: BaseOperationConfig<TBefore, TShape, TAfter> | false
165
+ updateMany?: BaseOperationConfig<TBefore, TShape, TAfter> | false
166
+ updateManyAndReturn?: BaseOperationConfig<TBefore, TShape, TAfter> | false
167
+ upsert?: BaseOperationConfig<TBefore, TShape, TAfter> | false
168
+ delete?: BaseOperationConfig<TBefore, TShape, TAfter> | false
169
+ deleteMany?: BaseOperationConfig<TBefore, TShape, TAfter> | false
170
+ updateEach?: BaseUpdateEachConfig<TBefore, TAfter>
171
+ aggregate?: BaseOperationConfig<TBefore, TShape, TAfter> | false
172
+ count?: BaseOperationConfig<TBefore, TShape, TAfter> | false
173
+ groupBy?: BaseOperationConfig<TBefore, TShape, TAfter> | false
147
174
  }
148
175
 
149
176
  export type OperationConfig = BaseOperationConfig<unknown>
150
177
  export type RouteConfig = BaseRouteConfig<unknown, unknown>
151
178
 
179
+ export type NormalizedGuardRouting =
180
+ | { kind: 'none' }
181
+ | { kind: 'single' }
182
+ | { kind: 'named'; keys: readonly string[] }
183
+
184
+ export type NormalizedVariantHooks<TBefore, TAfter> = Readonly<
185
+ Record<
186
+ string,
187
+ {
188
+ before: readonly TBefore[]
189
+ after: readonly TAfter[]
190
+ }
191
+ >
192
+ >
193
+
194
+ export interface NormalizedOperationConfig<TBefore, TAfter> {
195
+ guardShape?: Record<string, unknown>
196
+ guardRouting: NormalizedGuardRouting
197
+ operationBefore: readonly TBefore[]
198
+ operationAfter: readonly TAfter[]
199
+ variantHooks: NormalizedVariantHooks<TBefore, TAfter>
200
+ pagination?: Readonly<Partial<PaginationConfig>>
201
+ }
202
+
203
+ type OperationConfigInput<TBefore, TAfter> = {
204
+ before?: TBefore[]
205
+ after?: TAfter[]
206
+ shape?: unknown
207
+ variants?: Record<
208
+ string,
209
+ {
210
+ shape?: unknown
211
+ before?: TBefore[]
212
+ after?: TAfter[]
213
+ }
214
+ >
215
+ pagination?: Partial<PaginationConfig>
216
+ }
217
+
218
+ function classifyGuardRouting(shape: unknown): NormalizedGuardRouting {
219
+ if (shape === undefined) return { kind: 'none' }
220
+ if (typeof shape === 'function') return { kind: 'single' }
221
+ if (!isPlainObject(shape)) return { kind: 'single' }
222
+
223
+ const keys = Object.keys(shape)
224
+ const isSingle =
225
+ keys.length === 0 ||
226
+ keys.every((key) => GUARD_SHAPE_CONFIG_KEYS.has(key))
227
+
228
+ return isSingle ? { kind: 'single' } : { kind: 'named', keys }
229
+ }
230
+
231
+ export function validateOperationConfig(
232
+ config: { shape?: unknown; variants?: unknown } | undefined,
233
+ location: string,
234
+ ): void {
235
+ if (!config) return
236
+
237
+ const hasShape = config.shape !== undefined
238
+ const hasVariants = config.variants !== undefined
239
+
240
+ if (hasShape && hasVariants) {
241
+ throw new Error(location + ': shape and variants cannot both be defined')
242
+ }
243
+
244
+ if (!hasVariants) return
245
+
246
+ const variants = config.variants
247
+ if (!isPlainObject(variants)) {
248
+ throw new Error(location + ': variants must be a non-array object')
249
+ }
250
+
251
+ const entries = Object.entries(variants)
252
+ if (entries.length === 0) {
253
+ throw new Error(location + ': variants must contain at least one entry')
254
+ }
255
+
256
+ for (const [key, rawEntry] of entries) {
257
+ if (GUARD_SHAPE_CONFIG_KEYS.has(key)) {
258
+ throw new Error(
259
+ location + ': variant name "' + key +
260
+ '" collides with a reserved guard shape key',
261
+ )
262
+ }
263
+
264
+ if (!isPlainObject(rawEntry)) {
265
+ throw new Error(
266
+ location + ': variant "' + key + '" must be an object with a shape',
267
+ )
268
+ }
269
+
270
+ if (rawEntry.shape === undefined) {
271
+ throw new Error(
272
+ location + ': variant "' + key + '" is missing "shape"',
273
+ )
274
+ }
275
+ }
276
+ }
277
+
278
+ export function validateUpdateEachConfig(
279
+ config: { variants?: unknown } | undefined,
280
+ location: string,
281
+ ): void {
282
+ if (config?.variants !== undefined) {
283
+ throw new Error(location + ': updateEach does not support variants')
284
+ }
285
+ }
286
+
287
+ export function normalizeOperation<TBefore, TAfter>(
288
+ config: OperationConfigInput<TBefore, TAfter> | undefined,
289
+ ): NormalizedOperationConfig<TBefore, TAfter> {
290
+ const operationBefore = config?.before ?? []
291
+ const operationAfter = config?.after ?? []
292
+
293
+ if (config?.variants !== undefined) {
294
+ const entries = Object.entries(config.variants)
295
+
296
+ return {
297
+ guardShape: Object.fromEntries(
298
+ entries.map(([key, entry]) => [key, entry.shape]),
299
+ ),
300
+ guardRouting: {
301
+ kind: 'named',
302
+ keys: entries.map(([key]) => key),
303
+ },
304
+ operationBefore,
305
+ operationAfter,
306
+ variantHooks: Object.fromEntries(
307
+ entries.map(([key, entry]) => [
308
+ key,
309
+ {
310
+ before: entry.before ?? [],
311
+ after: entry.after ?? [],
312
+ },
313
+ ]),
314
+ ),
315
+ pagination: config.pagination,
316
+ }
317
+ }
318
+
319
+ return {
320
+ guardShape: config?.shape as Record<string, unknown> | undefined,
321
+ guardRouting: classifyGuardRouting(config?.shape),
322
+ operationBefore,
323
+ operationAfter,
324
+ variantHooks: {},
325
+ pagination: config?.pagination,
326
+ }
327
+ }
328
+
329
+ export function resolveOperationVariantKey(
330
+ routing: NormalizedGuardRouting,
331
+ caller: string | undefined,
332
+ ): GuardVariantResolution {
333
+ if (routing.kind === 'none' || routing.kind === 'single') {
334
+ return resolveGuardVariantKey({ kind: 'single' })
335
+ }
336
+
337
+ return resolveGuardVariantKey({
338
+ kind: 'named',
339
+ keys: routing.keys,
340
+ caller,
341
+ reservedKeys: GUARD_SHAPE_CONFIG_KEYS,
342
+ })
343
+ }
344
+
152
345
  export function validateCountSourceWhere(
153
346
  cs: PaginationCountSource | undefined,
154
347
  location: string,
@@ -74,25 +74,55 @@ export function generateRouteConfigType(
74
74
 
75
75
  const shapeOps = Array.from(new Set(ROUTER_OPERATIONS))
76
76
  const opShapeImports = shapeOps
77
- .map((op) => `${m}${capitalize(op)}ShapeInput`)
77
+ .flatMap((op) => {
78
+ const prefix = `${m}${capitalize(op)}Shape`
79
+ return [prefix, `${prefix}Input`]
80
+ })
78
81
  .join(',\n ')
79
82
 
83
+ const shapeOrFnAliases = shapeOps
84
+ .map((op) => {
85
+ const prefix = `${m}${capitalize(op)}Shape`
86
+ return (
87
+ `type ${prefix}OrFn<TCtx = unknown> =\n` +
88
+ ` | ${prefix}\n` +
89
+ ` | ((ctx: TCtx) => ${prefix})`
90
+ )
91
+ })
92
+ .join('\n\n')
93
+
80
94
  const overrides = ROUTER_OPERATIONS.map((routerOp) => {
81
95
  const c = capitalize(routerOp)
82
96
  const isRead = READ_OPERATION_NAMES.has(routerOp)
83
- const lines = [
97
+ const commonLines = [
84
98
  ` before?: ${beforeRef}[]`,
85
99
  ` after?: ${afterRef}[]`,
86
- ` shape?: ${m}${c}ShapeInput<TCtx>`,
87
100
  ` pagination?: Partial<PaginationConfig>`,
88
101
  ]
102
+
89
103
  if (isRead && supportsProgressive) {
90
- lines.push(` progressive?: Record<string, ProgressiveVariantConfig>`)
91
- lines.push(
104
+ commonLines.push(
105
+ ` progressive?: Record<string, ProgressiveVariantConfig>`,
106
+ )
107
+ commonLines.push(
92
108
  ` progressiveStages?: Record<string, ProgressiveStage<TCtx, TPrisma>>`,
93
109
  )
94
110
  }
95
- return ` ${routerOp}?: {\n${lines.join('\n')}\n } | false`
111
+
112
+ const commonConfig = `{\n${commonLines.join('\n')}\n }`
113
+ const variantsConfig =
114
+ `Record<string, {\n` +
115
+ ` shape: ${m}${c}ShapeOrFn<TCtx>\n` +
116
+ ` before?: ${beforeRef}[]\n` +
117
+ ` after?: ${afterRef}[]\n` +
118
+ ` }>`
119
+
120
+ return (
121
+ ` ${routerOp}?: (${commonConfig} & (\n` +
122
+ ` | { shape?: ${m}${c}ShapeInput<TCtx>; variants?: never }\n` +
123
+ ` | { shape?: never; variants: ${variantsConfig} }\n` +
124
+ ` )) | false`
125
+ )
96
126
  }).join('\n')
97
127
 
98
128
  const omitKeys = ROUTER_OPERATIONS.map((k) => `'${k}'`).join('\n | ')
@@ -100,6 +130,7 @@ export function generateRouteConfigType(
100
130
  return (
101
131
  progressiveTypeImport +
102
132
  `import type {\n ${opShapeImports}\n} from '${guardShapesImport}${ext}'\n\n` +
133
+ `${shapeOrFnAliases}\n\n` +
103
134
  `export type ${m}RouteConfig${generics} = Omit<\n` +
104
135
  ` ${baseConfig},\n` +
105
136
  ` | ${omitKeys}\n` +