prisma-generator-express 1.38.0 → 1.40.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 +524 -163
- package/dist/constants.d.ts +1 -1
- package/dist/generators/generateHonoHandler.d.ts +4 -0
- package/dist/generators/generateHonoHandler.js +94 -0
- package/dist/generators/generateHonoHandler.js.map +1 -0
- package/dist/generators/generateRouterHono.d.ts +6 -0
- package/dist/generators/generateRouterHono.js +368 -0
- package/dist/generators/generateRouterHono.js.map +1 -0
- package/dist/generators/generateUnifiedDocs.js +50 -4
- package/dist/generators/generateUnifiedDocs.js.map +1 -1
- package/dist/generators/generateUnifiedScalarUI.js +55 -2
- package/dist/generators/generateUnifiedScalarUI.js.map +1 -1
- package/dist/index.js +27 -10
- package/dist/index.js.map +1 -1
- package/package.json +4 -2
- package/src/constants.ts +1 -1
- package/src/copy/routeConfig.hono.ts +21 -0
- package/src/generators/generateHonoHandler.ts +104 -0
- package/src/generators/generateRouterHono.ts +380 -0
- package/src/generators/generateUnifiedDocs.ts +52 -4
- package/src/generators/generateUnifiedScalarUI.ts +56 -2
- package/src/index.ts +28 -11
package/dist/index.js
CHANGED
|
@@ -7,8 +7,10 @@ const generator_helper_1 = require("@prisma/generator-helper");
|
|
|
7
7
|
const path_1 = __importDefault(require("path"));
|
|
8
8
|
const generateUnifiedHandler_1 = require("./generators/generateUnifiedHandler");
|
|
9
9
|
const generateFastifyHandler_1 = require("./generators/generateFastifyHandler");
|
|
10
|
+
const generateHonoHandler_1 = require("./generators/generateHonoHandler");
|
|
10
11
|
const generateRouter_1 = require("./generators/generateRouter");
|
|
11
12
|
const generateRouterFastify_1 = require("./generators/generateRouterFastify");
|
|
13
|
+
const generateRouterHono_1 = require("./generators/generateRouterHono");
|
|
12
14
|
const generateUnifiedScalarUI_1 = require("./generators/generateUnifiedScalarUI");
|
|
13
15
|
const generateUnifiedDocs_1 = require("./generators/generateUnifiedDocs");
|
|
14
16
|
const generateQueryBuilderHelper_1 = require("./generators/generateQueryBuilderHelper");
|
|
@@ -19,9 +21,9 @@ const copyFiles_1 = require("./utils/copyFiles");
|
|
|
19
21
|
const constants_1 = require("./constants");
|
|
20
22
|
function getTarget(options) {
|
|
21
23
|
const raw = String(options.generator.config.target ?? 'express').toLowerCase();
|
|
22
|
-
if (raw === 'express' || raw === 'fastify')
|
|
24
|
+
if (raw === 'express' || raw === 'fastify' || raw === 'hono')
|
|
23
25
|
return raw;
|
|
24
|
-
throw new Error(`Invalid target "${raw}". Expected "express" or "
|
|
26
|
+
throw new Error(`Invalid target "${raw}". Expected "express", "fastify", or "hono".`);
|
|
25
27
|
}
|
|
26
28
|
(0, generator_helper_1.generatorHandler)({
|
|
27
29
|
onManifest() {
|
|
@@ -50,7 +52,11 @@ function getTarget(options) {
|
|
|
50
52
|
operation: 'operationRuntime',
|
|
51
53
|
});
|
|
52
54
|
const modelNames = [];
|
|
53
|
-
const generateHandler = target === 'fastify'
|
|
55
|
+
const generateHandler = target === 'fastify'
|
|
56
|
+
? generateFastifyHandler_1.generateFastifyHandler
|
|
57
|
+
: target === 'hono'
|
|
58
|
+
? generateHonoHandler_1.generateHonoHandler
|
|
59
|
+
: generateUnifiedHandler_1.generateUnifiedHandler;
|
|
54
60
|
for (const model of options.dmmf.datamodel.models) {
|
|
55
61
|
if (model.documentation &&
|
|
56
62
|
model.documentation.includes('generator off')) {
|
|
@@ -80,12 +86,18 @@ function getTarget(options) {
|
|
|
80
86
|
enums: options.dmmf.datamodel.enums,
|
|
81
87
|
guardShapesImport,
|
|
82
88
|
})
|
|
83
|
-
:
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
+
: target === 'hono'
|
|
90
|
+
? (0, generateRouterHono_1.generateHonoRouterFunction)({
|
|
91
|
+
model: model,
|
|
92
|
+
enums: options.dmmf.datamodel.enums,
|
|
93
|
+
guardShapesImport,
|
|
94
|
+
})
|
|
95
|
+
: (0, generateRouter_1.generateRouterFunction)({
|
|
96
|
+
model: model,
|
|
97
|
+
enums: options.dmmf.datamodel.enums,
|
|
98
|
+
relativeClientPath,
|
|
99
|
+
guardShapesImport,
|
|
100
|
+
});
|
|
89
101
|
await (0, writeFileSafely_1.writeFileSafely)({
|
|
90
102
|
content: routerContent,
|
|
91
103
|
options,
|
|
@@ -116,7 +128,12 @@ function getTarget(options) {
|
|
|
116
128
|
console.log('\n═══ Generation Complete ═══');
|
|
117
129
|
console.log(`✓ ${modelNames.length} models (${target})`);
|
|
118
130
|
console.log(`✓ OpenAPI documentation generated`);
|
|
119
|
-
|
|
131
|
+
if (target === 'hono') {
|
|
132
|
+
console.log(`✓ Query builder helper generated (not auto-started for Hono)`);
|
|
133
|
+
}
|
|
134
|
+
else {
|
|
135
|
+
console.log(`✓ Query builder helper generated`);
|
|
136
|
+
}
|
|
120
137
|
console.log('');
|
|
121
138
|
},
|
|
122
139
|
});
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;AAAA,+DAIiC;AACjC,gDAAuB;AACvB,gFAA4E;AAC5E,gFAA4E;AAC5E,gEAAoE;AACpE,8EAAkF;AAClF,kFAA8E;AAC9E,0EAAsE;AACtE,wFAAoF;AACpF,8EAG2C;AAC3C,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "prisma-generator-express",
|
|
3
|
-
"description": "Prisma generator for Express
|
|
4
|
-
"version": "1.
|
|
3
|
+
"description": "Prisma generator for Express, Fastify, and Hono CRUD APIs with OpenAPI documentation",
|
|
4
|
+
"version": "1.40.0",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
7
7
|
"license": "MIT",
|
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
"@prisma/client": ">=6.0.0",
|
|
26
26
|
"express": ">=4.0.0",
|
|
27
27
|
"fastify": "^5.0.0",
|
|
28
|
+
"hono": ">=4.0.0",
|
|
28
29
|
"prisma-guard": ">=1.0.0",
|
|
29
30
|
"prisma-query-builder-ui": ">=1.1.0",
|
|
30
31
|
"prisma-sql": ">=1.0.0"
|
|
@@ -50,6 +51,7 @@
|
|
|
50
51
|
"express": "^5.2.1",
|
|
51
52
|
"fast-xml-parser": "^5.3.3",
|
|
52
53
|
"fastify": "^5.6.2",
|
|
54
|
+
"hono": "^4.12.23",
|
|
53
55
|
"lru-cache": "^11.2.4",
|
|
54
56
|
"pino": "^10.1.0",
|
|
55
57
|
"pino-pretty": "^13.0.0",
|
package/src/constants.ts
CHANGED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { Context, Next } from 'hono'
|
|
2
|
+
import type {
|
|
3
|
+
BaseOperationConfig,
|
|
4
|
+
BaseRouteConfig,
|
|
5
|
+
QueryBuilderConfig,
|
|
6
|
+
OpenApiServerConfig,
|
|
7
|
+
OpenApiSecuritySchemeConfig,
|
|
8
|
+
} from './routeConfig'
|
|
9
|
+
|
|
10
|
+
export type { QueryBuilderConfig, OpenApiServerConfig, OpenApiSecuritySchemeConfig }
|
|
11
|
+
|
|
12
|
+
export type HonoHookHandler<Env extends { Variables: any } = any> = (
|
|
13
|
+
c: Context<Env>,
|
|
14
|
+
next: Next,
|
|
15
|
+
) => Promise<Response | void> | Response | void
|
|
16
|
+
|
|
17
|
+
export type OperationConfig<TShape = Record<string, any>> =
|
|
18
|
+
BaseOperationConfig<HonoHookHandler, TShape>
|
|
19
|
+
|
|
20
|
+
export type RouteConfig<TShape = Record<string, any>> =
|
|
21
|
+
BaseRouteConfig<HonoHookHandler, Context, TShape>
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { DMMF } from '@prisma/generator-helper'
|
|
2
|
+
import { toCamelCase } from '../utils/strings'
|
|
3
|
+
|
|
4
|
+
const CORE_NAME_MAP: Record<string, string> = {
|
|
5
|
+
delete: 'deleteUnique',
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function coreFnName(op: string): string {
|
|
9
|
+
return CORE_NAME_MAP[op] || op
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const READ_OPS = [
|
|
13
|
+
'findMany',
|
|
14
|
+
'findFirst',
|
|
15
|
+
'findFirstOrThrow',
|
|
16
|
+
'findUnique',
|
|
17
|
+
'findUniqueOrThrow',
|
|
18
|
+
'findManyPaginated',
|
|
19
|
+
'aggregate',
|
|
20
|
+
'count',
|
|
21
|
+
'groupBy',
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
const WRITE_OPS = [
|
|
25
|
+
'create',
|
|
26
|
+
'createMany',
|
|
27
|
+
'createManyAndReturn',
|
|
28
|
+
'update',
|
|
29
|
+
'updateMany',
|
|
30
|
+
'updateManyAndReturn',
|
|
31
|
+
'upsert',
|
|
32
|
+
'delete',
|
|
33
|
+
'deleteMany',
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
const CREATED_OPS = new Set([
|
|
37
|
+
'create',
|
|
38
|
+
'createMany',
|
|
39
|
+
'createManyAndReturn',
|
|
40
|
+
])
|
|
41
|
+
|
|
42
|
+
export function generateHonoHandler(options: {
|
|
43
|
+
model: DMMF.Model
|
|
44
|
+
}): string {
|
|
45
|
+
const modelName = options.model.name
|
|
46
|
+
const prefix = toCamelCase(modelName)
|
|
47
|
+
|
|
48
|
+
const readHandlers = READ_OPS.map((op) => {
|
|
49
|
+
const exportName = `${prefix}${op.charAt(0).toUpperCase() + op.slice(1)}`
|
|
50
|
+
|
|
51
|
+
return `
|
|
52
|
+
export async function ${exportName}(c: Context<HonoEnv>): Promise<void> {
|
|
53
|
+
const data = await core.${coreFnName(op)}(buildContext(c))
|
|
54
|
+
c.set('resultData', data)
|
|
55
|
+
}`
|
|
56
|
+
}).join('\n')
|
|
57
|
+
|
|
58
|
+
const writeHandlers = WRITE_OPS.map((op) => {
|
|
59
|
+
const exportName = `${prefix}${op.charAt(0).toUpperCase() + op.slice(1)}`
|
|
60
|
+
const statusCode = CREATED_OPS.has(op) ? 201 : 200
|
|
61
|
+
|
|
62
|
+
return `
|
|
63
|
+
export async function ${exportName}(c: Context<HonoEnv>): Promise<void> {
|
|
64
|
+
const data = await core.${coreFnName(op)}(buildContext(c))
|
|
65
|
+
c.set('resultData', data)
|
|
66
|
+
c.set('resultStatus', ${statusCode})
|
|
67
|
+
}`
|
|
68
|
+
}).join('\n')
|
|
69
|
+
|
|
70
|
+
return `import type { Context } from 'hono'
|
|
71
|
+
import * as core from './${modelName}Core'
|
|
72
|
+
import type { OperationContext } from '../operationRuntime'
|
|
73
|
+
|
|
74
|
+
type HonoVariables = {
|
|
75
|
+
prisma: any
|
|
76
|
+
postgres?: any
|
|
77
|
+
sqlite?: any
|
|
78
|
+
parsedQuery?: Record<string, unknown>
|
|
79
|
+
body?: unknown
|
|
80
|
+
routeConfig?: any
|
|
81
|
+
guardShape?: Record<string, unknown>
|
|
82
|
+
guardCaller?: string
|
|
83
|
+
resultData?: unknown
|
|
84
|
+
resultStatus?: number
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
type HonoEnv = { Variables: HonoVariables }
|
|
88
|
+
|
|
89
|
+
function buildContext(c: Context<HonoEnv>): OperationContext {
|
|
90
|
+
return {
|
|
91
|
+
prisma: c.get('prisma'),
|
|
92
|
+
postgres: c.get('postgres'),
|
|
93
|
+
sqlite: c.get('sqlite'),
|
|
94
|
+
parsedQuery: c.get('parsedQuery'),
|
|
95
|
+
body: c.get('body'),
|
|
96
|
+
guardShape: c.get('guardShape'),
|
|
97
|
+
guardCaller: c.get('guardCaller'),
|
|
98
|
+
paginationConfig: c.get('routeConfig')?.pagination,
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
${readHandlers}
|
|
102
|
+
${writeHandlers}
|
|
103
|
+
`
|
|
104
|
+
}
|
|
@@ -0,0 +1,380 @@
|
|
|
1
|
+
import { DMMF } from '@prisma/generator-helper'
|
|
2
|
+
import { toCamelCase } from '../utils/strings'
|
|
3
|
+
import { generateRouteConfigType } from './generateRouteConfigType'
|
|
4
|
+
|
|
5
|
+
export function generateHonoRouterFunction({
|
|
6
|
+
model,
|
|
7
|
+
enums,
|
|
8
|
+
guardShapesImport,
|
|
9
|
+
}: {
|
|
10
|
+
model: DMMF.Model
|
|
11
|
+
enums: DMMF.DatamodelEnum[]
|
|
12
|
+
guardShapesImport: string | null
|
|
13
|
+
}): string {
|
|
14
|
+
const modelName = model.name
|
|
15
|
+
const prefix = toCamelCase(modelName)
|
|
16
|
+
const modelNameLower = modelName.toLowerCase()
|
|
17
|
+
const routerFunctionName = `${prefix}Router`
|
|
18
|
+
|
|
19
|
+
const fieldsMeta = model.fields.map((f) => ({
|
|
20
|
+
name: f.name,
|
|
21
|
+
kind: f.kind,
|
|
22
|
+
type: f.type,
|
|
23
|
+
isList: f.isList,
|
|
24
|
+
isRequired: f.isRequired,
|
|
25
|
+
hasDefaultValue: f.hasDefaultValue,
|
|
26
|
+
isUpdatedAt: f.isUpdatedAt ?? false,
|
|
27
|
+
documentation: f.documentation,
|
|
28
|
+
relationFromFields: f.relationFromFields,
|
|
29
|
+
}))
|
|
30
|
+
|
|
31
|
+
const referencedEnumTypes = new Set(
|
|
32
|
+
model.fields.filter((f) => f.kind === 'enum').map((f) => f.type),
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
const enumsMeta = enums
|
|
36
|
+
.filter((e) => referencedEnumTypes.has(e.name))
|
|
37
|
+
.map((e) => ({
|
|
38
|
+
name: e.name,
|
|
39
|
+
values: e.values.map((v) => ({ name: v.name })),
|
|
40
|
+
}))
|
|
41
|
+
|
|
42
|
+
return `import { Hono } from 'hono'
|
|
43
|
+
import type { Context, Next } from 'hono'
|
|
44
|
+
import { HTTPException } from 'hono/http-exception'
|
|
45
|
+
import {
|
|
46
|
+
${prefix}FindUnique,
|
|
47
|
+
${prefix}FindUniqueOrThrow,
|
|
48
|
+
${prefix}FindFirst,
|
|
49
|
+
${prefix}FindFirstOrThrow,
|
|
50
|
+
${prefix}FindMany,
|
|
51
|
+
${prefix}FindManyPaginated,
|
|
52
|
+
${prefix}Create,
|
|
53
|
+
${prefix}CreateMany,
|
|
54
|
+
${prefix}CreateManyAndReturn,
|
|
55
|
+
${prefix}Update,
|
|
56
|
+
${prefix}UpdateMany,
|
|
57
|
+
${prefix}UpdateManyAndReturn,
|
|
58
|
+
${prefix}Upsert,
|
|
59
|
+
${prefix}Delete,
|
|
60
|
+
${prefix}DeleteMany,
|
|
61
|
+
${prefix}Aggregate,
|
|
62
|
+
${prefix}Count,
|
|
63
|
+
${prefix}GroupBy,
|
|
64
|
+
} from './${modelName}Handlers'
|
|
65
|
+
import type { RouteConfig, HonoHookHandler } from '../routeConfig.target'
|
|
66
|
+
import { parseQueryParams } from '../parseQueryParams'
|
|
67
|
+
import { sanitizeKeys } from '../misc'
|
|
68
|
+
import { buildModelOpenApi } from '../buildModelOpenApi'
|
|
69
|
+
import { mapError, transformResult, HttpError } from '../operationRuntime'
|
|
70
|
+
|
|
71
|
+
${generateRouteConfigType(modelName, 'HonoHookHandler', guardShapesImport)}
|
|
72
|
+
type HonoVariables = {
|
|
73
|
+
prisma: any
|
|
74
|
+
postgres?: any
|
|
75
|
+
sqlite?: any
|
|
76
|
+
parsedQuery?: Record<string, unknown>
|
|
77
|
+
body?: unknown
|
|
78
|
+
routeConfig?: ${modelName}RouteConfig
|
|
79
|
+
guardShape?: Record<string, unknown>
|
|
80
|
+
guardCaller?: string
|
|
81
|
+
resultData?: unknown
|
|
82
|
+
resultStatus?: number
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
type HonoEnv = { Variables: HonoVariables }
|
|
86
|
+
|
|
87
|
+
const _env = typeof process !== 'undefined' && process.env ? process.env : {} as Record<string, string | undefined>
|
|
88
|
+
|
|
89
|
+
const MODEL_FIELDS = ${JSON.stringify(fieldsMeta, null, 2)} as const
|
|
90
|
+
|
|
91
|
+
const MODEL_ENUMS = ${JSON.stringify(enumsMeta, null, 2)} as const
|
|
92
|
+
|
|
93
|
+
const defaultOpConfig = {
|
|
94
|
+
before: [] as HonoHookHandler[],
|
|
95
|
+
after: [] as HonoHookHandler[],
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function normalizePrefix(p: string): string {
|
|
99
|
+
if (!p) return ''
|
|
100
|
+
let result = p
|
|
101
|
+
if (!result.startsWith('/')) result = '/' + result
|
|
102
|
+
while (result.length > 1 && result.endsWith('/')) result = result.slice(0, -1)
|
|
103
|
+
if (result === '/') return ''
|
|
104
|
+
return result
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
async function safeParseBody(c: Context<HonoEnv>): Promise<unknown> {
|
|
108
|
+
try {
|
|
109
|
+
return await c.req.json()
|
|
110
|
+
} catch {
|
|
111
|
+
throw new HttpError(400, 'Invalid JSON in request body')
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export function ${routerFunctionName}<TCtx = unknown>(
|
|
116
|
+
config: ${modelName}RouteConfig<TCtx> = {},
|
|
117
|
+
): Hono<HonoEnv> {
|
|
118
|
+
const app = new Hono<HonoEnv>()
|
|
119
|
+
|
|
120
|
+
const customPrefix = normalizePrefix(config.customUrlPrefix || '')
|
|
121
|
+
const modelPrefix = config.addModelPrefix !== false ? '/${modelNameLower}' : ''
|
|
122
|
+
const basePath = customPrefix + modelPrefix
|
|
123
|
+
|
|
124
|
+
const openApiDisabled = config.disableOpenApi === true
|
|
125
|
+
|| (config.disableOpenApi !== false && (
|
|
126
|
+
_env.DISABLE_OPENAPI === 'true'
|
|
127
|
+
|| _env.NODE_ENV === 'production'
|
|
128
|
+
))
|
|
129
|
+
|
|
130
|
+
const postReadsEnabled = !config.disablePostReads
|
|
131
|
+
|
|
132
|
+
app.onError((err, c) => {
|
|
133
|
+
if (err instanceof HTTPException) {
|
|
134
|
+
return c.json({ message: err.message }, err.status)
|
|
135
|
+
}
|
|
136
|
+
const httpError = mapError(err)
|
|
137
|
+
return c.json({ message: httpError.message }, httpError.status as any)
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
const parseQueryMw = async (c: Context<HonoEnv>, next: Next): Promise<void> => {
|
|
141
|
+
const raw = c.req.query()
|
|
142
|
+
if (raw && Object.keys(raw).length > 0) {
|
|
143
|
+
c.set(
|
|
144
|
+
'parsedQuery',
|
|
145
|
+
parseQueryParams(raw as Record<string, unknown>) as Record<string, unknown>,
|
|
146
|
+
)
|
|
147
|
+
}
|
|
148
|
+
await next()
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const parseBodyAsQueryMw = async (c: Context<HonoEnv>, next: Next): Promise<void> => {
|
|
152
|
+
const body = await safeParseBody(c)
|
|
153
|
+
if (!body || typeof body !== 'object' || Array.isArray(body)) {
|
|
154
|
+
throw new HttpError(400, 'Request body must be a JSON object')
|
|
155
|
+
}
|
|
156
|
+
c.set('parsedQuery', sanitizeKeys(body as Record<string, unknown>))
|
|
157
|
+
await next()
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const parseBodyMw = async (c: Context<HonoEnv>, next: Next): Promise<void> => {
|
|
161
|
+
const body = await safeParseBody(c)
|
|
162
|
+
c.set('body', body)
|
|
163
|
+
await next()
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const setContextMw = (opConfig: any) => async (c: Context<HonoEnv>, next: Next): Promise<void> => {
|
|
167
|
+
c.set('routeConfig', config as ${modelName}RouteConfig)
|
|
168
|
+
if (opConfig.shape) {
|
|
169
|
+
c.set('guardShape', opConfig.shape)
|
|
170
|
+
const headerName = config.guard?.variantHeader || 'x-api-variant'
|
|
171
|
+
const caller = config.guard?.resolveVariant?.(c as any)
|
|
172
|
+
?? c.req.header(headerName)
|
|
173
|
+
?? undefined
|
|
174
|
+
if (caller) {
|
|
175
|
+
c.set('guardCaller', caller)
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
await next()
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const sendResultMw = async (c: Context<HonoEnv>, _next: Next): Promise<Response> => {
|
|
182
|
+
const data = c.get('resultData')
|
|
183
|
+
const status = c.get('resultStatus') ?? 200
|
|
184
|
+
if (data === undefined) {
|
|
185
|
+
return c.json({ message: 'No data set by handler' }, 500)
|
|
186
|
+
}
|
|
187
|
+
return c.json(transformResult(data) as any, status as any)
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const wrap = (fn: (c: Context<HonoEnv>) => Promise<void>) =>
|
|
191
|
+
async (c: Context<HonoEnv>, next: Next): Promise<void> => {
|
|
192
|
+
await fn(c)
|
|
193
|
+
await next()
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if (!openApiDisabled) {
|
|
197
|
+
const openapiJsonPath = basePath ? \`\${basePath}/openapi.json\` : '/openapi.json'
|
|
198
|
+
const openapiYamlPath = basePath ? \`\${basePath}/openapi.yaml\` : '/openapi.yaml'
|
|
199
|
+
|
|
200
|
+
app.get(openapiJsonPath, (c) => {
|
|
201
|
+
const spec = buildModelOpenApi(
|
|
202
|
+
'${modelName}',
|
|
203
|
+
MODEL_FIELDS as any,
|
|
204
|
+
MODEL_ENUMS as any,
|
|
205
|
+
config,
|
|
206
|
+
{ format: 'json' },
|
|
207
|
+
)
|
|
208
|
+
return c.json(spec as any)
|
|
209
|
+
})
|
|
210
|
+
|
|
211
|
+
app.get(openapiYamlPath, (c) => {
|
|
212
|
+
const yaml = buildModelOpenApi(
|
|
213
|
+
'${modelName}',
|
|
214
|
+
MODEL_FIELDS as any,
|
|
215
|
+
MODEL_ENUMS as any,
|
|
216
|
+
config,
|
|
217
|
+
{ format: 'yaml' },
|
|
218
|
+
) as string
|
|
219
|
+
return c.body(yaml, 200, { 'Content-Type': 'application/yaml' })
|
|
220
|
+
})
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
if (config.enableAll || config.findFirst) {
|
|
224
|
+
const opConfig = config.findFirst || defaultOpConfig
|
|
225
|
+
const { before = [], after = [] } = opConfig
|
|
226
|
+
const path = basePath ? \`\${basePath}/first\` : '/first'
|
|
227
|
+
app.get(path, parseQueryMw, setContextMw(opConfig), ...before, wrap(${prefix}FindFirst), ...after, sendResultMw)
|
|
228
|
+
if (postReadsEnabled) {
|
|
229
|
+
app.post(path, parseBodyAsQueryMw, setContextMw(opConfig), ...before, wrap(${prefix}FindFirst), ...after, sendResultMw)
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
if (config.enableAll || config.findFirstOrThrow) {
|
|
234
|
+
const opConfig = config.findFirstOrThrow || defaultOpConfig
|
|
235
|
+
const { before = [], after = [] } = opConfig
|
|
236
|
+
const path = basePath ? \`\${basePath}/first/strict\` : '/first/strict'
|
|
237
|
+
app.get(path, parseQueryMw, setContextMw(opConfig), ...before, wrap(${prefix}FindFirstOrThrow), ...after, sendResultMw)
|
|
238
|
+
if (postReadsEnabled) {
|
|
239
|
+
app.post(path, parseBodyAsQueryMw, setContextMw(opConfig), ...before, wrap(${prefix}FindFirstOrThrow), ...after, sendResultMw)
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
if (config.enableAll || config.findManyPaginated) {
|
|
244
|
+
const opConfig = config.findManyPaginated || defaultOpConfig
|
|
245
|
+
const { before = [], after = [] } = opConfig
|
|
246
|
+
const path = basePath ? \`\${basePath}/paginated\` : '/paginated'
|
|
247
|
+
app.get(path, parseQueryMw, setContextMw(opConfig), ...before, wrap(${prefix}FindManyPaginated), ...after, sendResultMw)
|
|
248
|
+
if (postReadsEnabled) {
|
|
249
|
+
app.post(path, parseBodyAsQueryMw, setContextMw(opConfig), ...before, wrap(${prefix}FindManyPaginated), ...after, sendResultMw)
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
if (config.enableAll || config.aggregate) {
|
|
254
|
+
const opConfig = config.aggregate || defaultOpConfig
|
|
255
|
+
const { before = [], after = [] } = opConfig
|
|
256
|
+
const path = basePath ? \`\${basePath}/aggregate\` : '/aggregate'
|
|
257
|
+
app.get(path, parseQueryMw, setContextMw(opConfig), ...before, wrap(${prefix}Aggregate), ...after, sendResultMw)
|
|
258
|
+
if (postReadsEnabled) {
|
|
259
|
+
app.post(path, parseBodyAsQueryMw, setContextMw(opConfig), ...before, wrap(${prefix}Aggregate), ...after, sendResultMw)
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
if (config.enableAll || config.count) {
|
|
264
|
+
const opConfig = config.count || defaultOpConfig
|
|
265
|
+
const { before = [], after = [] } = opConfig
|
|
266
|
+
const path = basePath ? \`\${basePath}/count\` : '/count'
|
|
267
|
+
app.get(path, parseQueryMw, setContextMw(opConfig), ...before, wrap(${prefix}Count), ...after, sendResultMw)
|
|
268
|
+
if (postReadsEnabled) {
|
|
269
|
+
app.post(path, parseBodyAsQueryMw, setContextMw(opConfig), ...before, wrap(${prefix}Count), ...after, sendResultMw)
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
if (config.enableAll || config.groupBy) {
|
|
274
|
+
const opConfig = config.groupBy || defaultOpConfig
|
|
275
|
+
const { before = [], after = [] } = opConfig
|
|
276
|
+
const path = basePath ? \`\${basePath}/groupby\` : '/groupby'
|
|
277
|
+
app.get(path, parseQueryMw, setContextMw(opConfig), ...before, wrap(${prefix}GroupBy), ...after, sendResultMw)
|
|
278
|
+
if (postReadsEnabled) {
|
|
279
|
+
app.post(path, parseBodyAsQueryMw, setContextMw(opConfig), ...before, wrap(${prefix}GroupBy), ...after, sendResultMw)
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
if (config.enableAll || config.findUniqueOrThrow) {
|
|
284
|
+
const opConfig = config.findUniqueOrThrow || defaultOpConfig
|
|
285
|
+
const { before = [], after = [] } = opConfig
|
|
286
|
+
const path = basePath ? \`\${basePath}/unique/strict\` : '/unique/strict'
|
|
287
|
+
app.get(path, parseQueryMw, setContextMw(opConfig), ...before, wrap(${prefix}FindUniqueOrThrow), ...after, sendResultMw)
|
|
288
|
+
if (postReadsEnabled) {
|
|
289
|
+
app.post(path, parseBodyAsQueryMw, setContextMw(opConfig), ...before, wrap(${prefix}FindUniqueOrThrow), ...after, sendResultMw)
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
if (config.enableAll || config.findUnique) {
|
|
294
|
+
const opConfig = config.findUnique || defaultOpConfig
|
|
295
|
+
const { before = [], after = [] } = opConfig
|
|
296
|
+
const path = basePath ? \`\${basePath}/unique\` : '/unique'
|
|
297
|
+
app.get(path, parseQueryMw, setContextMw(opConfig), ...before, wrap(${prefix}FindUnique), ...after, sendResultMw)
|
|
298
|
+
if (postReadsEnabled) {
|
|
299
|
+
app.post(path, parseBodyAsQueryMw, setContextMw(opConfig), ...before, wrap(${prefix}FindUnique), ...after, sendResultMw)
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
if (config.enableAll || config.findMany) {
|
|
304
|
+
const opConfig = config.findMany || defaultOpConfig
|
|
305
|
+
const { before = [], after = [] } = opConfig
|
|
306
|
+
const path = basePath || '/'
|
|
307
|
+
app.get(path, parseQueryMw, setContextMw(opConfig), ...before, wrap(${prefix}FindMany), ...after, sendResultMw)
|
|
308
|
+
if (postReadsEnabled) {
|
|
309
|
+
const postPath = basePath ? \`\${basePath}/read\` : '/read'
|
|
310
|
+
app.post(postPath, parseBodyAsQueryMw, setContextMw(opConfig), ...before, wrap(${prefix}FindMany), ...after, sendResultMw)
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
if (config.enableAll || config.createManyAndReturn) {
|
|
315
|
+
const opConfig = config.createManyAndReturn || defaultOpConfig
|
|
316
|
+
const { before = [], after = [] } = opConfig
|
|
317
|
+
const path = basePath ? \`\${basePath}/many/return\` : '/many/return'
|
|
318
|
+
app.post(path, parseBodyMw, setContextMw(opConfig), ...before, wrap(${prefix}CreateManyAndReturn), ...after, sendResultMw)
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
if (config.enableAll || config.createMany) {
|
|
322
|
+
const opConfig = config.createMany || defaultOpConfig
|
|
323
|
+
const { before = [], after = [] } = opConfig
|
|
324
|
+
const path = basePath ? \`\${basePath}/many\` : '/many'
|
|
325
|
+
app.post(path, parseBodyMw, setContextMw(opConfig), ...before, wrap(${prefix}CreateMany), ...after, sendResultMw)
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
if (config.enableAll || config.create) {
|
|
329
|
+
const opConfig = config.create || defaultOpConfig
|
|
330
|
+
const { before = [], after = [] } = opConfig
|
|
331
|
+
const path = basePath || '/'
|
|
332
|
+
app.post(path, parseBodyMw, setContextMw(opConfig), ...before, wrap(${prefix}Create), ...after, sendResultMw)
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
if (config.enableAll || config.updateManyAndReturn) {
|
|
336
|
+
const opConfig = config.updateManyAndReturn || defaultOpConfig
|
|
337
|
+
const { before = [], after = [] } = opConfig
|
|
338
|
+
const path = basePath ? \`\${basePath}/many/return\` : '/many/return'
|
|
339
|
+
app.put(path, parseBodyMw, setContextMw(opConfig), ...before, wrap(${prefix}UpdateManyAndReturn), ...after, sendResultMw)
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
if (config.enableAll || config.updateMany) {
|
|
343
|
+
const opConfig = config.updateMany || defaultOpConfig
|
|
344
|
+
const { before = [], after = [] } = opConfig
|
|
345
|
+
const path = basePath ? \`\${basePath}/many\` : '/many'
|
|
346
|
+
app.put(path, parseBodyMw, setContextMw(opConfig), ...before, wrap(${prefix}UpdateMany), ...after, sendResultMw)
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
if (config.enableAll || config.update) {
|
|
350
|
+
const opConfig = config.update || defaultOpConfig
|
|
351
|
+
const { before = [], after = [] } = opConfig
|
|
352
|
+
const path = basePath || '/'
|
|
353
|
+
app.put(path, parseBodyMw, setContextMw(opConfig), ...before, wrap(${prefix}Update), ...after, sendResultMw)
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
if (config.enableAll || config.upsert) {
|
|
357
|
+
const opConfig = config.upsert || defaultOpConfig
|
|
358
|
+
const { before = [], after = [] } = opConfig
|
|
359
|
+
const path = basePath || '/'
|
|
360
|
+
app.patch(path, parseBodyMw, setContextMw(opConfig), ...before, wrap(${prefix}Upsert), ...after, sendResultMw)
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
if (config.enableAll || config.deleteMany) {
|
|
364
|
+
const opConfig = config.deleteMany || defaultOpConfig
|
|
365
|
+
const { before = [], after = [] } = opConfig
|
|
366
|
+
const path = basePath ? \`\${basePath}/many\` : '/many'
|
|
367
|
+
app.delete(path, parseBodyMw, setContextMw(opConfig), ...before, wrap(${prefix}DeleteMany), ...after, sendResultMw)
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
if (config.enableAll || config.delete) {
|
|
371
|
+
const opConfig = config.delete || defaultOpConfig
|
|
372
|
+
const { before = [], after = [] } = opConfig
|
|
373
|
+
const path = basePath || '/'
|
|
374
|
+
app.delete(path, parseBodyMw, setContextMw(opConfig), ...before, wrap(${prefix}Delete), ...after, sendResultMw)
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
return app
|
|
378
|
+
}
|
|
379
|
+
`
|
|
380
|
+
}
|