prisma-generator-express 1.67.0 → 1.69.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 (68) hide show
  1. package/README.md +223 -28
  2. package/dist/copy/operationDefinitions.d.ts +4 -1
  3. package/dist/copy/operationDefinitions.js +64 -3
  4. package/dist/copy/operationDefinitions.js.map +1 -1
  5. package/dist/generators/generateFastifyHandler.js +55 -9
  6. package/dist/generators/generateFastifyHandler.js.map +1 -1
  7. package/dist/generators/generateHonoHandler.js +44 -4
  8. package/dist/generators/generateHonoHandler.js.map +1 -1
  9. package/dist/generators/generateHonoOpenApi.d.ts +13 -0
  10. package/dist/generators/generateHonoOpenApi.js +70 -0
  11. package/dist/generators/generateHonoOpenApi.js.map +1 -0
  12. package/dist/generators/generateMcpApp.d.ts +10 -0
  13. package/dist/generators/generateMcpApp.js +113 -0
  14. package/dist/generators/generateMcpApp.js.map +1 -0
  15. package/dist/generators/generateModelMcp.d.ts +9 -0
  16. package/dist/generators/generateModelMcp.js +145 -0
  17. package/dist/generators/generateModelMcp.js.map +1 -0
  18. package/dist/generators/generateModelMetadata.js +9 -5
  19. package/dist/generators/generateModelMetadata.js.map +1 -1
  20. package/dist/generators/generateRouter.js +62 -56
  21. package/dist/generators/generateRouter.js.map +1 -1
  22. package/dist/generators/generateRouterFastify.js +56 -64
  23. package/dist/generators/generateRouterFastify.js.map +1 -1
  24. package/dist/generators/generateRouterHono.js +43 -411
  25. package/dist/generators/generateRouterHono.js.map +1 -1
  26. package/dist/generators/generateRouterPartsHono.d.ts +14 -0
  27. package/dist/generators/generateRouterPartsHono.js +180 -0
  28. package/dist/generators/generateRouterPartsHono.js.map +1 -0
  29. package/dist/generators/generateUnifiedHandler.js +36 -20
  30. package/dist/generators/generateUnifiedHandler.js.map +1 -1
  31. package/dist/index.d.ts +2 -0
  32. package/dist/index.js +101 -3
  33. package/dist/index.js.map +1 -1
  34. package/dist/utils/copyFiles.d.ts +1 -1
  35. package/dist/utils/copyFiles.js +20 -1
  36. package/dist/utils/copyFiles.js.map +1 -1
  37. package/dist/utils/mcpGate.d.ts +10 -0
  38. package/dist/utils/mcpGate.js +118 -0
  39. package/dist/utils/mcpGate.js.map +1 -0
  40. package/dist/utils/writeFileSafely.js +6 -0
  41. package/dist/utils/writeFileSafely.js.map +1 -1
  42. package/package.json +18 -3
  43. package/src/copy/buildModelOpenApi.ts +76 -126
  44. package/src/copy/docsRenderer.ts +14 -15
  45. package/src/copy/mcpRuntime.ts +1017 -0
  46. package/src/copy/operationDefinitions.ts +78 -4
  47. package/src/copy/operationPipeline.ts +267 -0
  48. package/src/copy/operationRuntime.ts +24 -14
  49. package/src/copy/operationSchemas.ts +2773 -0
  50. package/src/copy/pagination.ts +10 -2
  51. package/src/copy/routeConfig.hono.ts +34 -5
  52. package/src/copy/routeConfig.ts +1 -1
  53. package/src/copy/routerParts.ts +524 -0
  54. package/src/generators/generateFastifyHandler.ts +55 -9
  55. package/src/generators/generateHonoHandler.ts +44 -4
  56. package/src/generators/generateHonoOpenApi.ts +93 -0
  57. package/src/generators/generateMcpApp.ts +133 -0
  58. package/src/generators/generateModelMcp.ts +208 -0
  59. package/src/generators/generateModelMetadata.ts +16 -10
  60. package/src/generators/generateRouter.ts +62 -56
  61. package/src/generators/generateRouterFastify.ts +56 -64
  62. package/src/generators/generateRouterHono.ts +49 -459
  63. package/src/generators/generateRouterPartsHono.ts +220 -0
  64. package/src/generators/generateUnifiedHandler.ts +36 -21
  65. package/src/index.ts +116 -3
  66. package/src/utils/copyFiles.ts +36 -0
  67. package/src/utils/mcpGate.ts +157 -0
  68. package/src/utils/writeFileSafely.ts +6 -0
@@ -0,0 +1,220 @@
1
+ import type { DMMF } from '@prisma/generator-helper'
2
+ import { generateRouteConfigType } from './generateRouteConfigType'
3
+ import type { ImportStyle } from '../utils/resolveImportStyle'
4
+ import { importExt } from '../utils/importExt'
5
+ import type { WriteStrategy } from '../constants'
6
+ import { modelPathSegment, type PathCase } from '../utils/pathCasing'
7
+ import { OPERATION_METADATA } from '../copy/operationDefinitions'
8
+
9
+ function opKindFor(opName: string): string {
10
+ switch (opName) {
11
+ case 'findUnique':
12
+ case 'findUniqueOrThrow':
13
+ return 'readUnique'
14
+ case 'findMany':
15
+ case 'findFirst':
16
+ case 'findFirstOrThrow':
17
+ case 'findManyPaginated':
18
+ case 'count':
19
+ case 'aggregate':
20
+ case 'groupBy':
21
+ return 'read'
22
+ case 'create':
23
+ return 'create'
24
+ case 'createMany':
25
+ case 'createManyAndReturn':
26
+ return 'createMany'
27
+ case 'update':
28
+ return 'update'
29
+ case 'updateMany':
30
+ case 'updateManyAndReturn':
31
+ return 'updateMany'
32
+ case 'upsert':
33
+ return 'upsert'
34
+ case 'delete':
35
+ return 'delete'
36
+ case 'deleteMany':
37
+ return 'deleteMany'
38
+ default:
39
+ return 'noop'
40
+ }
41
+ }
42
+
43
+ /**
44
+ * The phase 9 static assembly boundary, emitted side: one named factory per
45
+ * operation, each returning a Hono sub-app with exactly that route. A consumer
46
+ * imports only the parts it serves; everything not imported is absent from the
47
+ * bundle — which `enableAll` at runtime can never do, because route
48
+ * configuration is runtime data. Heavy features are referenced only by their
49
+ * own parts (`userOpenApi` is the sole static referencer of the OpenAPI
50
+ * renderer and `yaml`).
51
+ */
52
+ export function generateHonoRouterParts({
53
+ model,
54
+ enums,
55
+ guardShapesImport,
56
+ clientImport,
57
+ importStyle,
58
+ writeStrategy,
59
+ dropGuard,
60
+ pathCase,
61
+ }: {
62
+ model: DMMF.Model
63
+ enums: DMMF.DatamodelEnum[]
64
+ guardShapesImport: string | null
65
+ clientImport?: string
66
+ importStyle: ImportStyle
67
+ writeStrategy: WriteStrategy
68
+ dropGuard: boolean
69
+ pathCase: PathCase
70
+ }): string {
71
+ const ext = importExt(importStyle)
72
+ const modelName = model.name
73
+ const modelSegment = modelPathSegment(modelName, pathCase)
74
+ const lower = modelName.charAt(0).toLowerCase() + modelName.slice(1)
75
+ const cap = (s: string) => s.charAt(0).toUpperCase() + s.slice(1)
76
+
77
+ const handlerImports = OPERATION_METADATA.map(
78
+ (m) => ` ${modelName}${cap(m.name)},`,
79
+ ).join('\n')
80
+
81
+ const readOps = OPERATION_METADATA.filter((m) => m.kind === 'read')
82
+ const writeOps = OPERATION_METADATA.filter(
83
+ (m) => m.kind === 'write' || m.kind === 'batch',
84
+ ).filter((m) => m.name !== 'updateEach')
85
+
86
+ const pathExpr = (suffix: string) =>
87
+ !suffix ? `setup.basePath || '/'` : `\`\${setup.basePath}${suffix}\``
88
+
89
+ const readFactories = readOps
90
+ .map((meta) => {
91
+ const fn = `${lower}${cap(meta.name)}`
92
+ const handler = `${modelName}${cap(meta.name)}`
93
+ const opKind = opKindFor(meta.name)
94
+ const pathValue = pathExpr(meta.pathSuffix)
95
+ const postPathValue =
96
+ meta.name === 'findMany'
97
+ ? `setup.basePath ? \`\${setup.basePath}/read\` : '/read'`
98
+ : pathExpr(meta.pathSuffix)
99
+ return `
100
+ export function ${fn}<TCtx = unknown, TPrisma extends PrismaClientLike = PrismaClientLike, TEnv extends HonoEnvBase = HonoEnvBase>(config: ${modelName}RouteConfig<TCtx, TPrisma, TEnv> = {}): Hono<GeneratedHonoEnv<TEnv>> {
101
+ const setup = ${lower}PartsSetup(config)
102
+ const opConfig = setup.opFor('${meta.configKey}')
103
+ const app = new Hono<GeneratedHonoEnv<TEnv>>()
104
+ app.get(${pathValue}, createReadRoute<TCtx, TPrisma, TEnv>({ config, opConfig, opKind: '${opKind}', handler: ${handler}, parse: 'query', dropGuard: setup.dropGuard, settleBeforeHooks: setup.settleBeforeHooks }))
105
+ return app
106
+ }
107
+
108
+ export function ${fn}PostRead<TCtx = unknown, TPrisma extends PrismaClientLike = PrismaClientLike, TEnv extends HonoEnvBase = HonoEnvBase>(config: ${modelName}RouteConfig<TCtx, TPrisma, TEnv> = {}): Hono<GeneratedHonoEnv<TEnv>> {
109
+ const setup = ${lower}PartsSetup(config)
110
+ const opConfig = setup.opFor('${meta.configKey}')
111
+ const app = new Hono<GeneratedHonoEnv<TEnv>>()
112
+ app.post(${postPathValue}, createReadRoute<TCtx, TPrisma, TEnv>({ config, opConfig, opKind: '${opKind}', handler: ${handler}, parse: 'body', dropGuard: setup.dropGuard, settleBeforeHooks: setup.settleBeforeHooks }))
113
+ return app
114
+ }
115
+ `
116
+ })
117
+ .join('\n')
118
+
119
+ const writeFactories = writeOps
120
+ .map((meta) => {
121
+ const fn = `${lower}${cap(meta.name)}`
122
+ const handler = `${modelName}${cap(meta.name)}`
123
+ const opKind = opKindFor(meta.name)
124
+ const pathValue = pathExpr(meta.pathSuffix)
125
+ return `
126
+ export function ${fn}<TCtx = unknown, TPrisma extends PrismaClientLike = PrismaClientLike, TEnv extends HonoEnvBase = HonoEnvBase>(config: ${modelName}RouteConfig<TCtx, TPrisma, TEnv> = {}): Hono<GeneratedHonoEnv<TEnv>> {
127
+ const setup = ${lower}PartsSetup(config)
128
+ const opConfig = setup.opFor('${meta.configKey}')
129
+ const app = new Hono<GeneratedHonoEnv<TEnv>>()
130
+ app.${meta.method}(${pathValue}, createWriteRoute<TCtx, TPrisma, TEnv>({ config, opConfig, opKind: '${opKind}', handler: ${handler}, dropGuard: setup.dropGuard, settleBeforeHooks: setup.settleBeforeHooks }))
131
+ return app
132
+ }
133
+ `
134
+ })
135
+ .join('\n')
136
+
137
+ return `import { Hono } from 'hono'
138
+ import {
139
+ ${handlerImports}
140
+ } from './${modelName}Handlers${ext}'
141
+ import type {
142
+ RouteConfig,
143
+ HonoEnvBase,
144
+ GeneratedHonoEnv,
145
+ PrismaClientLike,
146
+ } from '../routeConfig.target${ext}'
147
+ import { normalizePrefix, getEnv, resolveDropGuardEnv } from '../misc${ext}'
148
+ import {
149
+ resolveGuardPolicy,
150
+ validateCountSourceWhere,
151
+ warnIfUnguardedRoutes,
152
+ } from '../routeConfig${ext}'
153
+ import {
154
+ createReadRoute,
155
+ createWriteRoute,
156
+ createUpdateEachRoute,
157
+ opConfigFor,
158
+ normalizeHonoOperation,
159
+ type HonoOpConfig,
160
+ } from '../routerParts${ext}'
161
+ import { MODEL_FIELDS, MODEL_ENUMS } from './${modelName}Metadata${ext}'
162
+
163
+ ${generateRouteConfigType(modelName, 'HonoBeforeHook', guardShapesImport, importStyle, 'hono', clientImport)}
164
+
165
+ // Fixed at generation time, identical to the legacy router factory.
166
+ const DROP_GUARD = ${dropGuard}
167
+
168
+ // No module-level side effects: this file must be tree-shakeable, so unused
169
+ // per-op exports — and everything only they reference, down to the OpenAPI
170
+ // renderer and yaml — drop out of a bundler's output. The environment is read
171
+ // inside the functions that need it.
172
+
173
+ function ${lower}PartsSetup<TCtx = unknown, TPrisma extends PrismaClientLike = PrismaClientLike, TEnv extends HonoEnvBase = HonoEnvBase>(config: ${modelName}RouteConfig<TCtx, TPrisma, TEnv>) {
174
+ validateCountSourceWhere(config.pagination?.countSource, '${modelName} pagination')
175
+ validateCountSourceWhere(
176
+ (config.findManyPaginated && typeof config.findManyPaginated === 'object' ? config.findManyPaginated : undefined)?.pagination?.countSource,
177
+ '${modelName} findManyPaginated pagination',
178
+ )
179
+
180
+ const isEnabled = (value: unknown): boolean => value !== false && !!(config.enableAll || value)
181
+ warnIfUnguardedRoutes('${modelName}', ['findMany', 'findUnique', 'findUniqueOrThrow', 'findFirst', 'findFirstOrThrow', 'findManyPaginated', 'count', 'aggregate', 'groupBy', 'create', 'createMany', 'createManyAndReturn', 'update', 'updateMany', 'updateManyAndReturn', 'upsert', 'delete', 'deleteMany'], config, isEnabled)
182
+
183
+ const customPrefix = normalizePrefix(config.customUrlPrefix || '')
184
+ const modelPrefix = config.addModelPrefix !== false ? '/${modelSegment}' : ''
185
+ const basePath = customPrefix + modelPrefix
186
+
187
+ const POLICY = resolveGuardPolicy(config)
188
+ const dropGuard =
189
+ DROP_GUARD || (POLICY.allowE2EGuardBypass && resolveDropGuardEnv(getEnv()))
190
+ const settleBeforeHooks = POLICY.guardResolutionOrder === 'before-hooks'
191
+
192
+ const opFor = <K extends keyof ${modelName}RouteConfig<TCtx, TPrisma, TEnv>>(
193
+ key: K,
194
+ ): HonoOpConfig<TEnv> => {
195
+ const raw = config[key] as unknown as Parameters<typeof normalizeHonoOperation<TEnv>>[0]
196
+ return opConfigFor<TEnv>(raw, '${modelName}.' + String(key), POLICY)
197
+ }
198
+
199
+ return { basePath, POLICY, dropGuard, settleBeforeHooks, opFor }
200
+ }
201
+ ${readFactories}
202
+ ${writeFactories}
203
+ export function ${lower}UpdateEach<TCtx = unknown, TPrisma extends PrismaClientLike = PrismaClientLike, TEnv extends HonoEnvBase = HonoEnvBase>(config: ${modelName}RouteConfig<TCtx, TPrisma, TEnv> = {}): Hono<GeneratedHonoEnv<TEnv>> {
204
+ const setup = ${lower}PartsSetup(config)
205
+ if (!setup.POLICY.enableUpdateEach) {
206
+ throw new Error(
207
+ '${modelName}.updateEach: enableUpdateEach is false, so this part ' +
208
+ 'is not available. It bypasses guard shapes entirely, so there is no ' +
209
+ 'configuration that makes it safe to expose.',
210
+ )
211
+ }
212
+ const raw = config.updateEach as unknown as Parameters<typeof normalizeHonoOperation<TEnv>>[0]
213
+ const opConfig = normalizeHonoOperation<TEnv>(raw)
214
+ const app = new Hono<GeneratedHonoEnv<TEnv>>()
215
+ app.post(setup.basePath ? \`\${setup.basePath}/each\` : '/each', createUpdateEachRoute<TCtx, TPrisma, TEnv>({ config, opConfig, handler: ${modelName}UpdateEach, dropGuard: setup.dropGuard }))
216
+ return app
217
+ }
218
+
219
+ `
220
+ }
@@ -11,7 +11,6 @@ export interface UnifiedHandlerOptions {
11
11
  export function generateUnifiedHandler(options: UnifiedHandlerOptions): string {
12
12
  const ext = importExt(options.importStyle)
13
13
  const modelName = options.model.name
14
-
15
14
  const dispatchOps = OPERATION_METADATA.filter((m) => m.name !== 'updateEach')
16
15
 
17
16
  const handlers = dispatchOps
@@ -25,10 +24,25 @@ export async function ${exportName}(
25
24
  next: NextFunction,
26
25
  ) {
27
26
  try {
28
- ;(res.locals as LocalsBag).data = await core.${meta.coreName}(buildContext(req, res))
27
+ const locals = readLocals(res)
28
+ ;locals.data = await executeOperation(
29
+ { variantKey: locals.guardVariantKey, caller: locals.guardCaller },
30
+ { guardShape: locals.guardShape },
31
+ {
32
+ core: core.${meta.coreName},
33
+ args: queryChannel(res),
34
+ body: req.body,
35
+ prisma: requirePrisma((req as ExtendedRequest).prisma),
36
+ postgres: (req as ExtendedRequest).postgres,
37
+ sqlite: (req as ExtendedRequest).sqlite,
38
+ pagination: locals.routeConfig?.pagination,
39
+ override: locals.operationOverride,
40
+ getContext: locals.getContext,
41
+ },
42
+ )
29
43
  next()
30
44
  } catch (error: unknown) {
31
- next(mapError(error))
45
+ next(classifyError(error))
32
46
  }
33
47
  }`
34
48
  })
@@ -36,8 +50,14 @@ export async function ${exportName}(
36
50
 
37
51
  return `import type { Request, Response, NextFunction } from 'express'
38
52
  import * as core from './${modelName}Core${ext}'
39
- import type { OperationContext, RuntimeOperationOverride } from '../operationRuntime${ext}'
40
- import { mapError } from '../errorMapper${ext}'
53
+ import type { RuntimeOperationOverride } from '../operationRuntime${ext}'
54
+ import type { PaginationConfig } from '../routeConfig${ext}'
55
+ import {
56
+ executeOperation,
57
+ classifyError,
58
+ requirePrisma,
59
+ type ArgsChannel,
60
+ } from '../operationPipeline${ext}'
41
61
 
42
62
  type ExtendedRequest = Request & {
43
63
  prisma?: unknown
@@ -47,30 +67,25 @@ type ExtendedRequest = Request & {
47
67
 
48
68
  type LocalsBag = {
49
69
  parsedQuery?: Record<string, unknown>
50
- routeConfig?: { pagination?: unknown }
70
+ routeConfig?: { pagination?: PaginationConfig }
51
71
  guardShape?: Record<string, unknown>
52
72
  guardCaller?: string
53
73
  guardVariantKey?: string
54
74
  operationOverride?: RuntimeOperationOverride
55
- resolveOperationContext?: () => unknown | Promise<unknown>
75
+ getContext?: () => Promise<unknown>
56
76
  data?: unknown
57
77
  }
58
78
 
59
- function buildContext(req: Request, res: Response): OperationContext {
60
- const extReq = req as ExtendedRequest
61
- const locals = res.locals as LocalsBag
79
+ function readLocals(res: Response): LocalsBag {
80
+ return res.locals as LocalsBag
81
+ }
82
+
83
+ function queryChannel(res: Response): ArgsChannel {
62
84
  return {
63
- operationOverride: locals.operationOverride,
64
- resolveOperationContext: locals.resolveOperationContext,
65
- prisma: extReq.prisma,
66
- postgres: extReq.postgres,
67
- sqlite: extReq.sqlite,
68
- parsedQuery: locals.parsedQuery,
69
- body: req.body,
70
- guardShape: locals.guardShape,
71
- guardCaller: locals.guardCaller,
72
- guardVariantKey: locals.guardVariantKey,
73
- paginationConfig: (locals.routeConfig?.pagination) as OperationContext['paginationConfig'],
85
+ read: () => readLocals(res).parsedQuery,
86
+ write: (next) => {
87
+ readLocals(res).parsedQuery = next
88
+ },
74
89
  }
75
90
  }
76
91
  ${handlers}
package/src/index.ts CHANGED
@@ -33,6 +33,10 @@ import { generateHonoHandler } from './generators/generateHonoHandler'
33
33
  import { generateRouterFunction } from './generators/generateRouter'
34
34
  import { generateFastifyRouterFunction } from './generators/generateRouterFastify'
35
35
  import { generateHonoRouterFunction } from './generators/generateRouterHono'
36
+ import { generateHonoRouterParts } from './generators/generateRouterPartsHono'
37
+ import { generateHonoOpenApiRoutes } from './generators/generateHonoOpenApi'
38
+ import { generateModelMcp } from './generators/generateModelMcp'
39
+ import { generateMcpApp, generateMcpMount } from './generators/generateMcpApp'
36
40
  import { generateScalarUIHandler } from './generators/generateUnifiedScalarUI'
37
41
  import { generateUnifiedDocs } from './generators/generateUnifiedDocs'
38
42
  import { generateQueryBuilderHelper } from './generators/generateQueryBuilderHelper'
@@ -48,6 +52,10 @@ import {
48
52
  getGuardShapesImport,
49
53
  } from './generators/generateImportPrismaStatement'
50
54
  import { writeFileSafely } from './utils/writeFileSafely'
55
+ import {
56
+ assertMcpGuardCompatibility,
57
+ assertMcpGuardVersion,
58
+ } from './utils/mcpGate'
51
59
  import { copyFiles } from './utils/copyFiles'
52
60
  import { resolveImportStyle, ImportStyle } from './utils/resolveImportStyle'
53
61
  import { parsePathCase, modelPathSegment, PathCase } from './utils/pathCasing'
@@ -111,6 +119,26 @@ function getPathCase(options: GeneratorOptions): PathCase {
111
119
  )
112
120
  }
113
121
 
122
+ function getMcp(options: GeneratorOptions): boolean {
123
+ const raw = (options.generator.config as Record<string, unknown>).mcp
124
+ if (raw === undefined || raw === null) return false
125
+ if (typeof raw === 'boolean') return raw
126
+ if (typeof raw === 'string') {
127
+ const lower = raw.trim().toLowerCase()
128
+ if (lower === 'true' || lower === '1' || lower === 'yes') return true
129
+ if (
130
+ lower === 'false' ||
131
+ lower === '0' ||
132
+ lower === 'no' ||
133
+ raw.trim() === ''
134
+ )
135
+ return false
136
+ }
137
+ throw new Error(
138
+ 'Invalid mcp option "' + String(raw) + '". Expected true or false.',
139
+ )
140
+ }
141
+
114
142
  function getDropGuard(options: GeneratorOptions): boolean {
115
143
  const raw = (options.generator.config as Record<string, unknown>).dropGuard
116
144
  if (raw === undefined || raw === null) return false
@@ -149,12 +177,26 @@ generatorHandler({
149
177
  },
150
178
 
151
179
  async onGenerate(options: GeneratorOptions) {
180
+ await runGenerate(options)
181
+ },
182
+ })
183
+
184
+ /**
185
+ * The full generation path, exported so tests exercise what `prisma generate`
186
+ * runs — including the mcp/dropGuard gate — instead of re-implementing pieces.
187
+ */
188
+ export async function runGenerate(options: GeneratorOptions): Promise<void> {
189
+ {
152
190
  const target = getTarget(options)
153
191
  const writeStrategy = getWriteStrategy(options)
154
192
  const findManyPaginatedMode = getFindManyPaginatedMode(options)
155
193
  const dropGuard = getDropGuard(options)
194
+ const mcp = getMcp(options)
156
195
  const pathCase = getPathCase(options)
157
196
 
197
+ assertMcpGuardCompatibility(mcp, dropGuard)
198
+ assertMcpGuardVersion(mcp, options.schemaPath)
199
+
158
200
  const manifestDefaultAbs = path.resolve(
159
201
  __dirname,
160
202
  '..',
@@ -174,6 +216,7 @@ generatorHandler({
174
216
  const importStyle = resolveImportStyle(options)
175
217
 
176
218
  console.log(`\n═══ Prisma Generator (${target.toUpperCase()}) ═══`)
219
+ if (mcp) console.log(` MCP: enabled (reads + opt-in writes)`)
177
220
  console.log(` Target: ${target}`)
178
221
  console.log(` Output: ${options.generator.output?.value}`)
179
222
  console.log(` Import style: ${importStyle}`)
@@ -193,7 +236,7 @@ generatorHandler({
193
236
  validateClientGeneratorPresent(options)
194
237
  }
195
238
 
196
- await copyFiles(options, target, importStyle)
239
+ await copyFiles(options, target, importStyle, mcp)
197
240
 
198
241
  const modelNames: string[] = []
199
242
  let guardArtifactsSeen = false
@@ -292,6 +335,38 @@ generatorHandler({
292
335
  operation: 'Router',
293
336
  })
294
337
 
338
+ if (target === 'hono') {
339
+ await writeFileSafely({
340
+ content: generateHonoRouterParts({
341
+ model: model as DMMF.Model,
342
+ enums: options.dmmf.datamodel.enums as DMMF.DatamodelEnum[],
343
+ guardShapesImport,
344
+ clientImport: getRelativeClientTypeImport(options, model.name),
345
+ importStyle,
346
+ writeStrategy,
347
+ dropGuard,
348
+ pathCase,
349
+ }),
350
+ options,
351
+ model: model as DMMF.Model,
352
+ operation: 'RouterParts',
353
+ })
354
+ await writeFileSafely({
355
+ content: generateHonoOpenApiRoutes({
356
+ model: model as DMMF.Model,
357
+ enums: options.dmmf.datamodel.enums as DMMF.DatamodelEnum[],
358
+ guardShapesImport,
359
+ clientImport: getRelativeClientTypeImport(options, model.name),
360
+ importStyle,
361
+ writeStrategy,
362
+ pathCase,
363
+ }),
364
+ options,
365
+ model: model as DMMF.Model,
366
+ operation: 'OpenApiRoutes',
367
+ })
368
+ }
369
+
295
370
  await writeFileSafely({
296
371
  content: generateScalarUIHandler({
297
372
  model: model as DMMF.Model,
@@ -327,6 +402,27 @@ generatorHandler({
327
402
  })
328
403
  }
329
404
 
405
+ if (mcp) {
406
+ // after the loop: each tool factory narrows schemas with THIS model's
407
+ // metadata plus the metadata of every model it relates to
408
+ for (const model of allModels) {
409
+ if (!modelNames.includes(model.name)) continue
410
+ await writeFileSafely({
411
+ content: generateModelMcp({
412
+ model,
413
+ allModels: allModels.filter((m) =>
414
+ modelNames.includes(m.name),
415
+ ) as DMMF.Model[],
416
+ importStyle,
417
+ writeStrategy,
418
+ }),
419
+ options,
420
+ model: model as DMMF.Model,
421
+ operation: 'Mcp',
422
+ })
423
+ }
424
+ }
425
+
330
426
  const pathSegments = Object.fromEntries(
331
427
  modelNames.map((n) => [n, modelPathSegment(n, pathCase)]),
332
428
  )
@@ -348,6 +444,23 @@ generatorHandler({
348
444
  operation: 'queryBuilder',
349
445
  })
350
446
 
447
+ if (mcp) {
448
+ await writeFileSafely({
449
+ content: generateMcpApp({
450
+ serverName: 'prisma-api',
451
+ serverVersion: require('../package.json').version,
452
+ importStyle,
453
+ }),
454
+ options,
455
+ operation: 'mcpApp',
456
+ })
457
+ await writeFileSafely({
458
+ content: generateMcpMount({ target, importStyle }),
459
+ options,
460
+ operation: 'mcpMount',
461
+ })
462
+ }
463
+
351
464
  if (modelNames.length > 0 && !guardArtifactsSeen) {
352
465
  console.log('')
353
466
  console.log(
@@ -361,5 +474,5 @@ generatorHandler({
361
474
  console.log('\n═══ Generation Complete ═══')
362
475
  console.log(`✓ ${modelNames.length} models (${target})`)
363
476
  console.log('')
364
- },
365
- })
477
+ }
478
+ }
@@ -9,10 +9,12 @@ const SHARED_FILES = [
9
9
  'parseQueryParams.ts',
10
10
  'buildModelOpenApi.ts',
11
11
  'operationDefinitions.ts',
12
+ 'operationSchemas.ts',
12
13
  'misc.ts',
13
14
  'routeConfig.ts',
14
15
  'docsRenderer.ts',
15
16
  'operationRuntime.ts',
17
+ 'operationPipeline.ts',
16
18
  'errorMapper.ts',
17
19
  'sse.ts',
18
20
  'pagination.ts',
@@ -126,6 +128,7 @@ export async function copyFiles(
126
128
  options: GeneratorOptions,
127
129
  target: Target,
128
130
  importStyle: ImportStyle,
131
+ mcp = false,
129
132
  ): Promise<void> {
130
133
  const outputPath = options.generator.output?.value
131
134
  if (!outputPath) return
@@ -153,6 +156,39 @@ export async function copyFiles(
153
156
  }
154
157
  }
155
158
 
159
+ if (target === 'hono') {
160
+ // The phase 9 assembly boundary. Imports the Hono route-config types by
161
+ // their source name; emitted as routeConfig.target.ts like every target.
162
+ const err = copyFileSync(
163
+ copyBase,
164
+ outputPath,
165
+ 'routerParts.ts',
166
+ importStyle,
167
+ {
168
+ required: true,
169
+ importRewrites: [
170
+ { from: './routeConfig.hono', to: './routeConfig.target' },
171
+ ],
172
+ },
173
+ )
174
+ if (err) errors.push(err)
175
+ }
176
+
177
+ if (mcp) {
178
+ // MCP runtime is copied ONLY for mcp=true generations: an mcp=false
179
+ // output contains no MCP files, imports or dependencies at all.
180
+ const err = copyFileSync(
181
+ copyBase,
182
+ outputPath,
183
+ 'mcpRuntime.ts',
184
+ importStyle,
185
+ {
186
+ required: true,
187
+ },
188
+ )
189
+ if (err) errors.push(err)
190
+ }
191
+
156
192
  const targetConfigFile = 'routeConfig.' + target + '.ts'
157
193
  const err = copyFileSync(
158
194
  copyBase,