prisma-generator-express 1.65.1 → 1.66.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.
package/README.md CHANGED
@@ -1,15 +1,25 @@
1
- # Prisma Generator Express
2
-
3
- [![npm version](https://badge.fury.io/js/prisma-generator-express.svg)](https://badge.fury.io/js/prisma-generator-express)
4
- [![npm](https://img.shields.io/npm/dt/prisma-generator-express.svg)](https://www.npmjs.com/package/prisma-generator-express)
5
- [![Coverage](https://img.shields.io/codecov/c/github/multipliedtwice/prisma-generator-express/main.svg)](https://codecov.io/gh/multipliedtwice/prisma-generator-express)
6
- [![npm](https://img.shields.io/npm/l/prisma-generator-express.svg)](LICENSE)
7
-
8
- Prisma generator that creates Express, Fastify, or Hono CRUD API routes with OpenAPI documentation from your Prisma schema.
9
-
10
- **Guides:** [Prisma Generator Express Series](https://multipliedtwice.github.io/prisma-generator-express/) — verified tutorials, deep dives, and troubleshooting references.
11
-
12
- **Article evidence:** [guard lab](article-labs/guard/) and [HTTP/PostgreSQL lab](article-labs/http/).
1
+ <p align="center">
2
+ <img src="docs/assets/favicon.svg" width="88" height="88" alt="Prisma Generator Express logo">
3
+ </p>
4
+
5
+ <h1 align="center">Prisma Generator Express</h1>
6
+
7
+ <p align="center"><strong>Ship Prisma APIs faster by generating Express, Fastify, or Hono CRUD routes, OpenAPI, pagination, hooks, and guard integration directly from your schema.</strong></p>
8
+
9
+ <p align="center">
10
+ <a href="https://www.npmjs.com/package/prisma-generator-express"><img src="https://badge.fury.io/js/prisma-generator-express.svg" alt="npm version"></a>
11
+ <a href="https://www.npmjs.com/package/prisma-generator-express"><img src="https://img.shields.io/npm/dt/prisma-generator-express.svg" alt="npm downloads"></a>
12
+ <a href="https://codecov.io/gh/multipliedtwice/prisma-generator-express"><img src="https://img.shields.io/codecov/c/github/multipliedtwice/prisma-generator-express/master.svg" alt="Coverage"></a>
13
+ <a href="LICENSE"><img src="https://img.shields.io/npm/l/prisma-generator-express.svg" alt="License"></a>
14
+ </p>
15
+
16
+ <p align="center">
17
+ <a href="https://multipliedtwice.github.io/prisma-generator-express/">Field Guide</a> ·
18
+ <a href="https://www.npmjs.com/package/prisma-generator-express">npm</a> ·
19
+ <a href="article-labs/guard/">Guard Evidence</a> ·
20
+ <a href="article-labs/http/">HTTP/PostgreSQL Evidence</a> ·
21
+ <a href="#documentation-endpoints">API Documentation</a>
22
+ </p>
13
23
 
14
24
  Running `npx prisma generate` produces:
15
25
 
@@ -1,2 +1,2 @@
1
1
  import { GeneratorOptions } from '@prisma/generator-helper';
2
- export declare function generateQueryBuilderHelper(options: Pick<GeneratorOptions, 'schemaPath'>): string;
2
+ export declare function generateQueryBuilderHelper(_options?: Partial<Pick<GeneratorOptions, 'schemaPath'>>): string;
@@ -1,10 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.generateQueryBuilderHelper = generateQueryBuilderHelper;
4
- function generateQueryBuilderHelper(options) {
5
- const schemaPath = options.schemaPath
6
- ? JSON.stringify(options.schemaPath)
7
- : "resolve(process.cwd(), 'prisma/schema.prisma')";
4
+ function generateQueryBuilderHelper(_options) {
5
+ const schemaPath = "resolve(process.cwd(), 'prisma/schema.prisma')";
8
6
  return `import { spawn } from 'child_process'
9
7
  import { resolve, join, dirname } from 'path'
10
8
  import { existsSync, readFileSync } from 'fs'
@@ -1 +1 @@
1
- {"version":3,"file":"generateQueryBuilderHelper.js","sourceRoot":"","sources":["../../src/generators/generateQueryBuilderHelper.ts"],"names":[],"mappings":";;AAEA,gEAmKC;AAnKD,SAAgB,0BAA0B,CACxC,OAA6C;IAE7C,MAAM,UAAU,GAAG,OAAO,CAAC,UAAU;QACnC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,UAAU,CAAC;QACpC,CAAC,CAAC,gDAAgD,CAAA;IAEpD,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;+CA+DsC,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA4FxD,CAAA;AACD,CAAC"}
1
+ {"version":3,"file":"generateQueryBuilderHelper.js","sourceRoot":"","sources":["../../src/generators/generateQueryBuilderHelper.ts"],"names":[],"mappings":";;AAoBA,gEAiKC;AAjKD,SAAgB,0BAA0B,CACxC,QAAwD;IAExD,MAAM,UAAU,GAAG,gDAAgD,CAAA;IAEnE,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;+CA+DsC,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA4FxD,CAAA;AACD,CAAC"}
@@ -131,13 +131,15 @@ const _env = getEnv()
131
131
  // which is upstream behaviour. See generateRouterHono.ts.
132
132
  const DROP_GUARD = ${dropGuard}
133
133
 
134
- type JsonLike =
135
- | string
136
- | number
137
- | boolean
138
- | null
139
- | JsonLike[]
140
- | { [k: string]: JsonLike }
134
+ /**
135
+ * DELIBERATELY NOT RECURSIVE.
136
+ *
137
+ * A self-referential JSON type sent through \`c.json()\` makes Hono's response
138
+ * inference instantiate without a fixed point, and TypeScript answers
139
+ * "type instantiation is excessively deep" — in the emitted file, for every
140
+ * consumer. One level is all this needs: the value is serialised, not walked.
141
+ */
142
+ type JsonLike = string | number | boolean | null | unknown[] | Record<string, unknown>
141
143
 
142
144
  type OperationConfigLike<TEnv extends HonoEnvBase> = {
143
145
  before?: HonoBeforeHook<TEnv>[]
@@ -230,17 +232,30 @@ function makeShapeMiddleware<TCtx, TPrisma, TEnv extends HonoEnvBase>(
230
232
  const dropGuard = DROP_GUARD || (policy.allowE2EGuardBypass && _env.E2E === 'true')
231
233
 
232
234
  return async (c: Context<GeneratedHonoEnv<TEnv>>): Promise<void> => {
235
+ /**
236
+ * INTERNAL REQUEST STATE IS SET THROUGH A CONCRETE CONTEXT.
237
+ *
238
+ * \`GeneratedHonoEnv<TEnv>['Variables']\` is \`HonoInternalVariables & TEnv['Variables']\`,
239
+ * and inside this generic function TypeScript cannot know that the consumer's
240
+ * half does not also declare \`routeConfig\` — so setting that key straight
241
+ * onto \`c\` is unassignable to the intersection and the emitted router does not
242
+ * typecheck under \`strict\` for ANY consumer. These keys belong to this
243
+ * router, so they are written through the internal shape they were declared
244
+ * in.
245
+ */
246
+ const vars = c as unknown as HandlerContext
247
+
233
248
  const merged = mergePaginationConfig(config.pagination, opConfig.pagination)
234
- if (merged) c.set('routeConfig', { pagination: merged })
249
+ if (merged) vars.set('routeConfig', { pagination: merged })
235
250
 
236
251
  const headerName = config.guard?.variantHeader || 'x-api-variant'
237
252
  const headerValue = c.req.header(headerName)
238
253
  const caller = config.guard?.resolveVariant?.(c) ?? headerValue ?? undefined
239
- if (typeof caller === 'string') c.set('guardCaller', caller)
254
+ if (typeof caller === 'string') vars.set('guardCaller', caller)
240
255
 
241
256
  const resolution = resolveOperationVariantKey(opConfig.guardRouting, caller)
242
257
  if (!resolution.ok) {
243
- c.set('guardVariantFailure', resolution)
258
+ vars.set('guardVariantFailure', resolution)
244
259
  return
245
260
  }
246
261
 
@@ -248,11 +263,11 @@ function makeShapeMiddleware<TCtx, TPrisma, TEnv extends HonoEnvBase>(
248
263
  opConfig.guardRouting.kind === 'named'
249
264
  ? resolution.key
250
265
  : undefined
251
- if (resolvedKey !== undefined) c.set('guardVariantKey', resolvedKey)
266
+ if (resolvedKey !== undefined) vars.set('guardVariantKey', resolvedKey)
252
267
 
253
268
  if (opConfig.guardShape) {
254
269
  const resolveCtx = typeof config.resolveContext === 'function'
255
- ? () => (config.resolveContext as (ctx: Context<GeneratedHonoEnv<TEnv>>) => unknown | Promise<unknown>)(c)
270
+ ? () => (config.resolveContext as unknown as (ctx: Context<GeneratedHonoEnv<TEnv>>) => unknown | Promise<unknown>)(c)
256
271
  : undefined
257
272
 
258
273
  /**
@@ -265,18 +280,20 @@ function makeShapeMiddleware<TCtx, TPrisma, TEnv extends HonoEnvBase>(
265
280
  * behaviour: it is resolved at the point of use, and a function returning
266
281
  * something unusable is not this router's business to refuse.
267
282
  */
268
- let effectiveShape: unknown = opConfig.guardShape
283
+ let effectiveShape: Record<string, unknown> | undefined = opConfig.guardShape
269
284
  if (policy.validateResolvedShapes) {
270
285
  const resolution = await resolveGuardShapeOnce(opConfig.guardShape, resolvedKey, resolveCtx)
271
286
  if (!resolution.ok) {
272
- c.set('guardShapeFailure', resolution.problem)
287
+ vars.set('guardShapeFailure', resolution.problem)
273
288
  return
274
289
  }
275
- effectiveShape = resolution.shape
290
+ // \`ok\` means the resolved value is a shape object; the fallback keeps the
291
+ // declared shape rather than storing something the context cannot hold.
292
+ effectiveShape = isPlainObject(resolution.shape) ? resolution.shape : opConfig.guardShape
276
293
  }
277
294
 
278
295
  if (!dropGuard) {
279
- c.set('guardShape', effectiveShape)
296
+ vars.set('guardShape', effectiveShape)
280
297
  } else {
281
298
  await applyDroppedGuard(
282
299
  effectiveShape,
@@ -284,24 +301,24 @@ function makeShapeMiddleware<TCtx, TPrisma, TEnv extends HonoEnvBase>(
284
301
  resolveCtx,
285
302
  opKind,
286
303
  {
287
- readQuery: c.get('parsedQuery'),
288
- writeBody: isPlainObject(c.get('body'))
289
- ? (c.get('body') as Record<string, unknown>)
304
+ readQuery: vars.get('parsedQuery'),
305
+ writeBody: isPlainObject(vars.get('body'))
306
+ ? (vars.get('body') as Record<string, unknown>)
290
307
  : undefined,
291
308
  },
292
309
  () => {
293
- let target = c.get('parsedQuery')
310
+ let target = vars.get('parsedQuery')
294
311
  if (!target) {
295
312
  target = {}
296
- c.set('parsedQuery', target)
313
+ vars.set('parsedQuery', target)
297
314
  }
298
315
  return target
299
316
  },
300
317
  () => {
301
- let target = c.get('body')
318
+ let target = vars.get('body')
302
319
  if (!isPlainObject(target)) {
303
320
  target = {}
304
- c.set('body', target)
321
+ vars.set('body', target)
305
322
  }
306
323
  return target as Record<string, unknown>
307
324
  },
@@ -380,7 +397,7 @@ export function ${routerFunctionName}<TCtx = unknown, TPrisma = any, TEnv extend
380
397
  '${modelName}',
381
398
  MODEL_FIELDS as unknown as Parameters<typeof buildModelOpenApi>[1],
382
399
  MODEL_ENUMS as unknown as Parameters<typeof buildModelOpenApi>[2],
383
- config as RouteConfig,
400
+ config as unknown as Parameters<typeof buildModelOpenApi>[3],
384
401
  { format: 'json', writeStrategy: '${writeStrategy}' },
385
402
  )
386
403
  }
@@ -393,14 +410,14 @@ export function ${routerFunctionName}<TCtx = unknown, TPrisma = any, TEnv extend
393
410
  '${modelName}',
394
411
  MODEL_FIELDS as unknown as Parameters<typeof buildModelOpenApi>[1],
395
412
  MODEL_ENUMS as unknown as Parameters<typeof buildModelOpenApi>[2],
396
- config as RouteConfig,
413
+ config as unknown as Parameters<typeof buildModelOpenApi>[3],
397
414
  { format: 'yaml', writeStrategy: '${writeStrategy}' },
398
415
  ) as string
399
416
  }
400
417
  return _openApiYamlCache
401
418
  }
402
419
 
403
- if (config.queryBuilder && config.queryBuilder !== false && _env.NODE_ENV !== 'production') {
420
+ if (config.queryBuilder && _env.NODE_ENV !== 'production') {
404
421
  console.warn(
405
422
  '[${modelName}Router] queryBuilder config is present but Hono target does not auto-start it. ' +
406
423
  'Run \`npx prisma-query-builder-ui\` in a separate process.',
@@ -408,7 +425,7 @@ export function ${routerFunctionName}<TCtx = unknown, TPrisma = any, TEnv extend
408
425
  }
409
426
 
410
427
  app.onError((err, c) => {
411
- return sendError(c as HandlerContext, err)
428
+ return sendError(c as unknown as HandlerContext, err)
412
429
  })
413
430
 
414
431
  if (!openApiDisabled) {
@@ -438,14 +455,15 @@ export function ${routerFunctionName}<TCtx = unknown, TPrisma = any, TEnv extend
438
455
  * that branch is inert rather than merely unreached when it is off.
439
456
  */
440
457
  const settleGuard = (c: Context<GeneratedHonoEnv<TEnv>>): void => {
441
- const failure = c.get('guardVariantFailure')
458
+ const vars = c as unknown as HandlerContext
459
+ const failure = vars.get('guardVariantFailure')
442
460
  if (failure) {
443
461
  throw new HTTPException(400, {
444
462
  message: formatGuardVariantResolutionError(failure),
445
463
  })
446
464
  }
447
465
 
448
- const shapeFailure = c.get('guardShapeFailure')
466
+ const shapeFailure = vars.get('guardShapeFailure')
449
467
  if (shapeFailure) {
450
468
  throw new HTTPException(500, {
451
469
  message: 'guard shape could not be resolved: ' + shapeFailure,
@@ -472,7 +490,7 @@ export function ${routerFunctionName}<TCtx = unknown, TPrisma = any, TEnv extend
472
490
  // Upstream order: hooks first, guard failure after.
473
491
  if (!SETTLE_BEFORE_HOOKS) settleGuard(c)
474
492
 
475
- const key = c.get('guardVariantKey')
493
+ const key = (c as unknown as HandlerContext).get('guardVariantKey')
476
494
  const variantHooks =
477
495
  key !== undefined ? opConfig.variantHooks[key] : undefined
478
496
 
@@ -507,7 +525,7 @@ export function ${routerFunctionName}<TCtx = unknown, TPrisma = any, TEnv extend
507
525
  // Upstream order: hooks first, guard failure after.
508
526
  if (!SETTLE_BEFORE_HOOKS) settleGuard(c)
509
527
 
510
- const key = c.get('guardVariantKey')
528
+ const key = (c as unknown as HandlerContext).get('guardVariantKey')
511
529
  const variantHooks =
512
530
  key !== undefined ? opConfig.variantHooks[key] : undefined
513
531
 
@@ -1 +1 @@
1
- {"version":3,"file":"generateRouterHono.js","sourceRoot":"","sources":["../../src/generators/generateRouterHono.ts"],"names":[],"mappings":";;AAmIA,gEAkhBC;AAppBD,uEAAmE;AAEnE,kDAA8C;AAE9C,uEAAiE;AAEjE,SAAS,QAAQ,CAAC,MAAc;IAC9B,IAAI,CAAC,MAAM;QAAE,OAAO,iBAAiB,CAAA;IACrC,OAAO,iBAAiB,MAAM,IAAI,CAAA;AACpC,CAAC;AAED,SAAS,SAAS,CAAC,MAAc;IAC/B,QAAQ,MAAM,EAAE,CAAC;QACf,KAAK,YAAY,CAAC;QAClB,KAAK,mBAAmB;YACtB,OAAO,YAAY,CAAA;QACrB,KAAK,UAAU,CAAC;QAChB,KAAK,WAAW,CAAC;QACjB,KAAK,kBAAkB,CAAC;QACxB,KAAK,mBAAmB,CAAC;QACzB,KAAK,OAAO,CAAC;QACb,KAAK,WAAW,CAAC;QACjB,KAAK,SAAS;YACZ,OAAO,MAAM,CAAA;QACf,KAAK,QAAQ;YACX,OAAO,QAAQ,CAAA;QACjB,KAAK,YAAY,CAAC;QAClB,KAAK,qBAAqB;YACxB,OAAO,YAAY,CAAA;QACrB,KAAK,QAAQ;YACX,OAAO,QAAQ,CAAA;QACjB,KAAK,YAAY,CAAC;QAClB,KAAK,qBAAqB;YACxB,OAAO,YAAY,CAAA;QACrB,KAAK,QAAQ;YACX,OAAO,QAAQ,CAAA;QACjB,KAAK,QAAQ;YACX,OAAO,QAAQ,CAAA;QACjB,KAAK,YAAY;YACf,OAAO,YAAY,CAAA;QACrB;YACE,OAAO,MAAM,CAAA;IACjB,CAAC;AACH,CAAC;AAED,SAAS,UAAU,CAAC,IAAyC,EAAE,SAAiB;IAC9E,MAAM,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,GAAG,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAA;IAChE,MAAM,WAAW,GAAG,GAAG,SAAS,GAAG,CAAC,EAAE,CAAA;IACtC,MAAM,SAAS,GAAG,QAAQ,CAAC,IAAI,CAAC,UAAU,CAAC,CAAA;IAC3C,MAAM,MAAM,GAAG,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IAEnC,MAAM,YAAY,GAAG,IAAI,CAAC,gBAAgB;QACxC,CAAC,CAAC,IAAI,CAAC,IAAI,KAAK,UAAU;YACxB,CAAC,CAAC;;gDAEwC,WAAW,kCAAkC,MAAM;MAC7F;YACA,CAAC,CAAC,iEAAiE,WAAW,kCAAkC,MAAM,KAAK;QAC7H,CAAC,CAAC,EAAE,CAAA;IAEN,OAAO,0BAA0B,IAAI,CAAC,SAAS;8BACnB,IAAI,CAAC,SAAS;mBACzB,SAAS;yCACa,WAAW,4BAA4B,MAAM;EACpF,YAAY;IACV,CAAA;AACJ,CAAC;AAED,SAAS,WAAW,CAAC,IAAyC,EAAE,SAAiB;IAC/E,MAAM,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,GAAG,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAA;IAChE,MAAM,WAAW,GAAG,GAAG,SAAS,GAAG,CAAC,EAAE,CAAA;IACtC,MAAM,SAAS,GAAG,QAAQ,CAAC,IAAI,CAAC,UAAU,CAAC,CAAA;IAC3C,MAAM,MAAM,GAAG,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IAEnC,OAAO,0BAA0B,IAAI,CAAC,SAAS;8BACnB,IAAI,CAAC,SAAS;mBACzB,SAAS;UAClB,IAAI,CAAC,MAAM,gCAAgC,WAAW,MAAM,MAAM;IACxE,CAAA;AACJ,CAAC;AAmDD,SAAgB,0BAA0B,CAAC,EACzC,KAAK,EACL,KAAK,EACL,iBAAiB,EACjB,WAAW,EACX,aAAa,EACb,SAAS,GAQV;IACC,MAAM,GAAG,GAAG,IAAA,qBAAS,EAAC,WAAW,CAAC,CAAA;IAClC,MAAM,SAAS,GAAG,KAAK,CAAC,IAAI,CAAA;IAC5B,MAAM,cAAc,GAAG,SAAS,CAAC,WAAW,EAAE,CAAA;IAC9C,MAAM,kBAAkB,GAAG,GAAG,SAAS,QAAQ,CAAA;IAE/C,MAAM,cAAc,GAAG,yCAAkB;SACtC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,SAAS,GAAG,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,GAAG,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC;SAChF,IAAI,CAAC,IAAI,CAAC,CAAA;IAEb,MAAM,OAAO,GAAG,yCAAkB,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,MAAM,CAAC,CAAA;IACnE,MAAM,QAAQ,GAAG,yCAAkB,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,OAAO,IAAI,CAAC,CAAC,IAAI,KAAK,OAAO,CAAC;SACxF,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,YAAY,CAAC,CAAA;IAEzC,MAAM,YAAY,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;IAC9E,MAAM,aAAa,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,WAAW,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;IAEjF,OAAO;;;;;EAKP,cAAc;YACJ,SAAS,WAAW,GAAG;;;;;;;;;+BASJ,GAAG;uDACqB,GAAG;+EACqB,GAAG;yDACzB,GAAG;;;;;;;;;wBASpC,GAAG;gEACqC,GAAG;sDACb,GAAG;0CACf,GAAG;yEAC4B,GAAG;sDACtB,GAAG;0DACC,GAAG;oDACT,GAAG;+CACR,SAAS,WAAW,GAAG;;EAEpE,IAAA,iDAAuB,EAAC,SAAS,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,WAAW,EAAE,MAAM,CAAC;;;;;;qBAMzE,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;YAqFlB,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBAwIH,kBAAkB,mFAAmF,SAAS;8DAClE,SAAS;;;OAGhE,SAAS;;;;;;;;4DAQ4C,cAAc;;;;;;;;;;;;;;;WAe/D,SAAS;;;;4CAIwB,aAAa;;;;;;;;;WAS9C,SAAS;;;;4CAIwB,aAAa;;;;;;;;UAQ/C,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;mCA0HgB,SAAS;;;;oCAIR,SAAS;;;;EAI3C,YAAY;;EAEZ,aAAa;;;;;;;;;;;;;;WAcJ,SAAS;;;;;;;;;+CAS2B,SAAS;;;;YAI5C,SAAS;;;;;;;;;;;gBAWL,SAAS;;;;;;;;;;;;CAYxB,CAAA;AACD,CAAC"}
1
+ {"version":3,"file":"generateRouterHono.js","sourceRoot":"","sources":["../../src/generators/generateRouterHono.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,9 +1,20 @@
1
1
  {
2
2
  "name": "prisma-generator-express",
3
3
  "description": "Prisma generator for Express, Fastify, and Hono CRUD APIs with OpenAPI documentation",
4
- "version": "1.65.1",
4
+ "version": "1.66.0",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
7
+ "exports": {
8
+ ".": {
9
+ "types": "./dist/index.d.ts",
10
+ "default": "./dist/index.js"
11
+ },
12
+ "./guard-options": {
13
+ "types": "./dist/guardOptions.d.ts",
14
+ "default": "./dist/guardOptions.js"
15
+ },
16
+ "./package.json": "./package.json"
17
+ },
7
18
  "license": "MIT",
8
19
  "bin": "dist/bin.js",
9
20
  "engines": {
@@ -38,6 +38,13 @@ export type HonoInternalVariables = {
38
38
  guardCaller?: string
39
39
  guardVariantKey?: string
40
40
  guardVariantFailure?: Extract<GuardVariantResolution, { ok: false }>
41
+ /**
42
+ * Set when `validateResolvedShapes` refuses what a shape function returned.
43
+ *
44
+ * It was written by the router and never declared here, so the emitted router
45
+ * failed to typecheck at exactly the line that implements the control.
46
+ */
47
+ guardShapeFailure?: string
41
48
  resultData?: unknown
42
49
  resultStatus?: number
43
50
  }
@@ -1,11 +1,27 @@
1
1
  import { GeneratorOptions } from '@prisma/generator-helper'
2
2
 
3
+ /**
4
+ * THE EMITTED DEFAULT IS THE CONSUMER'S OWN SCHEMA, NEVER THE GENERATOR'S.
5
+ *
6
+ * This baked `options.schemaPath` — an ABSOLUTE path on the machine that ran
7
+ * `prisma generate` — into the generated helper as its runtime default. For a
8
+ * consumer that generates and runs in the same checkout that is invisible; for
9
+ * anyone else it is a path that does not exist, and for a generated artifact
10
+ * built in a staging directory it is worse than that: the directory is renamed
11
+ * away at adoption, so the shipped fallback can never resolve on ANY machine,
12
+ * including the one that produced it.
13
+ *
14
+ * `resolve(process.cwd(), 'prisma/schema.prisma')` — Prisma's own convention,
15
+ * relative to wherever the consumer's process runs — was already written here as
16
+ * the no-schemaPath branch. It is now the only default.
17
+ *
18
+ * THE EXPLICIT OVERRIDE IS UNTOUCHED: `startQueryBuilder({ schemaPath })` still
19
+ * wins, which is how a consumer with a different layout says so.
20
+ */
3
21
  export function generateQueryBuilderHelper(
4
- options: Pick<GeneratorOptions, 'schemaPath'>,
22
+ _options?: Partial<Pick<GeneratorOptions, 'schemaPath'>>,
5
23
  ): string {
6
- const schemaPath = options.schemaPath
7
- ? JSON.stringify(options.schemaPath)
8
- : "resolve(process.cwd(), 'prisma/schema.prisma')"
24
+ const schemaPath = "resolve(process.cwd(), 'prisma/schema.prisma')"
9
25
 
10
26
  return `import { spawn } from 'child_process'
11
27
  import { resolve, join, dirname } from 'path'
@@ -205,13 +205,15 @@ const _env = getEnv()
205
205
  // which is upstream behaviour. See generateRouterHono.ts.
206
206
  const DROP_GUARD = ${dropGuard}
207
207
 
208
- type JsonLike =
209
- | string
210
- | number
211
- | boolean
212
- | null
213
- | JsonLike[]
214
- | { [k: string]: JsonLike }
208
+ /**
209
+ * DELIBERATELY NOT RECURSIVE.
210
+ *
211
+ * A self-referential JSON type sent through \`c.json()\` makes Hono's response
212
+ * inference instantiate without a fixed point, and TypeScript answers
213
+ * "type instantiation is excessively deep" — in the emitted file, for every
214
+ * consumer. One level is all this needs: the value is serialised, not walked.
215
+ */
216
+ type JsonLike = string | number | boolean | null | unknown[] | Record<string, unknown>
215
217
 
216
218
  type OperationConfigLike<TEnv extends HonoEnvBase> = {
217
219
  before?: HonoBeforeHook<TEnv>[]
@@ -304,17 +306,30 @@ function makeShapeMiddleware<TCtx, TPrisma, TEnv extends HonoEnvBase>(
304
306
  const dropGuard = DROP_GUARD || (policy.allowE2EGuardBypass && _env.E2E === 'true')
305
307
 
306
308
  return async (c: Context<GeneratedHonoEnv<TEnv>>): Promise<void> => {
309
+ /**
310
+ * INTERNAL REQUEST STATE IS SET THROUGH A CONCRETE CONTEXT.
311
+ *
312
+ * \`GeneratedHonoEnv<TEnv>['Variables']\` is \`HonoInternalVariables & TEnv['Variables']\`,
313
+ * and inside this generic function TypeScript cannot know that the consumer's
314
+ * half does not also declare \`routeConfig\` — so setting that key straight
315
+ * onto \`c\` is unassignable to the intersection and the emitted router does not
316
+ * typecheck under \`strict\` for ANY consumer. These keys belong to this
317
+ * router, so they are written through the internal shape they were declared
318
+ * in.
319
+ */
320
+ const vars = c as unknown as HandlerContext
321
+
307
322
  const merged = mergePaginationConfig(config.pagination, opConfig.pagination)
308
- if (merged) c.set('routeConfig', { pagination: merged })
323
+ if (merged) vars.set('routeConfig', { pagination: merged })
309
324
 
310
325
  const headerName = config.guard?.variantHeader || 'x-api-variant'
311
326
  const headerValue = c.req.header(headerName)
312
327
  const caller = config.guard?.resolveVariant?.(c) ?? headerValue ?? undefined
313
- if (typeof caller === 'string') c.set('guardCaller', caller)
328
+ if (typeof caller === 'string') vars.set('guardCaller', caller)
314
329
 
315
330
  const resolution = resolveOperationVariantKey(opConfig.guardRouting, caller)
316
331
  if (!resolution.ok) {
317
- c.set('guardVariantFailure', resolution)
332
+ vars.set('guardVariantFailure', resolution)
318
333
  return
319
334
  }
320
335
 
@@ -322,11 +337,11 @@ function makeShapeMiddleware<TCtx, TPrisma, TEnv extends HonoEnvBase>(
322
337
  opConfig.guardRouting.kind === 'named'
323
338
  ? resolution.key
324
339
  : undefined
325
- if (resolvedKey !== undefined) c.set('guardVariantKey', resolvedKey)
340
+ if (resolvedKey !== undefined) vars.set('guardVariantKey', resolvedKey)
326
341
 
327
342
  if (opConfig.guardShape) {
328
343
  const resolveCtx = typeof config.resolveContext === 'function'
329
- ? () => (config.resolveContext as (ctx: Context<GeneratedHonoEnv<TEnv>>) => unknown | Promise<unknown>)(c)
344
+ ? () => (config.resolveContext as unknown as (ctx: Context<GeneratedHonoEnv<TEnv>>) => unknown | Promise<unknown>)(c)
330
345
  : undefined
331
346
 
332
347
  /**
@@ -339,18 +354,20 @@ function makeShapeMiddleware<TCtx, TPrisma, TEnv extends HonoEnvBase>(
339
354
  * behaviour: it is resolved at the point of use, and a function returning
340
355
  * something unusable is not this router's business to refuse.
341
356
  */
342
- let effectiveShape: unknown = opConfig.guardShape
357
+ let effectiveShape: Record<string, unknown> | undefined = opConfig.guardShape
343
358
  if (policy.validateResolvedShapes) {
344
359
  const resolution = await resolveGuardShapeOnce(opConfig.guardShape, resolvedKey, resolveCtx)
345
360
  if (!resolution.ok) {
346
- c.set('guardShapeFailure', resolution.problem)
361
+ vars.set('guardShapeFailure', resolution.problem)
347
362
  return
348
363
  }
349
- effectiveShape = resolution.shape
364
+ // \`ok\` means the resolved value is a shape object; the fallback keeps the
365
+ // declared shape rather than storing something the context cannot hold.
366
+ effectiveShape = isPlainObject(resolution.shape) ? resolution.shape : opConfig.guardShape
350
367
  }
351
368
 
352
369
  if (!dropGuard) {
353
- c.set('guardShape', effectiveShape)
370
+ vars.set('guardShape', effectiveShape)
354
371
  } else {
355
372
  await applyDroppedGuard(
356
373
  effectiveShape,
@@ -358,24 +375,24 @@ function makeShapeMiddleware<TCtx, TPrisma, TEnv extends HonoEnvBase>(
358
375
  resolveCtx,
359
376
  opKind,
360
377
  {
361
- readQuery: c.get('parsedQuery'),
362
- writeBody: isPlainObject(c.get('body'))
363
- ? (c.get('body') as Record<string, unknown>)
378
+ readQuery: vars.get('parsedQuery'),
379
+ writeBody: isPlainObject(vars.get('body'))
380
+ ? (vars.get('body') as Record<string, unknown>)
364
381
  : undefined,
365
382
  },
366
383
  () => {
367
- let target = c.get('parsedQuery')
384
+ let target = vars.get('parsedQuery')
368
385
  if (!target) {
369
386
  target = {}
370
- c.set('parsedQuery', target)
387
+ vars.set('parsedQuery', target)
371
388
  }
372
389
  return target
373
390
  },
374
391
  () => {
375
- let target = c.get('body')
392
+ let target = vars.get('body')
376
393
  if (!isPlainObject(target)) {
377
394
  target = {}
378
- c.set('body', target)
395
+ vars.set('body', target)
379
396
  }
380
397
  return target as Record<string, unknown>
381
398
  },
@@ -454,7 +471,7 @@ export function ${routerFunctionName}<TCtx = unknown, TPrisma = any, TEnv extend
454
471
  '${modelName}',
455
472
  MODEL_FIELDS as unknown as Parameters<typeof buildModelOpenApi>[1],
456
473
  MODEL_ENUMS as unknown as Parameters<typeof buildModelOpenApi>[2],
457
- config as RouteConfig,
474
+ config as unknown as Parameters<typeof buildModelOpenApi>[3],
458
475
  { format: 'json', writeStrategy: '${writeStrategy}' },
459
476
  )
460
477
  }
@@ -467,14 +484,14 @@ export function ${routerFunctionName}<TCtx = unknown, TPrisma = any, TEnv extend
467
484
  '${modelName}',
468
485
  MODEL_FIELDS as unknown as Parameters<typeof buildModelOpenApi>[1],
469
486
  MODEL_ENUMS as unknown as Parameters<typeof buildModelOpenApi>[2],
470
- config as RouteConfig,
487
+ config as unknown as Parameters<typeof buildModelOpenApi>[3],
471
488
  { format: 'yaml', writeStrategy: '${writeStrategy}' },
472
489
  ) as string
473
490
  }
474
491
  return _openApiYamlCache
475
492
  }
476
493
 
477
- if (config.queryBuilder && config.queryBuilder !== false && _env.NODE_ENV !== 'production') {
494
+ if (config.queryBuilder && _env.NODE_ENV !== 'production') {
478
495
  console.warn(
479
496
  '[${modelName}Router] queryBuilder config is present but Hono target does not auto-start it. ' +
480
497
  'Run \`npx prisma-query-builder-ui\` in a separate process.',
@@ -482,7 +499,7 @@ export function ${routerFunctionName}<TCtx = unknown, TPrisma = any, TEnv extend
482
499
  }
483
500
 
484
501
  app.onError((err, c) => {
485
- return sendError(c as HandlerContext, err)
502
+ return sendError(c as unknown as HandlerContext, err)
486
503
  })
487
504
 
488
505
  if (!openApiDisabled) {
@@ -512,14 +529,15 @@ export function ${routerFunctionName}<TCtx = unknown, TPrisma = any, TEnv extend
512
529
  * that branch is inert rather than merely unreached when it is off.
513
530
  */
514
531
  const settleGuard = (c: Context<GeneratedHonoEnv<TEnv>>): void => {
515
- const failure = c.get('guardVariantFailure')
532
+ const vars = c as unknown as HandlerContext
533
+ const failure = vars.get('guardVariantFailure')
516
534
  if (failure) {
517
535
  throw new HTTPException(400, {
518
536
  message: formatGuardVariantResolutionError(failure),
519
537
  })
520
538
  }
521
539
 
522
- const shapeFailure = c.get('guardShapeFailure')
540
+ const shapeFailure = vars.get('guardShapeFailure')
523
541
  if (shapeFailure) {
524
542
  throw new HTTPException(500, {
525
543
  message: 'guard shape could not be resolved: ' + shapeFailure,
@@ -546,7 +564,7 @@ export function ${routerFunctionName}<TCtx = unknown, TPrisma = any, TEnv extend
546
564
  // Upstream order: hooks first, guard failure after.
547
565
  if (!SETTLE_BEFORE_HOOKS) settleGuard(c)
548
566
 
549
- const key = c.get('guardVariantKey')
567
+ const key = (c as unknown as HandlerContext).get('guardVariantKey')
550
568
  const variantHooks =
551
569
  key !== undefined ? opConfig.variantHooks[key] : undefined
552
570
 
@@ -581,7 +599,7 @@ export function ${routerFunctionName}<TCtx = unknown, TPrisma = any, TEnv extend
581
599
  // Upstream order: hooks first, guard failure after.
582
600
  if (!SETTLE_BEFORE_HOOKS) settleGuard(c)
583
601
 
584
- const key = c.get('guardVariantKey')
602
+ const key = (c as unknown as HandlerContext).get('guardVariantKey')
585
603
  const variantHooks =
586
604
  key !== undefined ? opConfig.variantHooks[key] : undefined
587
605