prisma-generator-express 1.66.0 → 1.67.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 +37 -3476
- package/dist/client/encodeQueryParams.js +3 -1
- package/dist/client/encodeQueryParams.js.map +1 -1
- package/dist/copy/misc.d.ts +1 -0
- package/dist/copy/misc.js +14 -0
- package/dist/copy/misc.js.map +1 -1
- package/dist/copy/operationDefinitions.js +69 -6
- package/dist/copy/operationDefinitions.js.map +1 -1
- package/dist/generators/generateFastifyHandler.d.ts +2 -2
- package/dist/generators/generateFastifyHandler.js +15 -5
- package/dist/generators/generateFastifyHandler.js.map +1 -1
- package/dist/generators/generateHonoHandler.d.ts +2 -2
- package/dist/generators/generateHonoHandler.js +12 -5
- package/dist/generators/generateHonoHandler.js.map +1 -1
- package/dist/generators/generateImportPrismaStatement.d.ts +1 -0
- package/dist/generators/generateImportPrismaStatement.js +6 -0
- package/dist/generators/generateImportPrismaStatement.js.map +1 -1
- package/dist/generators/generateModelMetadata.js +20 -10
- package/dist/generators/generateModelMetadata.js.map +1 -1
- package/dist/generators/generateOperationCore.d.ts +3 -3
- package/dist/generators/generateOperationCore.js +51 -16
- package/dist/generators/generateOperationCore.js.map +1 -1
- package/dist/generators/generateRelationMeta.js +2 -2
- package/dist/generators/generateRelationMeta.js.map +1 -1
- package/dist/generators/generateRouteConfigType.d.ts +2 -2
- package/dist/generators/generateRouteConfigType.js +29 -11
- package/dist/generators/generateRouteConfigType.js.map +1 -1
- package/dist/generators/generateRouter.d.ts +7 -4
- package/dist/generators/generateRouter.js +107 -14
- package/dist/generators/generateRouter.js.map +1 -1
- package/dist/generators/generateRouterFastify.d.ts +7 -4
- package/dist/generators/generateRouterFastify.js +56 -28
- package/dist/generators/generateRouterFastify.js.map +1 -1
- package/dist/generators/generateRouterHono.d.ts +7 -4
- package/dist/generators/generateRouterHono.js +36 -23
- package/dist/generators/generateRouterHono.js.map +1 -1
- package/dist/generators/generateUnifiedDocs.d.ts +1 -1
- package/dist/generators/generateUnifiedDocs.js +15 -8
- package/dist/generators/generateUnifiedDocs.js.map +1 -1
- package/dist/generators/generateUnifiedHandler.d.ts +2 -2
- package/dist/generators/generateUnifiedHandler.js +11 -3
- package/dist/generators/generateUnifiedHandler.js.map +1 -1
- package/dist/guardOptions.js +12 -7
- package/dist/guardOptions.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +22 -1
- package/dist/index.js.map +1 -1
- package/dist/utils/copyFiles.js +22 -6
- package/dist/utils/copyFiles.js.map +1 -1
- package/dist/utils/importExt.d.ts +1 -1
- package/dist/utils/pathCasing.d.ts +4 -0
- package/dist/utils/pathCasing.js +40 -0
- package/dist/utils/pathCasing.js.map +1 -0
- package/dist/utils/resolveImportStyle.js +13 -5
- package/dist/utils/resolveImportStyle.js.map +1 -1
- package/dist/utils/writeFileSafely.js.map +1 -1
- package/package.json +5 -3
- package/src/bin.ts +1 -1
- package/src/client/encodeQueryParams.ts +39 -35
- package/src/constants.ts +1 -1
- package/src/copy/autoIncludePlanner.ts +93 -27
- package/src/copy/autoIncludePlannerGuarded.ts +171 -48
- package/src/copy/autoIncludeRuntimeGuarded.ts +25 -7
- package/src/copy/buildModelOpenApi.ts +153 -50
- package/src/copy/concurrency.ts +10 -8
- package/src/copy/errorMapper.ts +68 -19
- package/src/copy/guardHelpers.ts +30 -9
- package/src/copy/materializedCount.ts +17 -5
- package/src/copy/misc.ts +88 -68
- package/src/copy/ndjson.ts +72 -0
- package/src/copy/operationDefinitions.ts +99 -21
- package/src/copy/operationRuntime.ts +100 -12
- package/src/copy/pagination.ts +25 -11
- package/src/copy/parseQueryParams.ts +47 -48
- package/src/copy/projectionDefaults.ts +6 -4
- package/src/copy/routeConfig.express.ts +16 -4
- package/src/copy/routeConfig.fastify.ts +16 -2
- package/src/copy/routeConfig.hono.ts +12 -9
- package/src/copy/routeConfig.ts +174 -39
- package/src/copy/scalarTypes.ts +7 -2
- package/src/copy/sse.ts +72 -19
- package/src/generators/generateFastifyHandler.ts +24 -13
- package/src/generators/generateHonoHandler.ts +21 -13
- package/src/generators/generateImportPrismaStatement.ts +93 -81
- package/src/generators/generateModelMetadata.ts +24 -12
- package/src/generators/generateOperationCore.ts +81 -33
- package/src/generators/generateRelationMeta.ts +23 -11
- package/src/generators/generateRouteConfigType.ts +39 -13
- package/src/generators/generateRouter.ts +114 -17
- package/src/generators/generateRouterFastify.ts +67 -31
- package/src/generators/generateRouterHono.ts +60 -30
- package/src/generators/generateUnifiedDocs.ts +29 -8
- package/src/generators/generateUnifiedHandler.ts +16 -8
- package/src/generators/generateUnifiedScalarUI.ts +1 -1
- package/src/guardOptions.ts +105 -92
- package/src/index.ts +46 -4
- package/src/utils/copyFiles.ts +199 -168
- package/src/utils/importExt.ts +2 -2
- package/src/utils/pathCasing.ts +42 -0
- package/src/utils/resolveImportStyle.ts +37 -15
- package/src/utils/strings.ts +2 -2
- package/src/utils/writeFileSafely.ts +6 -2
|
@@ -90,7 +90,10 @@ function guarded(
|
|
|
90
90
|
return d.guard(shape, caller) as GuardedDelegate
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
-
function createClientGoneChecker(
|
|
93
|
+
function createClientGoneChecker(
|
|
94
|
+
res: Response,
|
|
95
|
+
signal?: AbortSignal,
|
|
96
|
+
): () => boolean {
|
|
94
97
|
return () => signal?.aborted === true || res.writableEnded || res.destroyed
|
|
95
98
|
}
|
|
96
99
|
|
|
@@ -178,7 +181,10 @@ async function runStage(
|
|
|
178
181
|
|
|
179
182
|
const targetModel = models[rel.type]
|
|
180
183
|
if (!targetModel) {
|
|
181
|
-
throw new HttpError(
|
|
184
|
+
throw new HttpError(
|
|
185
|
+
500,
|
|
186
|
+
'Target model not in relation metadata: ' + rel.type,
|
|
187
|
+
)
|
|
182
188
|
}
|
|
183
189
|
|
|
184
190
|
const stageArgs: Record<string, unknown> = { ...stage.stageArgs }
|
|
@@ -289,14 +295,24 @@ async function runGuardedAutoIncludeSingle(
|
|
|
289
295
|
if (!okField) return
|
|
290
296
|
} catch (err) {
|
|
291
297
|
if (isAborted()) return
|
|
292
|
-
console.error(
|
|
298
|
+
console.error(
|
|
299
|
+
LOG_PREFIX,
|
|
300
|
+
'guarded stage failed:',
|
|
301
|
+
stage.relationPath,
|
|
302
|
+
err,
|
|
303
|
+
)
|
|
293
304
|
stageErrorMessage = mapError(err).message
|
|
294
305
|
return
|
|
295
306
|
}
|
|
296
307
|
|
|
297
308
|
if (isAborted()) return
|
|
298
309
|
completed++
|
|
299
|
-
const okProg = sendSSEProgress(
|
|
310
|
+
const okProg = sendSSEProgress(
|
|
311
|
+
res,
|
|
312
|
+
stage.relationPath,
|
|
313
|
+
completed,
|
|
314
|
+
plan.stages.length,
|
|
315
|
+
)
|
|
300
316
|
if (!okProg) return
|
|
301
317
|
})
|
|
302
318
|
|
|
@@ -336,7 +352,8 @@ export async function runGuardedAutoIncludeProgressive(
|
|
|
336
352
|
} catch (err) {
|
|
337
353
|
return handleGuardedFallback(
|
|
338
354
|
options,
|
|
339
|
-
'guarded auto-progressive fallback: getExtendedClient failed: ' +
|
|
355
|
+
'guarded auto-progressive fallback: getExtendedClient failed: ' +
|
|
356
|
+
mapError(err).message,
|
|
340
357
|
)
|
|
341
358
|
}
|
|
342
359
|
|
|
@@ -352,7 +369,8 @@ export async function runGuardedAutoIncludeProgressive(
|
|
|
352
369
|
} catch (err) {
|
|
353
370
|
return handleGuardedFallback(
|
|
354
371
|
options,
|
|
355
|
-
'guarded auto-progressive fallback: resolve failed: ' +
|
|
372
|
+
'guarded auto-progressive fallback: resolve failed: ' +
|
|
373
|
+
mapError(err).message,
|
|
356
374
|
)
|
|
357
375
|
}
|
|
358
376
|
|
|
@@ -376,4 +394,4 @@ export async function runGuardedAutoIncludeProgressive(
|
|
|
376
394
|
}
|
|
377
395
|
|
|
378
396
|
return runGuardedAutoIncludeSingle(options, plan, extended)
|
|
379
|
-
}
|
|
397
|
+
}
|
|
@@ -55,22 +55,59 @@ type BuildOptions = {
|
|
|
55
55
|
description?: string
|
|
56
56
|
version?: string
|
|
57
57
|
writeStrategy?: WriteStrategy
|
|
58
|
+
pathSegment?: string
|
|
58
59
|
}
|
|
59
60
|
|
|
60
|
-
const WHERE_PROP: SchemaObject = {
|
|
61
|
-
|
|
61
|
+
const WHERE_PROP: SchemaObject = {
|
|
62
|
+
type: 'object',
|
|
63
|
+
description: 'Filter conditions',
|
|
64
|
+
}
|
|
65
|
+
const TAKE_PROP: SchemaObject = {
|
|
66
|
+
type: 'integer',
|
|
67
|
+
description: 'Limit results',
|
|
68
|
+
}
|
|
62
69
|
const SKIP_PROP: SchemaObject = { type: 'integer', description: 'Skip results' }
|
|
63
|
-
const CURSOR_PROP: SchemaObject = {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
const
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
const
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
70
|
+
const CURSOR_PROP: SchemaObject = {
|
|
71
|
+
type: 'object',
|
|
72
|
+
description: 'Cursor for pagination',
|
|
73
|
+
}
|
|
74
|
+
const ORDERBY_PROP: SchemaObject = {
|
|
75
|
+
description: 'Sort order (object or array of objects)',
|
|
76
|
+
}
|
|
77
|
+
const SELECT_PROP: SchemaObject = {
|
|
78
|
+
type: 'object',
|
|
79
|
+
description: 'Select fields',
|
|
80
|
+
}
|
|
81
|
+
const INCLUDE_PROP: SchemaObject = {
|
|
82
|
+
type: 'object',
|
|
83
|
+
description: 'Include relations',
|
|
84
|
+
}
|
|
85
|
+
const OMIT_PROP: SchemaObject = {
|
|
86
|
+
type: 'object',
|
|
87
|
+
description: 'Omit fields from response',
|
|
88
|
+
}
|
|
89
|
+
const DISTINCT_PROP: SchemaObject = {
|
|
90
|
+
description: 'Distinct fields (string or array of strings)',
|
|
91
|
+
}
|
|
92
|
+
const AGG_COUNT: SchemaObject = {
|
|
93
|
+
description: 'Count aggregate (true or field selection object)',
|
|
94
|
+
}
|
|
95
|
+
const AGG_AVG: SchemaObject = {
|
|
96
|
+
type: 'object',
|
|
97
|
+
description: 'Average aggregate (field selection object)',
|
|
98
|
+
}
|
|
99
|
+
const AGG_SUM: SchemaObject = {
|
|
100
|
+
type: 'object',
|
|
101
|
+
description: 'Sum aggregate (field selection object)',
|
|
102
|
+
}
|
|
103
|
+
const AGG_MIN: SchemaObject = {
|
|
104
|
+
type: 'object',
|
|
105
|
+
description: 'Min aggregate (field selection object)',
|
|
106
|
+
}
|
|
107
|
+
const AGG_MAX: SchemaObject = {
|
|
108
|
+
type: 'object',
|
|
109
|
+
description: 'Max aggregate (field selection object)',
|
|
110
|
+
}
|
|
74
111
|
|
|
75
112
|
const PROJECTION_PROPS: Record<string, SchemaObject> = {
|
|
76
113
|
select: SELECT_PROP,
|
|
@@ -330,8 +367,18 @@ function applyWriteStrategy(
|
|
|
330
367
|
verb: string
|
|
331
368
|
targetOp: string
|
|
332
369
|
}> = [
|
|
333
|
-
{
|
|
334
|
-
|
|
370
|
+
{
|
|
371
|
+
method: 'post',
|
|
372
|
+
successCode: '201',
|
|
373
|
+
verb: 'Create',
|
|
374
|
+
targetOp: 'createManyAndReturn',
|
|
375
|
+
},
|
|
376
|
+
{
|
|
377
|
+
method: 'put',
|
|
378
|
+
successCode: '200',
|
|
379
|
+
verb: 'Update',
|
|
380
|
+
targetOp: 'updateManyAndReturn',
|
|
381
|
+
},
|
|
335
382
|
]
|
|
336
383
|
|
|
337
384
|
for (const entry of forceReturnOps) {
|
|
@@ -341,9 +388,11 @@ function applyWriteStrategy(
|
|
|
341
388
|
target,
|
|
342
389
|
entry.successCode,
|
|
343
390
|
entry.verb + ' many ' + modelName + ' (forceReturn)',
|
|
344
|
-
'writeStrategy="forceReturn": this endpoint silently invokes ' +
|
|
345
|
-
|
|
346
|
-
|
|
391
|
+
'writeStrategy="forceReturn": this endpoint silently invokes ' +
|
|
392
|
+
entry.targetOp +
|
|
393
|
+
' and returns the ' +
|
|
394
|
+
(entry.verb === 'Create' ? 'created' : 'updated') +
|
|
395
|
+
' records instead of { count }.',
|
|
347
396
|
)
|
|
348
397
|
}
|
|
349
398
|
}
|
|
@@ -382,7 +431,9 @@ export function buildModelOpenApi(
|
|
|
382
431
|
const basePath =
|
|
383
432
|
normalizePrefix(prefixSource) +
|
|
384
433
|
removeTrailingSlash(
|
|
385
|
-
config.addModelPrefix !== false
|
|
434
|
+
config.addModelPrefix !== false
|
|
435
|
+
? `/${options.pathSegment ?? modelName.toLowerCase()}`
|
|
436
|
+
: '',
|
|
386
437
|
)
|
|
387
438
|
|
|
388
439
|
const referencedEnumTypes = new Set(
|
|
@@ -794,20 +845,47 @@ function generatePaths(
|
|
|
794
845
|
config: RouteConfig,
|
|
795
846
|
fields: ModelField[],
|
|
796
847
|
) {
|
|
797
|
-
const
|
|
848
|
+
const postReadsFor = (op: string): boolean => {
|
|
849
|
+
const raw: unknown = (config as Record<string, unknown>)[op]
|
|
850
|
+
const opValue =
|
|
851
|
+
raw && typeof raw === 'object'
|
|
852
|
+
? (raw as { disablePostReads?: boolean }).disablePostReads
|
|
853
|
+
: undefined
|
|
854
|
+
return !(opValue ?? config.disablePostReads ?? false)
|
|
855
|
+
}
|
|
798
856
|
|
|
799
|
-
const createInputRef = {
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
const
|
|
857
|
+
const createInputRef = {
|
|
858
|
+
$ref: `#/components/schemas/${modelName}CreateInput`,
|
|
859
|
+
}
|
|
860
|
+
const updateInputRef = {
|
|
861
|
+
$ref: `#/components/schemas/${modelName}UpdateInput`,
|
|
862
|
+
}
|
|
863
|
+
const createManyInputRef = {
|
|
864
|
+
$ref: `#/components/schemas/${modelName}CreateManyInput`,
|
|
865
|
+
}
|
|
866
|
+
const updateManyMutationRef = {
|
|
867
|
+
$ref: `#/components/schemas/${modelName}UpdateManyMutationInput`,
|
|
868
|
+
}
|
|
803
869
|
const responseRef = { $ref: `#/components/schemas/${modelName}Response` }
|
|
804
|
-
const nullableResponseSchema = {
|
|
805
|
-
|
|
870
|
+
const nullableResponseSchema = {
|
|
871
|
+
oneOf: [responseRef, { type: 'null' as const }],
|
|
872
|
+
}
|
|
873
|
+
const batchCountRef = {
|
|
874
|
+
$ref: `#/components/schemas/${modelName}BatchCountResponse`,
|
|
875
|
+
}
|
|
806
876
|
const listRef = { $ref: `#/components/schemas/${modelName}ListResponse` }
|
|
807
|
-
const aggregateRef = {
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
const
|
|
877
|
+
const aggregateRef = {
|
|
878
|
+
$ref: `#/components/schemas/${modelName}AggregateResponse`,
|
|
879
|
+
}
|
|
880
|
+
const groupByItemRef = {
|
|
881
|
+
$ref: `#/components/schemas/${modelName}GroupByItem`,
|
|
882
|
+
}
|
|
883
|
+
const updateEachItemRef = {
|
|
884
|
+
$ref: `#/components/schemas/${modelName}UpdateEachItemInput`,
|
|
885
|
+
}
|
|
886
|
+
const updateEachResponseRef = {
|
|
887
|
+
$ref: `#/components/schemas/${modelName}UpdateEachResponse`,
|
|
888
|
+
}
|
|
811
889
|
|
|
812
890
|
if (opEnabled(config, 'findMany')) {
|
|
813
891
|
const meta = OPERATION_BY_NAME['findMany']
|
|
@@ -830,7 +908,7 @@ function generatePaths(
|
|
|
830
908
|
addErrorResponses(op, meta.errors)
|
|
831
909
|
addPath(spec, opPath(basePath, 'findMany'), 'get', op)
|
|
832
910
|
|
|
833
|
-
if (
|
|
911
|
+
if (postReadsFor('findMany')) {
|
|
834
912
|
addPostReadOperation(
|
|
835
913
|
spec,
|
|
836
914
|
postReadPath(basePath, 'findMany'),
|
|
@@ -862,7 +940,7 @@ function generatePaths(
|
|
|
862
940
|
addErrorResponses(op, meta.errors)
|
|
863
941
|
addPath(spec, opPath(basePath, 'findUnique'), 'get', op)
|
|
864
942
|
|
|
865
|
-
if (
|
|
943
|
+
if (postReadsFor('findUnique')) {
|
|
866
944
|
addPostReadOperation(
|
|
867
945
|
spec,
|
|
868
946
|
postReadPath(basePath, 'findUnique'),
|
|
@@ -893,7 +971,7 @@ function generatePaths(
|
|
|
893
971
|
addErrorResponses(op, meta.errors)
|
|
894
972
|
addPath(spec, opPath(basePath, 'findUniqueOrThrow'), 'get', op)
|
|
895
973
|
|
|
896
|
-
if (
|
|
974
|
+
if (postReadsFor('findUniqueOrThrow')) {
|
|
897
975
|
addPostReadOperation(
|
|
898
976
|
spec,
|
|
899
977
|
postReadPath(basePath, 'findUniqueOrThrow'),
|
|
@@ -924,7 +1002,7 @@ function generatePaths(
|
|
|
924
1002
|
addErrorResponses(op, meta.errors)
|
|
925
1003
|
addPath(spec, opPath(basePath, 'findFirst'), 'get', op)
|
|
926
1004
|
|
|
927
|
-
if (
|
|
1005
|
+
if (postReadsFor('findFirst')) {
|
|
928
1006
|
addPostReadOperation(
|
|
929
1007
|
spec,
|
|
930
1008
|
postReadPath(basePath, 'findFirst'),
|
|
@@ -955,7 +1033,7 @@ function generatePaths(
|
|
|
955
1033
|
addErrorResponses(op, meta.errors)
|
|
956
1034
|
addPath(spec, opPath(basePath, 'findFirstOrThrow'), 'get', op)
|
|
957
1035
|
|
|
958
|
-
if (
|
|
1036
|
+
if (postReadsFor('findFirstOrThrow')) {
|
|
959
1037
|
addPostReadOperation(
|
|
960
1038
|
spec,
|
|
961
1039
|
postReadPath(basePath, 'findFirstOrThrow'),
|
|
@@ -987,7 +1065,7 @@ function generatePaths(
|
|
|
987
1065
|
addErrorResponses(op, meta.errors)
|
|
988
1066
|
addPath(spec, opPath(basePath, 'findManyPaginated'), 'get', op)
|
|
989
1067
|
|
|
990
|
-
if (
|
|
1068
|
+
if (postReadsFor('findManyPaginated')) {
|
|
991
1069
|
addPostReadOperation(
|
|
992
1070
|
spec,
|
|
993
1071
|
postReadPath(basePath, 'findManyPaginated'),
|
|
@@ -1327,7 +1405,8 @@ function generatePaths(
|
|
|
1327
1405
|
},
|
|
1328
1406
|
{
|
|
1329
1407
|
type: 'object',
|
|
1330
|
-
description:
|
|
1408
|
+
description:
|
|
1409
|
+
'Per-field count object when select is provided',
|
|
1331
1410
|
},
|
|
1332
1411
|
],
|
|
1333
1412
|
},
|
|
@@ -1339,7 +1418,7 @@ function generatePaths(
|
|
|
1339
1418
|
addErrorResponses(op, meta.errors)
|
|
1340
1419
|
addPath(spec, opPath(basePath, 'count'), 'get', op)
|
|
1341
1420
|
|
|
1342
|
-
if (
|
|
1421
|
+
if (postReadsFor('count')) {
|
|
1343
1422
|
addPostReadOperation(
|
|
1344
1423
|
spec,
|
|
1345
1424
|
postReadPath(basePath, 'count'),
|
|
@@ -1380,7 +1459,7 @@ function generatePaths(
|
|
|
1380
1459
|
addErrorResponses(op, meta.errors)
|
|
1381
1460
|
addPath(spec, opPath(basePath, 'aggregate'), 'get', op)
|
|
1382
1461
|
|
|
1383
|
-
if (
|
|
1462
|
+
if (postReadsFor('aggregate')) {
|
|
1384
1463
|
addPostReadOperation(
|
|
1385
1464
|
spec,
|
|
1386
1465
|
postReadPath(basePath, 'aggregate'),
|
|
@@ -1416,7 +1495,7 @@ function generatePaths(
|
|
|
1416
1495
|
addErrorResponses(op, meta.errors)
|
|
1417
1496
|
addPath(spec, opPath(basePath, 'groupBy'), 'get', op)
|
|
1418
1497
|
|
|
1419
|
-
if (
|
|
1498
|
+
if (postReadsFor('groupBy')) {
|
|
1420
1499
|
addPostReadOperation(
|
|
1421
1500
|
spec,
|
|
1422
1501
|
postReadPath(basePath, 'groupBy'),
|
|
@@ -1581,12 +1660,30 @@ function mapFieldToSchema(field: ModelField): SchemaObject | RefObject {
|
|
|
1581
1660
|
}
|
|
1582
1661
|
|
|
1583
1662
|
function nestedListRelationOps(fieldType: string): SchemaObject {
|
|
1584
|
-
const createInput: SchemaObject = {
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
const
|
|
1589
|
-
|
|
1663
|
+
const createInput: SchemaObject = {
|
|
1664
|
+
type: 'object',
|
|
1665
|
+
description: `${fieldType} create input`,
|
|
1666
|
+
}
|
|
1667
|
+
const uniqueId: SchemaObject = {
|
|
1668
|
+
type: 'object',
|
|
1669
|
+
description: 'Unique identifier',
|
|
1670
|
+
}
|
|
1671
|
+
const uniqueConnect: SchemaObject = {
|
|
1672
|
+
type: 'object',
|
|
1673
|
+
description: 'Unique identifier to connect',
|
|
1674
|
+
}
|
|
1675
|
+
const uniqueDisconnect: SchemaObject = {
|
|
1676
|
+
type: 'object',
|
|
1677
|
+
description: 'Unique identifier to disconnect',
|
|
1678
|
+
}
|
|
1679
|
+
const uniqueDelete: SchemaObject = {
|
|
1680
|
+
type: 'object',
|
|
1681
|
+
description: 'Unique identifier to delete',
|
|
1682
|
+
}
|
|
1683
|
+
const whereFilter: SchemaObject = {
|
|
1684
|
+
type: 'object',
|
|
1685
|
+
description: 'Where filter',
|
|
1686
|
+
}
|
|
1590
1687
|
const whereCreatePair: SchemaObject = {
|
|
1591
1688
|
type: 'object',
|
|
1592
1689
|
description: '{ where, create } pair',
|
|
@@ -1648,12 +1745,16 @@ function nestedSingleRelationOps(fieldType: string): SchemaObject {
|
|
|
1648
1745
|
description: '{ where, create } pair',
|
|
1649
1746
|
properties: { where: { type: 'object' }, create: { type: 'object' } },
|
|
1650
1747
|
},
|
|
1651
|
-
disconnect: {
|
|
1748
|
+
disconnect: {
|
|
1749
|
+
type: 'boolean',
|
|
1750
|
+
description: 'Disconnect the related record',
|
|
1751
|
+
},
|
|
1652
1752
|
delete: { type: 'boolean', description: 'Delete the related record' },
|
|
1653
1753
|
update: { type: 'object', description: `${fieldType} update input` },
|
|
1654
1754
|
upsert: {
|
|
1655
1755
|
type: 'object',
|
|
1656
|
-
description:
|
|
1756
|
+
description:
|
|
1757
|
+
'{ create, update } pair — create if not exists, update if exists',
|
|
1657
1758
|
properties: {
|
|
1658
1759
|
create: { type: 'object', description: `${fieldType} create input` },
|
|
1659
1760
|
update: { type: 'object', description: `${fieldType} update input` },
|
|
@@ -1668,7 +1769,9 @@ function mapFieldToWriteSchema(
|
|
|
1668
1769
|
mode: 'create' | 'update',
|
|
1669
1770
|
): SchemaObject | RefObject {
|
|
1670
1771
|
if (field.kind === 'object') {
|
|
1671
|
-
return field.isList
|
|
1772
|
+
return field.isList
|
|
1773
|
+
? nestedListRelationOps(field.type)
|
|
1774
|
+
: nestedSingleRelationOps(field.type)
|
|
1672
1775
|
}
|
|
1673
1776
|
|
|
1674
1777
|
let baseSchema: SchemaObject | RefObject
|
|
@@ -1733,4 +1836,4 @@ function mapScalarType(type: string): SchemaObject {
|
|
|
1733
1836
|
},
|
|
1734
1837
|
}
|
|
1735
1838
|
return typeMap[type] || { type: 'string' }
|
|
1736
|
-
}
|
|
1839
|
+
}
|
package/src/copy/concurrency.ts
CHANGED
|
@@ -8,13 +8,15 @@ export async function mapLimited<T>(
|
|
|
8
8
|
const workerCount = Math.min(limit, items.length)
|
|
9
9
|
const workers: Promise<void>[] = []
|
|
10
10
|
for (let w = 0; w < workerCount; w++) {
|
|
11
|
-
workers.push(
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
11
|
+
workers.push(
|
|
12
|
+
(async () => {
|
|
13
|
+
for (;;) {
|
|
14
|
+
const i = index++
|
|
15
|
+
if (i >= items.length) return
|
|
16
|
+
await fn(items[i], i)
|
|
17
|
+
}
|
|
18
|
+
})(),
|
|
19
|
+
)
|
|
18
20
|
}
|
|
19
21
|
await Promise.all(workers)
|
|
20
|
-
}
|
|
22
|
+
}
|
package/src/copy/errorMapper.ts
CHANGED
|
@@ -36,10 +36,19 @@ const PRISMA_ERROR_MAP: Record<string, { status: number; message: string }> = {
|
|
|
36
36
|
P2023: { status: 500, message: 'Inconsistent column data' },
|
|
37
37
|
P2024: { status: 503, message: 'Connection pool timeout' },
|
|
38
38
|
P2025: { status: 404, message: 'Record not found' },
|
|
39
|
-
P2026: {
|
|
40
|
-
|
|
39
|
+
P2026: {
|
|
40
|
+
status: 501,
|
|
41
|
+
message: 'Feature not supported by the current database provider',
|
|
42
|
+
},
|
|
43
|
+
P2027: {
|
|
44
|
+
status: 400,
|
|
45
|
+
message: 'Multiple errors occurred during transaction execution',
|
|
46
|
+
},
|
|
41
47
|
P2028: { status: 500, message: 'Transaction API error' },
|
|
42
|
-
P2030: {
|
|
48
|
+
P2030: {
|
|
49
|
+
status: 400,
|
|
50
|
+
message: 'Cannot find a fulltext index for the search',
|
|
51
|
+
},
|
|
43
52
|
P2033: { status: 400, message: 'Number out of range for the field type' },
|
|
44
53
|
P2034: { status: 409, message: 'Transaction conflict, please retry' },
|
|
45
54
|
}
|
|
@@ -59,11 +68,20 @@ function asErrorShape(error: unknown): ErrorShape {
|
|
|
59
68
|
}
|
|
60
69
|
|
|
61
70
|
function isProduction(): boolean {
|
|
62
|
-
return
|
|
71
|
+
return (
|
|
72
|
+
typeof process !== 'undefined' &&
|
|
73
|
+
process.env &&
|
|
74
|
+
process.env.NODE_ENV === 'production'
|
|
75
|
+
)
|
|
63
76
|
}
|
|
64
77
|
|
|
65
78
|
function isHttpStatus(value: unknown): value is number {
|
|
66
|
-
return
|
|
79
|
+
return (
|
|
80
|
+
typeof value === 'number' &&
|
|
81
|
+
Number.isInteger(value) &&
|
|
82
|
+
value >= 400 &&
|
|
83
|
+
value <= 599
|
|
84
|
+
)
|
|
67
85
|
}
|
|
68
86
|
|
|
69
87
|
export function mapError(error: unknown): HttpError {
|
|
@@ -77,18 +95,27 @@ export function mapError(error: unknown): HttpError {
|
|
|
77
95
|
? e.statusCode
|
|
78
96
|
: null
|
|
79
97
|
if (duckStatus !== null) {
|
|
80
|
-
const detail =
|
|
98
|
+
const detail =
|
|
99
|
+
typeof e.message === 'string' && e.message
|
|
100
|
+
? e.message
|
|
101
|
+
: 'Internal server error'
|
|
81
102
|
return new HttpError(duckStatus, detail)
|
|
82
103
|
}
|
|
83
104
|
|
|
84
|
-
if (e.name === 'ShapeError')
|
|
85
|
-
|
|
86
|
-
if (e.name === '
|
|
105
|
+
if (e.name === 'ShapeError')
|
|
106
|
+
return new HttpError(400, e.message || 'Shape validation failed')
|
|
107
|
+
if (e.name === 'CallerError')
|
|
108
|
+
return new HttpError(400, e.message || 'Caller validation failed')
|
|
109
|
+
if (e.name === 'PolicyError')
|
|
110
|
+
return new HttpError(403, e.message || 'Policy denied')
|
|
87
111
|
if (e.name === 'ZodError') {
|
|
88
112
|
const issues = e.issues
|
|
89
113
|
const message = Array.isArray(issues)
|
|
90
|
-
? (issues as Array<{ message?: string }>)
|
|
91
|
-
|
|
114
|
+
? (issues as Array<{ message?: string }>)
|
|
115
|
+
.map((i) => i.message ?? '')
|
|
116
|
+
.filter(Boolean)
|
|
117
|
+
.join('; ')
|
|
118
|
+
: e.message || 'Validation failed'
|
|
92
119
|
return new HttpError(400, message)
|
|
93
120
|
}
|
|
94
121
|
if (typeof e.code === 'string') {
|
|
@@ -98,7 +125,9 @@ export function mapError(error: unknown): HttpError {
|
|
|
98
125
|
const shouldStripDetail = isProd && mapped.status >= 500
|
|
99
126
|
return new HttpError(
|
|
100
127
|
mapped.status,
|
|
101
|
-
!shouldStripDetail && detail
|
|
128
|
+
!shouldStripDetail && detail
|
|
129
|
+
? mapped.message + ': ' + detail
|
|
130
|
+
: mapped.message,
|
|
102
131
|
)
|
|
103
132
|
}
|
|
104
133
|
if (e.code.startsWith('P')) {
|
|
@@ -108,19 +137,39 @@ export function mapError(error: unknown): HttpError {
|
|
|
108
137
|
}
|
|
109
138
|
}
|
|
110
139
|
if (typeof e.name === 'string') {
|
|
111
|
-
if (e.name === 'PrismaClientValidationError')
|
|
112
|
-
|
|
140
|
+
if (e.name === 'PrismaClientValidationError')
|
|
141
|
+
return new HttpError(400, e.message || 'Invalid query parameters')
|
|
142
|
+
if (e.name === 'PrismaClientKnownRequestError')
|
|
143
|
+
return new HttpError(400, e.message || 'Database request error')
|
|
113
144
|
if (e.name === 'PrismaClientInitializationError') {
|
|
114
|
-
return new HttpError(
|
|
145
|
+
return new HttpError(
|
|
146
|
+
503,
|
|
147
|
+
isProd
|
|
148
|
+
? 'Service unavailable'
|
|
149
|
+
: e.message || 'Database connection failed',
|
|
150
|
+
)
|
|
115
151
|
}
|
|
116
152
|
if (e.name === 'PrismaClientRustPanicError') {
|
|
117
|
-
return new HttpError(
|
|
153
|
+
return new HttpError(
|
|
154
|
+
500,
|
|
155
|
+
isProd
|
|
156
|
+
? 'Internal server error'
|
|
157
|
+
: e.message || 'Internal database engine error',
|
|
158
|
+
)
|
|
118
159
|
}
|
|
119
160
|
if (e.name === 'PrismaClientUnknownRequestError') {
|
|
120
|
-
return new HttpError(
|
|
161
|
+
return new HttpError(
|
|
162
|
+
500,
|
|
163
|
+
isProd
|
|
164
|
+
? 'Internal server error'
|
|
165
|
+
: e.message || 'Unknown database error',
|
|
166
|
+
)
|
|
121
167
|
}
|
|
122
168
|
}
|
|
123
169
|
const msg = error instanceof Error ? error.message : String(error)
|
|
124
170
|
console.error(LOG_PREFIX, 'Unhandled error:', error)
|
|
125
|
-
return new HttpError(
|
|
126
|
-
|
|
171
|
+
return new HttpError(
|
|
172
|
+
500,
|
|
173
|
+
isProd ? 'Internal server error' : msg || 'Internal server error',
|
|
174
|
+
)
|
|
175
|
+
}
|
package/src/copy/guardHelpers.ts
CHANGED
|
@@ -3,12 +3,17 @@ import { HttpError } from './errorMapper'
|
|
|
3
3
|
export type PrismaDelegateLike = {
|
|
4
4
|
count: (args?: unknown) => Promise<unknown>
|
|
5
5
|
findMany: (args?: unknown) => Promise<unknown>
|
|
6
|
-
guard?: (
|
|
6
|
+
guard?: (
|
|
7
|
+
shape: Record<string, unknown>,
|
|
8
|
+
caller?: string,
|
|
9
|
+
) => PrismaDelegateLike
|
|
7
10
|
}
|
|
8
11
|
|
|
9
|
-
export function assertGuard(
|
|
10
|
-
delegate:
|
|
11
|
-
): asserts delegate is
|
|
12
|
+
export function assertGuard<T extends PrismaDelegateLike>(
|
|
13
|
+
delegate: T,
|
|
14
|
+
): asserts delegate is T & {
|
|
15
|
+
guard: NonNullable<T['guard']>
|
|
16
|
+
} {
|
|
12
17
|
if (typeof delegate.guard !== 'function') {
|
|
13
18
|
throw new HttpError(
|
|
14
19
|
500,
|
|
@@ -18,9 +23,24 @@ export function assertGuard(
|
|
|
18
23
|
}
|
|
19
24
|
|
|
20
25
|
export const GUARD_SHAPE_CONFIG_KEYS = new Set([
|
|
21
|
-
'data',
|
|
22
|
-
'
|
|
23
|
-
'
|
|
26
|
+
'data',
|
|
27
|
+
'create',
|
|
28
|
+
'update',
|
|
29
|
+
'where',
|
|
30
|
+
'include',
|
|
31
|
+
'select',
|
|
32
|
+
'orderBy',
|
|
33
|
+
'cursor',
|
|
34
|
+
'take',
|
|
35
|
+
'skip',
|
|
36
|
+
'distinct',
|
|
37
|
+
'having',
|
|
38
|
+
'_count',
|
|
39
|
+
'_avg',
|
|
40
|
+
'_sum',
|
|
41
|
+
'_min',
|
|
42
|
+
'_max',
|
|
43
|
+
'by',
|
|
24
44
|
])
|
|
25
45
|
|
|
26
46
|
function keepWhereOnly(obj: Record<string, unknown>): Record<string, unknown> {
|
|
@@ -39,7 +59,8 @@ export function buildCountShape(
|
|
|
39
59
|
return (...args: unknown[]) => keepWhereOnly(fn(...args))
|
|
40
60
|
}
|
|
41
61
|
const keys = Object.keys(shape)
|
|
42
|
-
const isSingleShape =
|
|
62
|
+
const isSingleShape =
|
|
63
|
+
keys.length === 0 || keys.every((k) => GUARD_SHAPE_CONFIG_KEYS.has(k))
|
|
43
64
|
if (isSingleShape) return keepWhereOnly(shape)
|
|
44
65
|
const result: Record<string, unknown> = {}
|
|
45
66
|
for (const [key, variant] of Object.entries(shape)) {
|
|
@@ -53,4 +74,4 @@ export function buildCountShape(
|
|
|
53
74
|
}
|
|
54
75
|
}
|
|
55
76
|
return result
|
|
56
|
-
}
|
|
77
|
+
}
|