prisma-generator-express 1.59.0 → 1.61.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 +150 -14
- package/dist/client/encodeQueryParams.js +19 -23
- package/dist/client/encodeQueryParams.js.map +1 -1
- package/dist/copy/operationDefinitions.d.ts +37 -0
- package/dist/copy/operationDefinitions.js +412 -0
- package/dist/copy/operationDefinitions.js.map +1 -0
- package/dist/generators/generateFastifyHandler.js +10 -41
- package/dist/generators/generateFastifyHandler.js.map +1 -1
- package/dist/generators/generateHonoHandler.js +10 -41
- package/dist/generators/generateHonoHandler.js.map +1 -1
- package/dist/generators/generateModelMetadata.d.ts +8 -0
- package/dist/generators/generateModelMetadata.js +77 -0
- package/dist/generators/generateModelMetadata.js.map +1 -0
- package/dist/generators/generateOperationCore.js +20 -30
- package/dist/generators/generateOperationCore.js.map +1 -1
- package/dist/generators/generateQueryBuilderHelper.js +1 -1
- package/dist/generators/generateQueryBuilderHelper.js.map +1 -1
- package/dist/generators/generateRouteConfigType.js +11 -57
- package/dist/generators/generateRouteConfigType.js.map +1 -1
- package/dist/generators/generateRouter.js +64 -177
- package/dist/generators/generateRouter.js.map +1 -1
- package/dist/generators/generateRouterFastify.js +59 -169
- package/dist/generators/generateRouterFastify.js.map +1 -1
- package/dist/generators/generateRouterHono.js +52 -152
- package/dist/generators/generateRouterHono.js.map +1 -1
- package/dist/generators/generateUnifiedHandler.js +7 -30
- package/dist/generators/generateUnifiedHandler.js.map +1 -1
- package/dist/generators/generateUnifiedScalarUI.js +9 -74
- package/dist/generators/generateUnifiedScalarUI.js.map +1 -1
- package/dist/index.js +11 -0
- package/dist/index.js.map +1 -1
- package/dist/utils/copyFiles.js +7 -0
- package/dist/utils/copyFiles.js.map +1 -1
- package/dist/utils/writeFileSafely.js +5 -12
- package/dist/utils/writeFileSafely.js.map +1 -1
- package/package.json +12 -2
- package/src/client/encodeQueryParams.ts +23 -36
- package/src/copy/autoIncludePlanner.ts +14 -23
- package/src/copy/autoIncludePlannerGuarded.ts +477 -0
- package/src/copy/autoIncludeRuntime.ts +269 -261
- package/src/copy/autoIncludeRuntimeGuarded.ts +379 -0
- package/src/copy/buildModelOpenApi.ts +248 -628
- package/src/copy/concurrency.ts +20 -0
- package/src/copy/docsRenderer.ts +63 -333
- package/src/copy/errorMapper.ts +126 -0
- package/src/copy/guardHelpers.ts +56 -0
- package/src/copy/materializedCount.ts +68 -0
- package/src/copy/materializedRouter.ts +33 -29
- package/src/copy/operationDefinitions.ts +359 -35
- package/src/copy/operationRuntime.ts +11 -605
- package/src/copy/pagination.ts +151 -0
- package/src/copy/scalarTypes.ts +2 -0
- package/src/copy/sse.ts +296 -0
- package/src/generators/generateFastifyHandler.ts +13 -47
- package/src/generators/generateHonoHandler.ts +13 -47
- package/src/generators/generateModelMetadata.ts +92 -0
- package/src/generators/generateOperationCore.ts +19 -32
- package/src/generators/generateQueryBuilderHelper.ts +1 -1
- package/src/generators/generateRouteConfigType.ts +9 -60
- package/src/generators/generateRouter.ts +88 -180
- package/src/generators/generateRouterFastify.ts +65 -172
- package/src/generators/generateRouterHono.ts +58 -155
- package/src/generators/generateUnifiedHandler.ts +8 -33
- package/src/generators/generateUnifiedScalarUI.ts +9 -91
- package/src/index.ts +13 -1
- package/src/utils/copyFiles.ts +7 -0
- package/src/utils/writeFileSafely.ts +5 -11
|
@@ -0,0 +1,477 @@
|
|
|
1
|
+
import { isPlainObject } from './misc'
|
|
2
|
+
import type {
|
|
3
|
+
ModelRelationField,
|
|
4
|
+
ModelRelationMap,
|
|
5
|
+
} from './autoIncludePlanner'
|
|
6
|
+
|
|
7
|
+
export type GuardedAutoIncludeStage = {
|
|
8
|
+
relationPath: string
|
|
9
|
+
parentPath: string
|
|
10
|
+
relationName: string
|
|
11
|
+
relationField: ModelRelationField
|
|
12
|
+
stageArgs: Record<string, unknown>
|
|
13
|
+
stageShape: Record<string, unknown>
|
|
14
|
+
depth: number
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export type GuardedAutoIncludePlan = {
|
|
18
|
+
rootArgs: Record<string, unknown>
|
|
19
|
+
rootShape: Record<string, unknown>
|
|
20
|
+
stages: GuardedAutoIncludeStage[]
|
|
21
|
+
internalFieldPaths: string[]
|
|
22
|
+
unsupportedReason?: string
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export type GuardedAutoIncludePlannerInput = {
|
|
26
|
+
rootModelName: string
|
|
27
|
+
models: Record<string, ModelRelationMap>
|
|
28
|
+
effectiveReadBody: Record<string, unknown>
|
|
29
|
+
shape: Record<string, unknown>
|
|
30
|
+
maxDepth?: number
|
|
31
|
+
maxStages?: number
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export const DEFAULT_GUARDED_MAX_DEPTH = 3
|
|
35
|
+
export const DEFAULT_GUARDED_MAX_STAGES = 20
|
|
36
|
+
|
|
37
|
+
const ALLOWED_TO_ONE_STAGE_ARGS = new Set(['select', 'include', 'omit'])
|
|
38
|
+
const ALLOWED_TO_MANY_STAGE_ARGS = new Set([
|
|
39
|
+
'select', 'include', 'omit', 'where', 'orderBy', 'take', 'skip', 'cursor', 'distinct',
|
|
40
|
+
])
|
|
41
|
+
|
|
42
|
+
function containsCountProjection(value: unknown): boolean {
|
|
43
|
+
if (!isPlainObject(value)) return false
|
|
44
|
+
if ('_count' in value) return true
|
|
45
|
+
for (const child of Object.values(value)) {
|
|
46
|
+
if (containsCountProjection(child)) return true
|
|
47
|
+
}
|
|
48
|
+
return false
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function projectionHasCount(node: Record<string, unknown>): boolean {
|
|
52
|
+
return containsCountProjection(node.select) || containsCountProjection(node.include)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function rootHasRelationRefInFilter(
|
|
56
|
+
args: Record<string, unknown>,
|
|
57
|
+
model: ModelRelationMap,
|
|
58
|
+
): boolean {
|
|
59
|
+
const walkWhere = (obj: unknown): boolean => {
|
|
60
|
+
if (!isPlainObject(obj)) return false
|
|
61
|
+
for (const key of Object.keys(obj)) {
|
|
62
|
+
if (model.relations[key]) return true
|
|
63
|
+
if (key === 'AND' || key === 'OR' || key === 'NOT') {
|
|
64
|
+
const sub = obj[key]
|
|
65
|
+
if (Array.isArray(sub)) {
|
|
66
|
+
for (const item of sub) if (walkWhere(item)) return true
|
|
67
|
+
} else if (walkWhere(sub)) {
|
|
68
|
+
return true
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return false
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const orderByHas = (v: unknown): boolean => {
|
|
76
|
+
const list = Array.isArray(v) ? v : [v]
|
|
77
|
+
for (const ob of list) {
|
|
78
|
+
if (!isPlainObject(ob)) continue
|
|
79
|
+
for (const key of Object.keys(ob)) if (model.relations[key]) return true
|
|
80
|
+
}
|
|
81
|
+
return false
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (walkWhere(args.where)) return true
|
|
85
|
+
if (args.orderBy && orderByHas(args.orderBy)) return true
|
|
86
|
+
if (isPlainObject(args.cursor)) {
|
|
87
|
+
for (const key of Object.keys(args.cursor)) {
|
|
88
|
+
if (model.relations[key]) return true
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return false
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function stageHasBlockedRelationRef(
|
|
95
|
+
args: Record<string, unknown>,
|
|
96
|
+
model: ModelRelationMap,
|
|
97
|
+
): boolean {
|
|
98
|
+
const orderByHas = (v: unknown): boolean => {
|
|
99
|
+
const list = Array.isArray(v) ? v : [v]
|
|
100
|
+
for (const ob of list) {
|
|
101
|
+
if (!isPlainObject(ob)) continue
|
|
102
|
+
for (const key of Object.keys(ob)) if (model.relations[key]) return true
|
|
103
|
+
}
|
|
104
|
+
return false
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (args.orderBy && orderByHas(args.orderBy)) return true
|
|
108
|
+
if (isPlainObject(args.cursor)) {
|
|
109
|
+
for (const key of Object.keys(args.cursor)) {
|
|
110
|
+
if (model.relations[key]) return true
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return false
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function whereMentionsField(where: unknown, fieldName: string): boolean {
|
|
117
|
+
if (!isPlainObject(where)) return false
|
|
118
|
+
for (const key of Object.keys(where)) {
|
|
119
|
+
if (key === fieldName) return true
|
|
120
|
+
if (key === 'AND' || key === 'OR' || key === 'NOT') {
|
|
121
|
+
const sub = where[key]
|
|
122
|
+
if (Array.isArray(sub)) {
|
|
123
|
+
for (const item of sub) if (whereMentionsField(item, fieldName)) return true
|
|
124
|
+
} else if (whereMentionsField(sub, fieldName)) {
|
|
125
|
+
return true
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return false
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function rejectIncludeOrOmit(node: Record<string, unknown>, label: string): string | null {
|
|
133
|
+
if (isPlainObject(node.include)) {
|
|
134
|
+
return label + ' uses include (guarded MVP supports select only)'
|
|
135
|
+
}
|
|
136
|
+
if (isPlainObject(node.omit)) {
|
|
137
|
+
return label + ' uses omit (guarded MVP supports select only)'
|
|
138
|
+
}
|
|
139
|
+
return null
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
type WalkContext = {
|
|
143
|
+
models: Record<string, ModelRelationMap>
|
|
144
|
+
stages: GuardedAutoIncludeStage[]
|
|
145
|
+
internalFieldPaths: string[]
|
|
146
|
+
maxDepth: number
|
|
147
|
+
maxStages: number
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
type WalkResult = {
|
|
151
|
+
unsupportedReason?: string
|
|
152
|
+
bodyProjectionAfterStrip?: Record<string, unknown>
|
|
153
|
+
shapeProjectionAfterStrip?: Record<string, unknown>
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function isRejectableWhere(value: unknown): boolean {
|
|
157
|
+
return value !== undefined && !isPlainObject(value)
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function walkRoot(
|
|
161
|
+
ctx: WalkContext,
|
|
162
|
+
modelName: string,
|
|
163
|
+
parentBody: Record<string, unknown>,
|
|
164
|
+
parentShape: Record<string, unknown>,
|
|
165
|
+
): WalkResult {
|
|
166
|
+
if (ctx.stages.length >= ctx.maxStages) {
|
|
167
|
+
return { unsupportedReason: 'stages reached maxStages=' + ctx.maxStages }
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const model = ctx.models[modelName]
|
|
171
|
+
if (!model) {
|
|
172
|
+
return { unsupportedReason: 'model ' + modelName + ' not in relation metadata' }
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const bodyIncludeOmit = rejectIncludeOrOmit(parentBody, 'root body')
|
|
176
|
+
if (bodyIncludeOmit) return { unsupportedReason: bodyIncludeOmit }
|
|
177
|
+
const shapeIncludeOmit = rejectIncludeOrOmit(parentShape, 'root shape')
|
|
178
|
+
if (shapeIncludeOmit) return { unsupportedReason: shapeIncludeOmit }
|
|
179
|
+
|
|
180
|
+
if (projectionHasCount(parentBody)) {
|
|
181
|
+
return { unsupportedReason: '_count in root body not supported in guarded MVP' }
|
|
182
|
+
}
|
|
183
|
+
if (projectionHasCount(parentShape)) {
|
|
184
|
+
return { unsupportedReason: '_count in root shape not supported in guarded MVP' }
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (isRejectableWhere(parentBody.where)) {
|
|
188
|
+
return { unsupportedReason: 'root body where must be a plain object' }
|
|
189
|
+
}
|
|
190
|
+
if (isRejectableWhere(parentShape.where)) {
|
|
191
|
+
return { unsupportedReason: 'root shape where must be a plain object' }
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (rootHasRelationRefInFilter(parentBody, model)) {
|
|
195
|
+
return { unsupportedReason: 'root body where/orderBy/cursor relation ref not supported' }
|
|
196
|
+
}
|
|
197
|
+
if (rootHasRelationRefInFilter(parentShape, model)) {
|
|
198
|
+
return { unsupportedReason: 'root shape where/orderBy/cursor relation ref not supported' }
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const bodySelect = parentBody.select
|
|
202
|
+
const shapeSelect = parentShape.select
|
|
203
|
+
|
|
204
|
+
const bodyProjection = isPlainObject(bodySelect) ? bodySelect : null
|
|
205
|
+
const shapeProjection = isPlainObject(shapeSelect) ? shapeSelect : null
|
|
206
|
+
|
|
207
|
+
if (!bodyProjection || !shapeProjection) {
|
|
208
|
+
return {}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const updatedBodyProjection: Record<string, unknown> = {}
|
|
212
|
+
const updatedShapeProjection: Record<string, unknown> = {}
|
|
213
|
+
|
|
214
|
+
const relationBranches: Array<{
|
|
215
|
+
name: string
|
|
216
|
+
bodyValue: unknown
|
|
217
|
+
shapeValue: unknown
|
|
218
|
+
}> = []
|
|
219
|
+
|
|
220
|
+
for (const [key, value] of Object.entries(bodyProjection)) {
|
|
221
|
+
if (model.relations[key]) {
|
|
222
|
+
if (value === false || value === null || value === undefined) {
|
|
223
|
+
updatedBodyProjection[key] = value
|
|
224
|
+
if (key in shapeProjection) {
|
|
225
|
+
updatedShapeProjection[key] = shapeProjection[key]
|
|
226
|
+
}
|
|
227
|
+
continue
|
|
228
|
+
}
|
|
229
|
+
const shapeBranch = shapeProjection[key]
|
|
230
|
+
if (shapeBranch === undefined) {
|
|
231
|
+
return {
|
|
232
|
+
unsupportedReason: 'body projects relation "' + key + '" not present in guard shape at root',
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
relationBranches.push({ name: key, bodyValue: value, shapeValue: shapeBranch })
|
|
236
|
+
} else {
|
|
237
|
+
updatedBodyProjection[key] = value
|
|
238
|
+
if (key in shapeProjection) {
|
|
239
|
+
updatedShapeProjection[key] = shapeProjection[key]
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
for (const branch of relationBranches) {
|
|
245
|
+
if (ctx.stages.length >= ctx.maxStages) {
|
|
246
|
+
return { unsupportedReason: 'stages reached maxStages=' + ctx.maxStages }
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const relation = model.relations[branch.name]
|
|
250
|
+
|
|
251
|
+
if (relation.direction === 'implicitM2M') {
|
|
252
|
+
return { unsupportedReason: 'implicit many-to-many not supported in guarded MVP' }
|
|
253
|
+
}
|
|
254
|
+
if (relation.parentLinkFields.length === 0 || relation.childLinkFields.length === 0) {
|
|
255
|
+
return { unsupportedReason: 'ambiguous relation metadata for ' + relation.name }
|
|
256
|
+
}
|
|
257
|
+
if (relation.parentLinkFields.length !== relation.childLinkFields.length) {
|
|
258
|
+
return { unsupportedReason: 'mismatched link field counts for ' + relation.name }
|
|
259
|
+
}
|
|
260
|
+
if (relation.parentLinkFields.length !== 1) {
|
|
261
|
+
return {
|
|
262
|
+
unsupportedReason: 'composite link fields not supported for guarded stage ' + relation.name,
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
if (!ctx.models[relation.type]) {
|
|
266
|
+
return {
|
|
267
|
+
unsupportedReason: 'target model ' + relation.type + ' not in relation metadata for ' + branch.name,
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
if (branch.bodyValue !== true && !isPlainObject(branch.bodyValue)) {
|
|
272
|
+
return { unsupportedReason: 'invalid relation projection body for ' + branch.name }
|
|
273
|
+
}
|
|
274
|
+
if (branch.shapeValue !== true && !isPlainObject(branch.shapeValue)) {
|
|
275
|
+
return { unsupportedReason: 'invalid relation projection shape for ' + branch.name }
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
const parentKey = relation.parentLinkFields[0]
|
|
279
|
+
const childKey = relation.childLinkFields[0]
|
|
280
|
+
|
|
281
|
+
if (updatedBodyProjection[parentKey] !== true) {
|
|
282
|
+
updatedBodyProjection[parentKey] = true
|
|
283
|
+
ctx.internalFieldPaths.push(parentKey)
|
|
284
|
+
}
|
|
285
|
+
if (updatedShapeProjection[parentKey] !== true) {
|
|
286
|
+
updatedShapeProjection[parentKey] = true
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
const relationBodyArgs: Record<string, unknown> = branch.bodyValue === true ? {} : branch.bodyValue
|
|
290
|
+
const relationShapeArgs: Record<string, unknown> = branch.shapeValue === true ? {} : branch.shapeValue
|
|
291
|
+
|
|
292
|
+
const allowedArgs = relation.isList ? ALLOWED_TO_MANY_STAGE_ARGS : ALLOWED_TO_ONE_STAGE_ARGS
|
|
293
|
+
for (const key of Object.keys(relationBodyArgs)) {
|
|
294
|
+
if (!allowedArgs.has(key)) {
|
|
295
|
+
return {
|
|
296
|
+
unsupportedReason: 'unsupported body arg "' + key + '" for ' +
|
|
297
|
+
(relation.isList ? 'to-many' : 'to-one') + ' relation ' + relation.name,
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
for (const key of Object.keys(relationShapeArgs)) {
|
|
302
|
+
if (!allowedArgs.has(key)) {
|
|
303
|
+
return {
|
|
304
|
+
unsupportedReason: 'unsupported shape arg "' + key + '" for ' +
|
|
305
|
+
(relation.isList ? 'to-many' : 'to-one') + ' relation ' + relation.name,
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
if (isPlainObject(relationBodyArgs.include)) {
|
|
311
|
+
return { unsupportedReason: 'stage body uses include for ' + relation.name + ' (guarded MVP supports select only)' }
|
|
312
|
+
}
|
|
313
|
+
if (isPlainObject(relationBodyArgs.omit)) {
|
|
314
|
+
return { unsupportedReason: 'stage body uses omit for ' + relation.name + ' (guarded MVP supports select only)' }
|
|
315
|
+
}
|
|
316
|
+
if (isPlainObject(relationShapeArgs.include)) {
|
|
317
|
+
return { unsupportedReason: 'stage shape uses include for ' + relation.name + ' (guarded MVP supports select only)' }
|
|
318
|
+
}
|
|
319
|
+
if (isPlainObject(relationShapeArgs.omit)) {
|
|
320
|
+
return { unsupportedReason: 'stage shape uses omit for ' + relation.name + ' (guarded MVP supports select only)' }
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
if (projectionHasCount(relationBodyArgs)) {
|
|
324
|
+
return { unsupportedReason: '_count in stage body for ' + relation.name + ' not supported in guarded MVP' }
|
|
325
|
+
}
|
|
326
|
+
if (projectionHasCount(relationShapeArgs)) {
|
|
327
|
+
return { unsupportedReason: '_count in stage shape for ' + relation.name + ' not supported in guarded MVP' }
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
if (isRejectableWhere(relationBodyArgs.where)) {
|
|
331
|
+
return { unsupportedReason: 'stage body where must be a plain object for ' + relation.name }
|
|
332
|
+
}
|
|
333
|
+
if (isRejectableWhere(relationShapeArgs.where)) {
|
|
334
|
+
return { unsupportedReason: 'stage shape where must be a plain object for ' + relation.name }
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
if (stageHasBlockedRelationRef(relationBodyArgs, ctx.models[relation.type])) {
|
|
338
|
+
return { unsupportedReason: 'stage body orderBy/cursor relation ref not supported for ' + relation.name }
|
|
339
|
+
}
|
|
340
|
+
if (stageHasBlockedRelationRef(relationShapeArgs, ctx.models[relation.type])) {
|
|
341
|
+
return { unsupportedReason: 'stage shape orderBy/cursor relation ref not supported for ' + relation.name }
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
if (whereMentionsField(relationBodyArgs.where, childKey)) {
|
|
345
|
+
return {
|
|
346
|
+
unsupportedReason: 'FK collision: stage body where for ' + relation.name +
|
|
347
|
+
' already mentions child link field "' + childKey + '"',
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
if (whereMentionsField(relationShapeArgs.where, childKey)) {
|
|
351
|
+
return {
|
|
352
|
+
unsupportedReason: 'FK collision: stage shape where for ' + relation.name +
|
|
353
|
+
' already mentions child link field "' + childKey + '"',
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
const stageArgs: Record<string, unknown> = { ...relationBodyArgs }
|
|
358
|
+
const stageShape: Record<string, unknown> = { ...relationShapeArgs }
|
|
359
|
+
|
|
360
|
+
injectChildFkIntoStageProjection(
|
|
361
|
+
stageArgs,
|
|
362
|
+
stageShape,
|
|
363
|
+
childKey,
|
|
364
|
+
branch.name,
|
|
365
|
+
ctx.internalFieldPaths,
|
|
366
|
+
)
|
|
367
|
+
|
|
368
|
+
injectChildFkPermissionIntoStageShape(stageShape, childKey)
|
|
369
|
+
|
|
370
|
+
ctx.stages.push({
|
|
371
|
+
relationPath: branch.name,
|
|
372
|
+
parentPath: '',
|
|
373
|
+
relationName: branch.name,
|
|
374
|
+
relationField: relation,
|
|
375
|
+
stageArgs,
|
|
376
|
+
stageShape,
|
|
377
|
+
depth: 1,
|
|
378
|
+
})
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
return {
|
|
382
|
+
bodyProjectionAfterStrip: updatedBodyProjection,
|
|
383
|
+
shapeProjectionAfterStrip: updatedShapeProjection,
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
function injectChildFkIntoStageProjection(
|
|
388
|
+
stageArgs: Record<string, unknown>,
|
|
389
|
+
stageShape: Record<string, unknown>,
|
|
390
|
+
childKey: string,
|
|
391
|
+
relationPath: string,
|
|
392
|
+
internalFieldPaths: string[],
|
|
393
|
+
): void {
|
|
394
|
+
const bodySelect = stageArgs.select
|
|
395
|
+
const shapeSelect = stageShape.select
|
|
396
|
+
|
|
397
|
+
if (isPlainObject(bodySelect)) {
|
|
398
|
+
if ((bodySelect as Record<string, unknown>)[childKey] !== true) {
|
|
399
|
+
const next: Record<string, unknown> = { ...bodySelect, [childKey]: true }
|
|
400
|
+
stageArgs.select = next
|
|
401
|
+
internalFieldPaths.push(relationPath + '.' + childKey)
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
if (isPlainObject(shapeSelect)) {
|
|
406
|
+
if ((shapeSelect as Record<string, unknown>)[childKey] !== true) {
|
|
407
|
+
const next: Record<string, unknown> = { ...shapeSelect, [childKey]: true }
|
|
408
|
+
stageShape.select = next
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
function injectChildFkPermissionIntoStageShape(
|
|
414
|
+
stageShape: Record<string, unknown>,
|
|
415
|
+
childKey: string,
|
|
416
|
+
): void {
|
|
417
|
+
const existingWhere = stageShape.where
|
|
418
|
+
const nextWhere: Record<string, unknown> = isPlainObject(existingWhere)
|
|
419
|
+
? { ...existingWhere }
|
|
420
|
+
: {}
|
|
421
|
+
nextWhere[childKey] = { in: true }
|
|
422
|
+
stageShape.where = nextWhere
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
export function planGuardedAutoInclude(
|
|
426
|
+
input: GuardedAutoIncludePlannerInput,
|
|
427
|
+
): GuardedAutoIncludePlan {
|
|
428
|
+
const maxDepth = input.maxDepth ?? DEFAULT_GUARDED_MAX_DEPTH
|
|
429
|
+
const maxStages = input.maxStages ?? DEFAULT_GUARDED_MAX_STAGES
|
|
430
|
+
|
|
431
|
+
const ctx: WalkContext = {
|
|
432
|
+
models: input.models,
|
|
433
|
+
stages: [],
|
|
434
|
+
internalFieldPaths: [],
|
|
435
|
+
maxDepth,
|
|
436
|
+
maxStages,
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
const result = walkRoot(
|
|
440
|
+
ctx,
|
|
441
|
+
input.rootModelName,
|
|
442
|
+
input.effectiveReadBody,
|
|
443
|
+
input.shape,
|
|
444
|
+
)
|
|
445
|
+
|
|
446
|
+
if (result.unsupportedReason) {
|
|
447
|
+
return {
|
|
448
|
+
rootArgs: input.effectiveReadBody,
|
|
449
|
+
rootShape: input.shape,
|
|
450
|
+
stages: [],
|
|
451
|
+
internalFieldPaths: [],
|
|
452
|
+
unsupportedReason: 'guarded auto-progressive fallback: ' + result.unsupportedReason,
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
const rootArgs: Record<string, unknown> = { ...input.effectiveReadBody }
|
|
457
|
+
const rootShape: Record<string, unknown> = { ...input.shape }
|
|
458
|
+
|
|
459
|
+
if (result.bodyProjectionAfterStrip) {
|
|
460
|
+
rootArgs.select = result.bodyProjectionAfterStrip
|
|
461
|
+
delete rootArgs.include
|
|
462
|
+
delete rootArgs.omit
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
if (result.shapeProjectionAfterStrip) {
|
|
466
|
+
rootShape.select = result.shapeProjectionAfterStrip
|
|
467
|
+
delete rootShape.include
|
|
468
|
+
delete rootShape.omit
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
return {
|
|
472
|
+
rootArgs,
|
|
473
|
+
rootShape,
|
|
474
|
+
stages: ctx.stages,
|
|
475
|
+
internalFieldPaths: ctx.internalFieldPaths,
|
|
476
|
+
}
|
|
477
|
+
}
|