@baseplate-dev/fastify-generators 0.5.3 → 0.6.1

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 (177) hide show
  1. package/CHANGELOG.md +70 -0
  2. package/dist/constants/fastify-packages.d.ts +14 -14
  3. package/dist/constants/fastify-packages.js +14 -14
  4. package/dist/constants/fastify-packages.js.map +1 -1
  5. package/dist/generators/auth/auth-context/templates/module/types/auth-context.types.ts +1 -0
  6. package/dist/generators/auth/auth-context/templates/module/utils/auth-context.utils.ts +3 -1
  7. package/dist/generators/core/fastify/fastify.generator.d.ts.map +1 -1
  8. package/dist/generators/core/fastify/fastify.generator.js +3 -1
  9. package/dist/generators/core/fastify/fastify.generator.js.map +1 -1
  10. package/dist/generators/pothos/index.d.ts +1 -0
  11. package/dist/generators/pothos/index.d.ts.map +1 -1
  12. package/dist/generators/pothos/index.js +1 -0
  13. package/dist/generators/pothos/index.js.map +1 -1
  14. package/dist/generators/pothos/pothos-auth/pothos-auth.generator.d.ts +5 -0
  15. package/dist/generators/pothos/pothos-auth/pothos-auth.generator.d.ts.map +1 -1
  16. package/dist/generators/pothos/pothos-auth/pothos-auth.generator.js +8 -0
  17. package/dist/generators/pothos/pothos-auth/pothos-auth.generator.js.map +1 -1
  18. package/dist/generators/pothos/pothos-prisma-count-query/index.d.ts +2 -0
  19. package/dist/generators/pothos/pothos-prisma-count-query/index.d.ts.map +1 -0
  20. package/dist/generators/pothos/pothos-prisma-count-query/index.js +2 -0
  21. package/dist/generators/pothos/pothos-prisma-count-query/index.js.map +1 -0
  22. package/dist/generators/pothos/pothos-prisma-count-query/pothos-prisma-count-query.generator.d.ts +16 -0
  23. package/dist/generators/pothos/pothos-prisma-count-query/pothos-prisma-count-query.generator.d.ts.map +1 -0
  24. package/dist/generators/pothos/pothos-prisma-count-query/pothos-prisma-count-query.generator.js +104 -0
  25. package/dist/generators/pothos/pothos-prisma-count-query/pothos-prisma-count-query.generator.js.map +1 -0
  26. package/dist/generators/pothos/pothos-prisma-crud-mutation/pothos-prisma-crud-mutation.generator.d.ts.map +1 -1
  27. package/dist/generators/pothos/pothos-prisma-crud-mutation/pothos-prisma-crud-mutation.generator.js +2 -5
  28. package/dist/generators/pothos/pothos-prisma-crud-mutation/pothos-prisma-crud-mutation.generator.js.map +1 -1
  29. package/dist/generators/pothos/pothos-prisma-find-query/pothos-prisma-find-query.generator.d.ts +4 -0
  30. package/dist/generators/pothos/pothos-prisma-find-query/pothos-prisma-find-query.generator.d.ts.map +1 -1
  31. package/dist/generators/pothos/pothos-prisma-find-query/pothos-prisma-find-query.generator.js +52 -10
  32. package/dist/generators/pothos/pothos-prisma-find-query/pothos-prisma-find-query.generator.js.map +1 -1
  33. package/dist/generators/pothos/pothos-prisma-list-query/pothos-prisma-list-query.generator.d.ts +4 -0
  34. package/dist/generators/pothos/pothos-prisma-list-query/pothos-prisma-list-query.generator.d.ts.map +1 -1
  35. package/dist/generators/pothos/pothos-prisma-list-query/pothos-prisma-list-query.generator.js +39 -3
  36. package/dist/generators/pothos/pothos-prisma-list-query/pothos-prisma-list-query.generator.js.map +1 -1
  37. package/dist/generators/pothos/pothos-prisma-object/pothos-prisma-object.generator.d.ts +7 -1
  38. package/dist/generators/pothos/pothos-prisma-object/pothos-prisma-object.generator.d.ts.map +1 -1
  39. package/dist/generators/pothos/pothos-prisma-object/pothos-prisma-object.generator.js +73 -13
  40. package/dist/generators/pothos/pothos-prisma-object/pothos-prisma-object.generator.js.map +1 -1
  41. package/dist/generators/prisma/_shared/build-data-helpers/generate-authorize-fragment.d.ts +20 -0
  42. package/dist/generators/prisma/_shared/build-data-helpers/generate-authorize-fragment.d.ts.map +1 -0
  43. package/dist/generators/prisma/_shared/build-data-helpers/generate-authorize-fragment.js +28 -0
  44. package/dist/generators/prisma/_shared/build-data-helpers/generate-authorize-fragment.js.map +1 -0
  45. package/dist/generators/prisma/_shared/build-data-helpers/generate-operation-callbacks.d.ts +48 -55
  46. package/dist/generators/prisma/_shared/build-data-helpers/generate-operation-callbacks.d.ts.map +1 -1
  47. package/dist/generators/prisma/_shared/build-data-helpers/generate-operation-callbacks.js +130 -67
  48. package/dist/generators/prisma/_shared/build-data-helpers/generate-operation-callbacks.js.map +1 -1
  49. package/dist/generators/prisma/_shared/build-data-helpers/generate-relation-build-data.d.ts +2 -0
  50. package/dist/generators/prisma/_shared/build-data-helpers/generate-relation-build-data.d.ts.map +1 -1
  51. package/dist/generators/prisma/_shared/build-data-helpers/generate-relation-build-data.js +3 -3
  52. package/dist/generators/prisma/_shared/build-data-helpers/generate-relation-build-data.js.map +1 -1
  53. package/dist/generators/prisma/_shared/build-data-helpers/index.d.ts +1 -0
  54. package/dist/generators/prisma/_shared/build-data-helpers/index.d.ts.map +1 -1
  55. package/dist/generators/prisma/_shared/build-data-helpers/index.js +1 -0
  56. package/dist/generators/prisma/_shared/build-data-helpers/index.js.map +1 -1
  57. package/dist/generators/prisma/data-utils/data-utils.generator.d.ts +25 -6
  58. package/dist/generators/prisma/data-utils/data-utils.generator.d.ts.map +1 -1
  59. package/dist/generators/prisma/data-utils/generated/index.d.ts +78 -23
  60. package/dist/generators/prisma/data-utils/generated/index.d.ts.map +1 -1
  61. package/dist/generators/prisma/data-utils/generated/template-paths.d.ts +3 -1
  62. package/dist/generators/prisma/data-utils/generated/template-paths.d.ts.map +1 -1
  63. package/dist/generators/prisma/data-utils/generated/template-paths.js +3 -1
  64. package/dist/generators/prisma/data-utils/generated/template-paths.js.map +1 -1
  65. package/dist/generators/prisma/data-utils/generated/ts-import-providers.d.ts +75 -18
  66. package/dist/generators/prisma/data-utils/generated/ts-import-providers.d.ts.map +1 -1
  67. package/dist/generators/prisma/data-utils/generated/ts-import-providers.js +26 -8
  68. package/dist/generators/prisma/data-utils/generated/ts-import-providers.js.map +1 -1
  69. package/dist/generators/prisma/data-utils/generated/typed-templates.d.ts +106 -34
  70. package/dist/generators/prisma/data-utils/generated/typed-templates.d.ts.map +1 -1
  71. package/dist/generators/prisma/data-utils/generated/typed-templates.js +61 -14
  72. package/dist/generators/prisma/data-utils/generated/typed-templates.js.map +1 -1
  73. package/dist/generators/prisma/data-utils/templates/src/utils/data-operations/commit-operations.ts +366 -0
  74. package/dist/generators/prisma/data-utils/templates/src/utils/data-operations/compose-operations.ts +131 -0
  75. package/dist/generators/prisma/data-utils/templates/src/utils/data-operations/field-definitions.ts +26 -30
  76. package/dist/generators/prisma/data-utils/templates/src/utils/data-operations/field-utils.ts +201 -0
  77. package/dist/generators/prisma/data-utils/templates/src/utils/data-operations/prisma-types.ts +24 -20
  78. package/dist/generators/prisma/data-utils/templates/src/utils/data-operations/prisma-utils.ts +14 -6
  79. package/dist/generators/prisma/data-utils/templates/src/utils/data-operations/relation-helpers.ts +21 -26
  80. package/dist/generators/prisma/data-utils/templates/src/utils/data-operations/types.ts +374 -25
  81. package/dist/generators/prisma/index.d.ts +2 -0
  82. package/dist/generators/prisma/index.d.ts.map +1 -1
  83. package/dist/generators/prisma/index.js +2 -0
  84. package/dist/generators/prisma/index.js.map +1 -1
  85. package/dist/generators/prisma/prisma-authorizer-utils/generated/index.d.ts +50 -12
  86. package/dist/generators/prisma/prisma-authorizer-utils/generated/index.d.ts.map +1 -1
  87. package/dist/generators/prisma/prisma-authorizer-utils/generated/template-renderers.d.ts +25 -6
  88. package/dist/generators/prisma/prisma-authorizer-utils/generated/template-renderers.d.ts.map +1 -1
  89. package/dist/generators/prisma/prisma-authorizer-utils/generated/typed-templates.d.ts +50 -12
  90. package/dist/generators/prisma/prisma-authorizer-utils/generated/typed-templates.d.ts.map +1 -1
  91. package/dist/generators/prisma/prisma-authorizer-utils/prisma-authorizer-utils.generator.d.ts +25 -6
  92. package/dist/generators/prisma/prisma-authorizer-utils/prisma-authorizer-utils.generator.d.ts.map +1 -1
  93. package/dist/generators/prisma/prisma-data-create/prisma-data-create.generator.d.ts +26 -6
  94. package/dist/generators/prisma/prisma-data-create/prisma-data-create.generator.d.ts.map +1 -1
  95. package/dist/generators/prisma/prisma-data-create/prisma-data-create.generator.js +53 -25
  96. package/dist/generators/prisma/prisma-data-create/prisma-data-create.generator.js.map +1 -1
  97. package/dist/generators/prisma/prisma-data-delete/prisma-data-delete.generator.d.ts +31 -6
  98. package/dist/generators/prisma/prisma-data-delete/prisma-data-delete.generator.d.ts.map +1 -1
  99. package/dist/generators/prisma/prisma-data-delete/prisma-data-delete.generator.js +52 -15
  100. package/dist/generators/prisma/prisma-data-delete/prisma-data-delete.generator.js.map +1 -1
  101. package/dist/generators/prisma/prisma-data-nested-field/prisma-data-nested-field.generator.d.ts +25 -6
  102. package/dist/generators/prisma/prisma-data-nested-field/prisma-data-nested-field.generator.d.ts.map +1 -1
  103. package/dist/generators/prisma/prisma-data-service/prisma-data-service.generator.d.ts +25 -6
  104. package/dist/generators/prisma/prisma-data-service/prisma-data-service.generator.d.ts.map +1 -1
  105. package/dist/generators/prisma/prisma-data-update/prisma-data-update.generator.d.ts +31 -6
  106. package/dist/generators/prisma/prisma-data-update/prisma-data-update.generator.d.ts.map +1 -1
  107. package/dist/generators/prisma/prisma-data-update/prisma-data-update.generator.js +65 -23
  108. package/dist/generators/prisma/prisma-data-update/prisma-data-update.generator.js.map +1 -1
  109. package/dist/generators/prisma/prisma-model-authorizer/prisma-model-authorizer.generator.d.ts +2 -0
  110. package/dist/generators/prisma/prisma-model-authorizer/prisma-model-authorizer.generator.d.ts.map +1 -1
  111. package/dist/generators/prisma/prisma-model-authorizer/prisma-model-authorizer.generator.js +86 -53
  112. package/dist/generators/prisma/prisma-model-authorizer/prisma-model-authorizer.generator.js.map +1 -1
  113. package/dist/generators/prisma/prisma-model-query-filter/index.d.ts +2 -0
  114. package/dist/generators/prisma/prisma-model-query-filter/index.d.ts.map +1 -0
  115. package/dist/generators/prisma/prisma-model-query-filter/index.js +2 -0
  116. package/dist/generators/prisma/prisma-model-query-filter/index.js.map +1 -0
  117. package/dist/generators/prisma/prisma-model-query-filter/prisma-model-query-filter.generator.d.ts +60 -0
  118. package/dist/generators/prisma/prisma-model-query-filter/prisma-model-query-filter.generator.d.ts.map +1 -0
  119. package/dist/generators/prisma/prisma-model-query-filter/prisma-model-query-filter.generator.js +124 -0
  120. package/dist/generators/prisma/prisma-model-query-filter/prisma-model-query-filter.generator.js.map +1 -0
  121. package/dist/generators/prisma/prisma-query-filter-utils/generated/index.d.ts +364 -0
  122. package/dist/generators/prisma/prisma-query-filter-utils/generated/index.d.ts.map +1 -0
  123. package/dist/generators/prisma/prisma-query-filter-utils/generated/index.js +13 -0
  124. package/dist/generators/prisma/prisma-query-filter-utils/generated/index.js.map +1 -0
  125. package/dist/generators/prisma/prisma-query-filter-utils/generated/template-paths.d.ts +13 -0
  126. package/dist/generators/prisma/prisma-query-filter-utils/generated/template-paths.d.ts.map +1 -0
  127. package/dist/generators/prisma/prisma-query-filter-utils/generated/template-paths.js +25 -0
  128. package/dist/generators/prisma/prisma-query-filter-utils/generated/template-paths.js.map +1 -0
  129. package/dist/generators/prisma/prisma-query-filter-utils/generated/template-renderers.d.ts +131 -0
  130. package/dist/generators/prisma/prisma-query-filter-utils/generated/template-renderers.d.ts.map +1 -0
  131. package/dist/generators/prisma/prisma-query-filter-utils/generated/template-renderers.js +49 -0
  132. package/dist/generators/prisma/prisma-query-filter-utils/generated/template-renderers.js.map +1 -0
  133. package/dist/generators/prisma/prisma-query-filter-utils/generated/ts-import-providers.d.ts +66 -0
  134. package/dist/generators/prisma/prisma-query-filter-utils/generated/ts-import-providers.d.ts.map +1 -0
  135. package/dist/generators/prisma/prisma-query-filter-utils/generated/ts-import-providers.js +40 -0
  136. package/dist/generators/prisma/prisma-query-filter-utils/generated/ts-import-providers.js.map +1 -0
  137. package/dist/generators/prisma/prisma-query-filter-utils/generated/typed-templates.d.ts +411 -0
  138. package/dist/generators/prisma/prisma-query-filter-utils/generated/typed-templates.d.ts.map +1 -0
  139. package/dist/generators/prisma/prisma-query-filter-utils/generated/typed-templates.js +46 -0
  140. package/dist/generators/prisma/prisma-query-filter-utils/generated/typed-templates.js.map +1 -0
  141. package/dist/generators/prisma/prisma-query-filter-utils/index.d.ts +4 -0
  142. package/dist/generators/prisma/prisma-query-filter-utils/index.d.ts.map +1 -0
  143. package/dist/generators/prisma/prisma-query-filter-utils/index.js +3 -0
  144. package/dist/generators/prisma/prisma-query-filter-utils/index.js.map +1 -0
  145. package/dist/generators/prisma/prisma-query-filter-utils/prisma-query-filter-utils.generator.d.ts +156 -0
  146. package/dist/generators/prisma/prisma-query-filter-utils/prisma-query-filter-utils.generator.d.ts.map +1 -0
  147. package/dist/generators/prisma/prisma-query-filter-utils/prisma-query-filter-utils.generator.js +32 -0
  148. package/dist/generators/prisma/prisma-query-filter-utils/prisma-query-filter-utils.generator.js.map +1 -0
  149. package/dist/generators/prisma/prisma-query-filter-utils/templates/src/utils/query-filters.ts +209 -0
  150. package/dist/generators/prisma/prisma-query-filter-utils/templates/src/utils/query-helpers.ts +70 -0
  151. package/dist/generators/vitest/prisma-vitest/generated/index.d.ts +0 -6
  152. package/dist/generators/vitest/prisma-vitest/generated/index.d.ts.map +1 -1
  153. package/dist/generators/vitest/prisma-vitest/generated/template-renderers.d.ts.map +1 -1
  154. package/dist/generators/vitest/prisma-vitest/generated/template-renderers.js +0 -1
  155. package/dist/generators/vitest/prisma-vitest/generated/template-renderers.js.map +1 -1
  156. package/dist/generators/vitest/prisma-vitest/generated/typed-templates.d.ts +0 -6
  157. package/dist/generators/vitest/prisma-vitest/generated/typed-templates.d.ts.map +1 -1
  158. package/dist/generators/vitest/prisma-vitest/generated/typed-templates.js +1 -4
  159. package/dist/generators/vitest/prisma-vitest/generated/typed-templates.js.map +1 -1
  160. package/dist/generators/vitest/prisma-vitest/templates/src/tests/helpers/prisma.test-helper.ts +2 -10
  161. package/dist/types/service-dto-kinds.d.ts +0 -6
  162. package/dist/types/service-dto-kinds.d.ts.map +1 -1
  163. package/dist/types/service-dto-kinds.js +0 -6
  164. package/dist/types/service-dto-kinds.js.map +1 -1
  165. package/dist/writers/pothos/helpers.d.ts +1 -0
  166. package/dist/writers/pothos/helpers.d.ts.map +1 -1
  167. package/dist/writers/pothos/helpers.js +1 -0
  168. package/dist/writers/pothos/helpers.js.map +1 -1
  169. package/dist/writers/pothos/scalar-fields.d.ts +3 -1
  170. package/dist/writers/pothos/scalar-fields.d.ts.map +1 -1
  171. package/dist/writers/pothos/scalar-fields.js +1 -0
  172. package/dist/writers/pothos/scalar-fields.js.map +1 -1
  173. package/dist/writers/prisma-schema/fields.d.ts +1 -1
  174. package/dist/writers/prisma-schema/fields.js +2 -2
  175. package/dist/writers/prisma-schema/fields.js.map +1 -1
  176. package/package.json +8 -8
  177. package/dist/generators/prisma/data-utils/templates/src/utils/data-operations/define-operations.ts +0 -1134
@@ -1,1134 +0,0 @@
1
- // @ts-nocheck
2
-
3
- import type {
4
- GetPayload,
5
- ModelPropName,
6
- ModelQuery,
7
- WhereUniqueInput,
8
- } from '$prismaTypes';
9
- import type {
10
- AnyFieldDefinition,
11
- AnyOperationHooks,
12
- DataOperationType,
13
- InferFieldOutput,
14
- InferFieldsCreateOutput,
15
- InferFieldsOutput,
16
- InferFieldsUpdateOutput,
17
- InferInput,
18
- InferInputSchema,
19
- OperationContext,
20
- OperationHooks,
21
- PrismaTransaction,
22
- TransactionalOperationContext,
23
- } from '$types';
24
- import type {
25
- GlobalRoleCheck,
26
- InstanceRoleCheck,
27
- } from '%authorizerUtilsImports';
28
- import type { Prisma } from '%prismaGeneratedImports';
29
- import type { ServiceContext } from '%serviceContextImports';
30
- import type { Result } from '@prisma/client/runtime/client';
31
-
32
- import { makeGenericPrismaDelegate } from '$prismaUtils';
33
- import {
34
- checkGlobalAuthorization,
35
- checkInstanceAuthorization,
36
- } from '%authorizerUtilsImports';
37
- import { NotFoundError } from '%errorHandlerServiceImports';
38
- import { prisma } from '%prismaImports';
39
- import { z } from 'zod';
40
-
41
- /**
42
- * Invokes an array of hooks with the provided context.
43
- *
44
- * All hooks are executed in parallel using `Promise.all`. If no hooks are provided
45
- * or the array is empty, this function returns immediately.
46
- *
47
- * @template TContext - The context type passed to each hook
48
- * @param hooks - Optional array of async hook functions to invoke
49
- * @param context - The context object passed to each hook
50
- * @returns Promise that resolves when all hooks have completed
51
- *
52
- * @example
53
- * ```typescript
54
- * await invokeHooks(config.hooks?.beforeExecute, {
55
- * operation: 'create',
56
- * serviceContext: ctx,
57
- * tx: transaction,
58
- * });
59
- * ```
60
- */
61
- export async function invokeHooks<TContext>(
62
- hooks: ((ctx: TContext) => Promise<void>)[] | undefined,
63
- context: TContext,
64
- ): Promise<void> {
65
- if (!hooks || hooks.length === 0) return;
66
- await Promise.all(hooks.map((hook) => hook(context)));
67
- }
68
-
69
- /**
70
- * Checks if any hooks are present that could modify data after the initial operation.
71
- * Used to determine if a re-fetch is needed after operation completes.
72
- *
73
- * @param hooks - The hooks object to check
74
- * @returns true if afterExecute or afterCommit hooks exist
75
- */
76
- function hasPostExecuteHooks(hooks: AnyOperationHooks): boolean {
77
- return (
78
- (hooks.afterExecute?.length ?? 0) > 0 ||
79
- (hooks.afterCommit?.length ?? 0) > 0
80
- );
81
- }
82
-
83
- type FieldDataOrFunction<TField extends AnyFieldDefinition> =
84
- | InferFieldOutput<TField>
85
- | ((tx: PrismaTransaction) => Promise<InferFieldOutput<TField>>);
86
-
87
- /**
88
- * Transforms field definitions into Prisma create/update data structures.
89
- *
90
- * This function processes each field definition by:
91
- * 1. Validating the input value against the field's schema
92
- * 2. Transforming the value into Prisma-compatible create/update data
93
- * 3. Collecting hooks from each field for execution during the operation lifecycle
94
- *
95
- * The function supports both synchronous and asynchronous field transformations.
96
- * If any field returns an async transformation function, the entire data object
97
- * becomes async and will be resolved inside the transaction.
98
- *
99
- * @template TFields - Record of field definitions
100
- * @param fields - Field definitions to process
101
- * @param input - Input data to validate and transform
102
- * @param options - Transformation options
103
- * @param options.serviceContext - Service context with user, request info
104
- * @param options.operation - Type of operation (create, update, upsert, delete)
105
- * @param options.allowOptionalFields - Whether to allow undefined field values
106
- * @param options.loadExisting - Function to load existing model data
107
- * @returns Object containing transformed data and collected hooks
108
- *
109
- * @example
110
- * ```typescript
111
- * const { data, hooks } = await transformFields(
112
- * { name: scalarField(z.string()), email: scalarField(z.email()) },
113
- * { name: 'John', email: 'john@example.com' },
114
- * {
115
- * serviceContext: ctx,
116
- * operation: 'create',
117
- * allowOptionalFields: false,
118
- * loadExisting: () => Promise.resolve(undefined),
119
- * },
120
- * );
121
- * ```
122
- */
123
- export async function transformFields<
124
- TFields extends Record<string, AnyFieldDefinition>,
125
- >(
126
- fields: TFields,
127
- input: InferInput<TFields>,
128
- {
129
- serviceContext,
130
- operation,
131
- allowOptionalFields,
132
- loadExisting,
133
- }: {
134
- serviceContext: ServiceContext;
135
- operation: DataOperationType;
136
- allowOptionalFields: boolean;
137
- loadExisting: () => Promise<object | undefined>;
138
- },
139
- ): Promise<{
140
- data:
141
- | InferFieldsOutput<TFields>
142
- | ((tx: PrismaTransaction) => Promise<InferFieldsOutput<TFields>>);
143
- hooks: AnyOperationHooks;
144
- }> {
145
- const hooks: Required<AnyOperationHooks> = {
146
- beforeExecute: [],
147
- afterExecute: [],
148
- afterCommit: [],
149
- };
150
-
151
- const data = {} as {
152
- [K in keyof TFields]: FieldDataOrFunction<TFields[K]>;
153
- };
154
-
155
- for (const [key, field] of Object.entries(fields)) {
156
- const fieldKey = key as keyof typeof input;
157
- const value = input[fieldKey];
158
-
159
- if (allowOptionalFields && value === undefined) continue;
160
-
161
- const result = await field.processInput(value, {
162
- operation,
163
- serviceContext,
164
- fieldName: fieldKey as string,
165
- loadExisting,
166
- });
167
-
168
- if (result.data !== undefined) {
169
- data[fieldKey as keyof TFields] = result.data as FieldDataOrFunction<
170
- TFields[keyof TFields]
171
- >;
172
- }
173
-
174
- if (result.hooks) {
175
- hooks.beforeExecute.push(...(result.hooks.beforeExecute ?? []));
176
- hooks.afterExecute.push(...(result.hooks.afterExecute ?? []));
177
- hooks.afterCommit.push(...(result.hooks.afterCommit ?? []));
178
- }
179
- }
180
-
181
- function splitCreateUpdateData(data: {
182
- [K in keyof TFields]: InferFieldOutput<TFields[K]>;
183
- }): {
184
- create: InferFieldsCreateOutput<TFields>;
185
- update: InferFieldsUpdateOutput<TFields>;
186
- } {
187
- const create = {} as InferFieldsCreateOutput<TFields>;
188
- const update = {} as InferFieldsUpdateOutput<TFields>;
189
- for (const [key, value] of Object.entries<
190
- InferFieldOutput<TFields[keyof TFields]>
191
- >(data)) {
192
- if (value.create !== undefined) {
193
- create[key as keyof TFields] =
194
- value.create as InferFieldsCreateOutput<TFields>[keyof TFields];
195
- }
196
- if (value.update !== undefined) {
197
- update[key as keyof TFields] =
198
- value.update as InferFieldsUpdateOutput<TFields>[keyof TFields];
199
- }
200
- }
201
- return { create, update };
202
- }
203
-
204
- const transformedData = Object.values(data).some(
205
- (value) => typeof value === 'function',
206
- )
207
- ? async (tx: PrismaTransaction) => {
208
- const awaitedData = Object.fromEntries(
209
- await Promise.all(
210
- Object.entries(data).map(
211
- async ([key, value]: [
212
- keyof TFields,
213
- FieldDataOrFunction<TFields[keyof TFields]>,
214
- ]): Promise<
215
- [keyof TFields, InferFieldOutput<TFields[keyof TFields]>]
216
- > => [key, typeof value === 'function' ? await value(tx) : value],
217
- ),
218
- ),
219
- ) as {
220
- [K in keyof TFields]: InferFieldOutput<TFields[K]>;
221
- };
222
- return splitCreateUpdateData(awaitedData);
223
- }
224
- : splitCreateUpdateData(
225
- data as { [K in keyof TFields]: InferFieldOutput<TFields[K]> },
226
- );
227
-
228
- return { data: transformedData, hooks };
229
- }
230
-
231
- /**
232
- * =========================================
233
- * Schema Generation Utilities
234
- * =========================================
235
- */
236
-
237
- /**
238
- * Generates a Zod schema for create operations from field definitions.
239
- *
240
- * Extracts the Zod schema from each field definition and combines them
241
- * into a single object schema. This schema can be used for validation
242
- * in GraphQL resolvers, REST endpoints, tRPC procedures, or OpenAPI documentation.
243
- *
244
- * @template TFields - Record of field definitions
245
- * @param fields - Field definitions to extract schemas from
246
- * @returns Zod object schema with all fields required
247
- *
248
- * @example
249
- * ```typescript
250
- * const fields = {
251
- * name: scalarField(z.string()),
252
- * email: scalarField(z.email()),
253
- * };
254
- *
255
- * const schema = generateCreateSchema(fields);
256
- * // schema is z.object({ name: z.string(), email: z.email() })
257
- *
258
- * // Use for validation
259
- * const validated = schema.parse({ name: 'John', email: 'john@example.com' });
260
- * ```
261
- */
262
- export function generateCreateSchema<
263
- TFields extends Record<string, AnyFieldDefinition>,
264
- >(fields: TFields): InferInputSchema<TFields> {
265
- const shape = Object.fromEntries(
266
- Object.entries(fields).map(([key, field]) => [key, field.schema]),
267
- ) as {
268
- [K in keyof TFields]: TFields[K]['schema'];
269
- };
270
-
271
- return z.object(shape) as InferInputSchema<TFields>;
272
- }
273
-
274
- /**
275
- * =========================================
276
- * Create Operation
277
- * =========================================
278
- */
279
-
280
- /**
281
- * Configuration for defining a create operation.
282
- *
283
- * Create operations insert new records into the database with support for:
284
- * - Field-level validation and transformation
285
- * - Authorization checks before creation
286
- * - Computed fields based on raw input
287
- * - Transaction management with lifecycle hooks
288
- * - Nested relation creation
289
- *
290
- * @template TModelName - Prisma model name (e.g., 'user', 'post')
291
- * @template TFields - Record of field definitions
292
- * @template TPrepareResult - Type of data returned by prepareComputedFields
293
- */
294
- export interface CreateOperationConfig<
295
- TModelName extends ModelPropName,
296
- TFields extends Record<string, AnyFieldDefinition>,
297
- TPrepareResult extends Record<string, unknown> | undefined = undefined,
298
- > {
299
- /**
300
- * Prisma model name
301
- */
302
- model: TModelName;
303
-
304
- /**
305
- * Field definitions for the create operation
306
- */
307
- fields: TFields;
308
-
309
- /**
310
- * Optional authorization check before creating.
311
- * Only global roles (strings) are allowed since there is no model instance.
312
- *
313
- * @example
314
- * ```typescript
315
- * authorize: ['admin', 'system']
316
- * ```
317
- */
318
- authorize?: GlobalRoleCheck[];
319
-
320
- /**
321
- * Optional step to prepare computed fields based off the raw input
322
- */
323
- prepareComputedFields?: (
324
- data: InferInput<TFields>,
325
- ctx: OperationContext<GetPayload<TModelName>, { hasResult: false }>,
326
- ) => TPrepareResult | Promise<TPrepareResult>;
327
-
328
- /**
329
- * Execute the create operation. This function receives validated field data
330
- * and must return a Prisma create operation. It runs inside the transaction.
331
- */
332
- create: <TQueryArgs extends ModelQuery<TModelName>>(input: {
333
- tx: PrismaTransaction;
334
- data: InferFieldsCreateOutput<TFields> & TPrepareResult;
335
- query: { include: NonNullable<TQueryArgs['include']> };
336
- serviceContext: ServiceContext;
337
- }) => Promise<
338
- Result<
339
- (typeof prisma)[TModelName],
340
- // We type the query parameter to ensure that the user always includes ...query into the create call
341
- { include: NonNullable<TQueryArgs['include']> },
342
- 'create'
343
- >
344
- >;
345
-
346
- /**
347
- * Optional hooks for the operation
348
- */
349
- hooks?: OperationHooks<GetPayload<TModelName>>;
350
-
351
- /**
352
- * Function to extract unique identifier from the created result.
353
- * Required for re-fetching fresh data after hooks modify related records.
354
- *
355
- * @example
356
- * ```typescript
357
- * getWhereUnique: (result) => ({ id: result.id })
358
- * ```
359
- */
360
- getWhereUnique: (
361
- result: GetPayload<TModelName>,
362
- ) => WhereUniqueInput<TModelName>;
363
- }
364
-
365
- /**
366
- * Input parameters for executing a create operation.
367
- *
368
- * @template TModelName - Prisma model name
369
- * @template TFields - Record of field definitions
370
- * @template TQueryArgs - Prisma query arguments (select/include)
371
- */
372
- export interface CreateOperationInput<
373
- TModelName extends ModelPropName,
374
- TFields extends Record<string, AnyFieldDefinition>,
375
- TQueryArgs extends ModelQuery<TModelName>,
376
- > {
377
- /** Data to create the new record with */
378
- data: InferInput<TFields>;
379
- /** Optional Prisma query arguments to shape the returned data */
380
- query?: TQueryArgs;
381
- /** Service context containing user info, request details, etc. */
382
- context: ServiceContext;
383
- /**
384
- * Skip Zod validation if data has already been validated (avoids double validation).
385
- * Set to true when validation happened at a higher layer (e.g., GraphQL input type validation).
386
- */
387
- skipValidation?: boolean;
388
- }
389
-
390
- type CreateOperationFunction<
391
- TModelName extends ModelPropName,
392
- TFields extends Record<string, AnyFieldDefinition>,
393
- > = (<TQueryArgs extends ModelQuery<TModelName>>(
394
- input: CreateOperationInput<TModelName, TFields, TQueryArgs>,
395
- ) => Promise<GetPayload<TModelName, TQueryArgs>>) & {
396
- $dataSchema: InferInputSchema<TFields>;
397
- };
398
-
399
- /**
400
- * Defines a type-safe create operation for a Prisma model.
401
- *
402
- * Creates a reusable function for inserting new records with built-in:
403
- * - Input validation via field definitions
404
- * - Authorization checks
405
- * - Computed field preparation
406
- * - Transaction management
407
- * - Hook execution at each lifecycle phase
408
- *
409
- * @template TModelName - Prisma model name
410
- * @template TFields - Record of field definitions
411
- * @template TPrepareResult - Type of prepared computed fields
412
- * @param config - Operation configuration
413
- * @returns Async function that executes the create operation
414
- *
415
- * @example
416
- * ```typescript
417
- * const createUser = defineCreateOperation({
418
- * model: 'user',
419
- * fields: {
420
- * name: scalarField(z.string()),
421
- * email: scalarField(z.email()),
422
- * },
423
- * authorize: async (data, ctx) => {
424
- * // Check if user has permission to create
425
- * },
426
- * create: ({ tx, data, query, serviceContext }) =>
427
- * tx.user.create({
428
- * data: {
429
- * name: data.name,
430
- * email: data.email,
431
- * createdById: serviceContext.user?.id,
432
- * },
433
- * ...query,
434
- * }),
435
- * });
436
- *
437
- * // Usage
438
- * const user = await createUser({
439
- * data: { name: 'John', email: 'john@example.com' },
440
- * context: serviceContext,
441
- * });
442
- * ```
443
- */
444
- export function defineCreateOperation<
445
- TModelName extends Prisma.TypeMap['meta']['modelProps'],
446
- TFields extends Record<string, AnyFieldDefinition>,
447
- TPrepareResult extends Record<string, unknown> | undefined = Record<
448
- string,
449
- never
450
- >,
451
- >(
452
- config: CreateOperationConfig<TModelName, TFields, TPrepareResult>,
453
- ): CreateOperationFunction<TModelName, TFields> {
454
- const dataSchema = generateCreateSchema(config.fields);
455
-
456
- const createOperation = async <TQueryArgs extends ModelQuery<TModelName>>({
457
- data,
458
- query,
459
- context,
460
- skipValidation,
461
- }: CreateOperationInput<TModelName, TFields, TQueryArgs>): Promise<
462
- GetPayload<TModelName, TQueryArgs>
463
- > => {
464
- // Throw error if query select is provided since we will not necessarily have a full result to return
465
- if (query?.select) {
466
- throw new Error(
467
- 'Query select is not supported for create operations. Use include instead.',
468
- );
469
- }
470
-
471
- // Validate data unless skipValidation is true (e.g., when GraphQL already validated)
472
- const validatedData = skipValidation ? data : dataSchema.parse(data);
473
-
474
- const baseOperationContext: OperationContext<
475
- GetPayload<TModelName>,
476
- { hasResult: false }
477
- > = {
478
- operation: 'create' as const,
479
- serviceContext: context,
480
- loadExisting: () => Promise.resolve(undefined),
481
- result: undefined,
482
- };
483
-
484
- // Authorization
485
- if (config.authorize && config.authorize.length > 0) {
486
- checkGlobalAuthorization(context, config.authorize);
487
- }
488
-
489
- // Step 1: Transform fields (OUTSIDE TRANSACTION)
490
- const [{ data: fieldsData, hooks: fieldsHooks }, preparedData] =
491
- await Promise.all([
492
- transformFields(config.fields, validatedData, {
493
- operation: 'create',
494
- serviceContext: context,
495
- allowOptionalFields: false,
496
- loadExisting: () => Promise.resolve(undefined),
497
- }),
498
- config.prepareComputedFields
499
- ? config.prepareComputedFields(validatedData, baseOperationContext)
500
- : Promise.resolve(undefined as TPrepareResult),
501
- ]);
502
-
503
- const allHooks: AnyOperationHooks = {
504
- beforeExecute: [
505
- ...(config.hooks?.beforeExecute ?? []),
506
- ...(fieldsHooks.beforeExecute ?? []),
507
- ],
508
- afterExecute: [
509
- ...(config.hooks?.afterExecute ?? []),
510
- ...(fieldsHooks.afterExecute ?? []),
511
- ],
512
- afterCommit: [
513
- ...(config.hooks?.afterCommit ?? []),
514
- ...(fieldsHooks.afterCommit ?? []),
515
- ],
516
- };
517
-
518
- // Check if we need to re-fetch after hooks (hooks may modify related records)
519
- const needsRefetch = hasPostExecuteHooks(allHooks);
520
-
521
- // Execute in transaction
522
- const transactionResult = await prisma.$transaction(async (tx) => {
523
- const txContext: TransactionalOperationContext<
524
- GetPayload<TModelName>,
525
- { hasResult: false }
526
- > = {
527
- ...baseOperationContext,
528
- tx,
529
- };
530
-
531
- // Run beforeExecute hooks
532
- await invokeHooks(allHooks.beforeExecute, txContext);
533
-
534
- // Run all async create data transformations
535
- const awaitedFieldsData =
536
- typeof fieldsData === 'function' ? await fieldsData(tx) : fieldsData;
537
-
538
- // If re-fetching, don't include relations in initial create
539
- const createQuery = needsRefetch
540
- ? ({} as { include: NonNullable<TQueryArgs['include']> })
541
- : ((query ?? {}) as { include: NonNullable<TQueryArgs['include']> });
542
-
543
- const result = await config.create({
544
- tx,
545
- data: { ...awaitedFieldsData.create, ...preparedData },
546
- query: createQuery,
547
- serviceContext: context,
548
- });
549
-
550
- // Run afterExecute hooks
551
- await invokeHooks(allHooks.afterExecute, {
552
- ...txContext,
553
- result,
554
- });
555
-
556
- return result;
557
- });
558
-
559
- // Run afterCommit hooks (outside transaction)
560
- await invokeHooks(allHooks.afterCommit, {
561
- ...baseOperationContext,
562
- result: transactionResult,
563
- });
564
-
565
- // Re-fetch if hooks existed and query includes relations
566
- if (needsRefetch && query?.include) {
567
- const delegate = makeGenericPrismaDelegate(prisma, config.model);
568
- const freshResult = await delegate.findUnique<TQueryArgs>({
569
- where: config.getWhereUnique(
570
- transactionResult as unknown as GetPayload<TModelName>,
571
- ),
572
- include: query.include,
573
- });
574
- if (!freshResult) {
575
- throw new NotFoundError(`${config.model} not found after create`);
576
- }
577
- return freshResult as GetPayload<TModelName, TQueryArgs>;
578
- }
579
-
580
- return transactionResult as GetPayload<TModelName, TQueryArgs>;
581
- };
582
- createOperation.$dataSchema = dataSchema;
583
- return createOperation;
584
- }
585
-
586
- /**
587
- * =========================================
588
- * Update Operation
589
- * =========================================
590
- */
591
-
592
- /**
593
- * Configuration for defining an update operation.
594
- *
595
- * Update operations modify existing database records with support for:
596
- * - Partial updates (only specified fields are updated)
597
- * - Authorization checks before modification
598
- * - Pre-transaction preparation step for heavy I/O
599
- * - Field-level validation and transformation
600
- * - Transaction management with lifecycle hooks
601
- *
602
- * @template TModelName - Prisma model name (e.g., 'user', 'post')
603
- * @template TFields - Record of field definitions
604
- * @template TPrepareResult - Type of data returned by prepare function
605
- */
606
- export interface UpdateOperationConfig<
607
- TModelName extends ModelPropName,
608
- TFields extends Record<string, AnyFieldDefinition>,
609
- TPrepareResult extends Record<string, unknown> | undefined = undefined,
610
- > {
611
- /**
612
- * Prisma model name
613
- */
614
- model: TModelName;
615
-
616
- /**
617
- * Field definitions for the update operation
618
- */
619
- fields: TFields;
620
-
621
- /**
622
- * Optional authorization check before updating.
623
- * Both global roles (strings) and instance roles (functions) are allowed.
624
- * Discrimination: `typeof check === 'string'` for global roles.
625
- *
626
- * @example
627
- * ```typescript
628
- * authorize: ['admin', userAuthorizer.roles.owner]
629
- * ```
630
- */
631
- authorize?: (GlobalRoleCheck | InstanceRoleCheck<GetPayload<TModelName>>)[];
632
-
633
- /**
634
- * Optional prepare step - runs BEFORE transaction
635
- * For heavy I/O, validation, data enrichment
636
- */
637
- prepareComputedFields?: (
638
- data: Partial<InferInput<TFields>>,
639
- ctx: OperationContext<GetPayload<TModelName>, { hasResult: false }>,
640
- ) => TPrepareResult | Promise<TPrepareResult>;
641
-
642
- /**
643
- * Execute the update operation. This function receives validated field data
644
- * and must return a Prisma update operation. It runs inside the transaction.
645
- */
646
- update: <TQueryArgs extends ModelQuery<TModelName>>(input: {
647
- tx: PrismaTransaction;
648
- where: WhereUniqueInput<TModelName>;
649
- data: InferFieldsUpdateOutput<TFields> & TPrepareResult;
650
- query: { include: NonNullable<TQueryArgs['include']> };
651
- serviceContext: ServiceContext;
652
- }) => Promise<
653
- Result<
654
- (typeof prisma)[TModelName],
655
- // We type the query parameter to ensure that the user always includes ...query into the update call
656
- { include: NonNullable<TQueryArgs['include']> },
657
- 'update'
658
- >
659
- >;
660
-
661
- /**
662
- * Optional hooks for the operation
663
- */
664
- hooks?: OperationHooks<GetPayload<TModelName>>;
665
- }
666
-
667
- /**
668
- * Input parameters for executing an update operation.
669
- *
670
- * @template TModelName - Prisma model name
671
- * @template TFields - Record of field definitions
672
- * @template TQueryArgs - Prisma query arguments (select/include)
673
- */
674
- export interface UpdateOperationInput<
675
- TModelName extends ModelPropName,
676
- TFields extends Record<string, AnyFieldDefinition>,
677
- TQueryArgs extends ModelQuery<TModelName>,
678
- > {
679
- /** Unique identifier to locate the record to update */
680
- where: WhereUniqueInput<TModelName>;
681
- /** Partial data containing only the fields to update */
682
- data: Partial<InferInput<TFields>>;
683
- /** Optional Prisma query arguments to shape the returned data */
684
- query?: TQueryArgs;
685
- /** Service context containing user info, request details, etc. */
686
- context: ServiceContext;
687
- /**
688
- * Skip Zod validation if data has already been validated (avoids double validation).
689
- * Set to true when validation happened at a higher layer (e.g., GraphQL input type validation).
690
- */
691
- skipValidation?: boolean;
692
- }
693
-
694
- type UpdateOperationFunction<
695
- TModelName extends ModelPropName,
696
- TFields extends Record<string, AnyFieldDefinition>,
697
- > = (<TQueryArgs extends ModelQuery<TModelName>>(
698
- input: UpdateOperationInput<TModelName, TFields, TQueryArgs>,
699
- ) => Promise<GetPayload<TModelName, TQueryArgs>>) & {
700
- $dataSchema: z.ZodObject<{
701
- [k in keyof TFields]: z.ZodOptional<TFields[k]['schema']>;
702
- }>;
703
- };
704
- /**
705
- * Defines a type-safe update operation for a Prisma model.
706
- *
707
- * Creates a reusable function for modifying existing records with built-in:
708
- * - Partial input validation (only specified fields are processed)
709
- * - Authorization checks with access to existing data
710
- * - Pre-transaction preparation for heavy I/O
711
- * - Transaction management
712
- * - Hook execution at each lifecycle phase
713
- *
714
- * @template TModelName - Prisma model name
715
- * @template TFields - Record of field definitions
716
- * @template TPrepareResult - Type of prepared data
717
- * @param config - Operation configuration
718
- * @returns Async function that executes the update operation
719
- * @throws {NotFoundError} If the record to update doesn't exist
720
- *
721
- * @example
722
- * ```typescript
723
- * const updateUser = defineUpdateOperation({
724
- * model: 'user',
725
- * fields: {
726
- * name: scalarField(z.string()),
727
- * email: scalarField(z.email()),
728
- * },
729
- * authorize: async (data, ctx) => {
730
- * const existing = await ctx.loadExisting();
731
- * // Check if user owns this record
732
- * },
733
- * update: ({ tx, where, data, query, serviceContext }) =>
734
- * tx.user.update({
735
- * where,
736
- * data: {
737
- * ...data,
738
- * updatedById: serviceContext.user?.id,
739
- * },
740
- * ...query,
741
- * }),
742
- * });
743
- *
744
- * // Usage
745
- * const user = await updateUser({
746
- * where: { id: userId },
747
- * data: { name: 'Jane' }, // Only update name
748
- * context: serviceContext,
749
- * });
750
- * ```
751
- */
752
- export function defineUpdateOperation<
753
- TModelName extends ModelPropName,
754
- TFields extends Record<string, AnyFieldDefinition>,
755
- TPrepareResult extends Record<string, unknown> | undefined = Record<
756
- string,
757
- never
758
- >,
759
- >(
760
- config: UpdateOperationConfig<TModelName, TFields, TPrepareResult>,
761
- ): UpdateOperationFunction<TModelName, TFields> {
762
- const dataSchema = generateCreateSchema(config.fields).partial();
763
-
764
- const updateOperation = async <TQueryArgs extends ModelQuery<TModelName>>({
765
- where,
766
- data: inputData,
767
- query,
768
- context,
769
- skipValidation,
770
- }: UpdateOperationInput<TModelName, TFields, TQueryArgs>): Promise<
771
- GetPayload<TModelName, TQueryArgs>
772
- > => {
773
- // Throw error if query select is provided since we will not necessarily have a full result to return
774
- if (query?.select) {
775
- throw new Error(
776
- 'Query select is not supported for update operations. Use include instead.',
777
- );
778
- }
779
-
780
- // Validate data unless skipValidation is true (e.g., when GraphQL already validated)
781
- const validatedData = (
782
- skipValidation ? inputData : dataSchema.parse(inputData)
783
- ) as Partial<InferInput<TFields>>;
784
-
785
- let existingItem: GetPayload<TModelName> | undefined;
786
-
787
- const delegate = makeGenericPrismaDelegate(prisma, config.model);
788
-
789
- const baseOperationContext: OperationContext<
790
- GetPayload<TModelName>,
791
- { hasResult: false }
792
- > = {
793
- operation: 'update' as const,
794
- serviceContext: context,
795
- loadExisting: async () => {
796
- if (existingItem) return existingItem;
797
- const result = await delegate.findUnique({
798
- where,
799
- });
800
- if (!result) throw new NotFoundError(`${config.model} not found`);
801
- existingItem = result;
802
- return result;
803
- },
804
- result: undefined,
805
- };
806
-
807
- // Authorization - check roles, lazy loading instance only if needed
808
- if (config.authorize && config.authorize.length > 0) {
809
- await checkInstanceAuthorization(
810
- context,
811
- baseOperationContext.loadExisting as () => Promise<
812
- GetPayload<TModelName>
813
- >,
814
- config.authorize,
815
- );
816
- }
817
-
818
- // Step 1: Transform fields (outside transaction)
819
- // Only transform fields provided in input
820
- const fieldsToTransform = Object.fromEntries(
821
- Object.entries(config.fields).filter(([key]) => key in validatedData),
822
- ) as TFields;
823
-
824
- const [{ data: fieldsData, hooks: fieldsHooks }, preparedData] =
825
- await Promise.all([
826
- transformFields(
827
- fieldsToTransform,
828
- validatedData as InferInput<TFields>,
829
- {
830
- operation: 'update',
831
- serviceContext: context,
832
- allowOptionalFields: true,
833
- loadExisting: baseOperationContext.loadExisting as () => Promise<
834
- Record<string, unknown>
835
- >,
836
- },
837
- ),
838
- config.prepareComputedFields
839
- ? config.prepareComputedFields(validatedData, baseOperationContext)
840
- : Promise.resolve(undefined as TPrepareResult),
841
- ]);
842
-
843
- // Combine config hooks with field hooks
844
- const allHooks: AnyOperationHooks = {
845
- beforeExecute: [
846
- ...(config.hooks?.beforeExecute ?? []),
847
- ...(fieldsHooks.beforeExecute ?? []),
848
- ],
849
- afterExecute: [
850
- ...(config.hooks?.afterExecute ?? []),
851
- ...(fieldsHooks.afterExecute ?? []),
852
- ],
853
- afterCommit: [
854
- ...(config.hooks?.afterCommit ?? []),
855
- ...(fieldsHooks.afterCommit ?? []),
856
- ],
857
- };
858
-
859
- // Check if we need to re-fetch after hooks (hooks may modify related records)
860
- const needsRefetch = hasPostExecuteHooks(allHooks);
861
-
862
- // Execute in transaction
863
- const transactionResult = await prisma.$transaction(async (tx) => {
864
- const txContext: TransactionalOperationContext<
865
- GetPayload<TModelName>,
866
- { hasResult: false }
867
- > = {
868
- ...baseOperationContext,
869
- tx,
870
- };
871
-
872
- // Run beforeExecute hooks
873
- await invokeHooks(allHooks.beforeExecute, txContext);
874
-
875
- // Run all async update data transformations
876
- const awaitedFieldsData =
877
- typeof fieldsData === 'function' ? await fieldsData(tx) : fieldsData;
878
-
879
- // If re-fetching, don't bother including relations in initial update
880
- const updateQuery = needsRefetch
881
- ? ({} as { include: NonNullable<TQueryArgs['include']> })
882
- : ((query ?? {}) as { include: NonNullable<TQueryArgs['include']> });
883
-
884
- const result = await config.update({
885
- tx,
886
- where,
887
- data: { ...awaitedFieldsData.update, ...preparedData },
888
- query: updateQuery,
889
- serviceContext: context,
890
- });
891
-
892
- // Run afterExecute hooks
893
- await invokeHooks(allHooks.afterExecute, {
894
- ...txContext,
895
- result,
896
- });
897
-
898
- return result;
899
- });
900
-
901
- // Run afterCommit hooks (outside transaction)
902
- await invokeHooks(allHooks.afterCommit, {
903
- ...baseOperationContext,
904
- result: transactionResult,
905
- });
906
-
907
- // Re-fetch if hooks existed and query includes relations
908
- if (needsRefetch && query?.include) {
909
- const freshResult = await delegate.findUnique<TQueryArgs>({
910
- where,
911
- include: query.include,
912
- });
913
- if (!freshResult) {
914
- throw new NotFoundError(`${config.model} not found after update`);
915
- }
916
- return freshResult as GetPayload<TModelName, TQueryArgs>;
917
- }
918
-
919
- return transactionResult as GetPayload<TModelName, TQueryArgs>;
920
- };
921
- updateOperation.$dataSchema = generateCreateSchema(config.fields).partial();
922
- return updateOperation;
923
- }
924
-
925
- /**
926
- * Configuration for defining a delete operation.
927
- *
928
- * Delete operations remove records from the database with support for:
929
- * - Authorization checks before deletion
930
- * - Transaction management
931
- * - Lifecycle hooks for cleanup operations
932
- * - Access to the record being deleted
933
- *
934
- * @template TModelName - Prisma model name (e.g., 'user', 'post')
935
- */
936
- export interface DeleteOperationConfig<TModelName extends ModelPropName> {
937
- /**
938
- * Prisma model name
939
- */
940
- model: TModelName;
941
-
942
- /**
943
- * Optional authorization check before deleting.
944
- * Both global roles (strings) and instance roles (functions) are allowed.
945
- * Discrimination: `typeof check === 'string'` for global roles.
946
- *
947
- * @example
948
- * ```typescript
949
- * authorize: ['admin', userAuthorizer.roles.owner]
950
- * ```
951
- */
952
- authorize?: (GlobalRoleCheck | InstanceRoleCheck<GetPayload<TModelName>>)[];
953
-
954
- /**
955
- * Execute the delete operation. This function receives the where clause
956
- * and must return a Prisma delete operation. It runs inside the transaction.
957
- */
958
- delete: <TQueryArgs extends ModelQuery<TModelName>>(input: {
959
- tx: PrismaTransaction;
960
- where: WhereUniqueInput<TModelName>;
961
- query: { include: NonNullable<TQueryArgs['include']> };
962
- serviceContext: ServiceContext;
963
- }) => Promise<
964
- Result<
965
- (typeof prisma)[TModelName],
966
- // We type the query parameter to ensure that the user always includes ...query into the delete call
967
- { include: NonNullable<TQueryArgs['include']> },
968
- 'delete'
969
- >
970
- >;
971
-
972
- /**
973
- * Optional hooks for the operation
974
- */
975
- hooks?: OperationHooks<GetPayload<TModelName>>;
976
- }
977
-
978
- /**
979
- * Input parameters for executing a delete operation.
980
- *
981
- * @template TModelName - Prisma model name
982
- * @template TQueryArgs - Prisma query arguments (select/include)
983
- */
984
- export interface DeleteOperationInput<
985
- TModelName extends ModelPropName,
986
- TQueryArgs extends ModelQuery<TModelName>,
987
- > {
988
- /** Unique identifier to locate the record to delete */
989
- where: WhereUniqueInput<TModelName>;
990
- /** Optional Prisma query arguments to shape the returned data */
991
- query?: TQueryArgs;
992
- /** Service context containing user info, request details, etc. */
993
- context: ServiceContext;
994
- }
995
-
996
- /**
997
- * Defines a type-safe delete operation for a Prisma model.
998
- *
999
- * Creates a reusable function for removing records with built-in:
1000
- * - Authorization checks with access to the record being deleted
1001
- * - Transaction management
1002
- * - Hook execution for cleanup operations (e.g., deleting associated files)
1003
- * - Returns the deleted record
1004
- *
1005
- * @template TModelName - Prisma model name
1006
- * @param config - Operation configuration
1007
- * @returns Async function that executes the delete operation
1008
- * @throws {NotFoundError} If the record to delete doesn't exist
1009
- *
1010
- * @example
1011
- * ```typescript
1012
- * const deleteUser = defineDeleteOperation({
1013
- * model: 'user',
1014
- * authorize: async (ctx) => {
1015
- * const existing = await ctx.loadExisting();
1016
- * // Check if user has permission to delete
1017
- * },
1018
- * delete: ({ tx, where, query, serviceContext }) =>
1019
- * tx.user.delete({
1020
- * where,
1021
- * ...query,
1022
- * }),
1023
- * hooks: {
1024
- * afterCommit: [
1025
- * async (ctx) => {
1026
- * // Clean up user's files, sessions, etc.
1027
- * await cleanupUserResources(ctx.result.id);
1028
- * },
1029
- * ],
1030
- * },
1031
- * });
1032
- *
1033
- * // Usage
1034
- * const deletedUser = await deleteUser({
1035
- * where: { id: userId },
1036
- * context: serviceContext,
1037
- * });
1038
- * ```
1039
- */
1040
- export function defineDeleteOperation<TModelName extends ModelPropName>(
1041
- config: DeleteOperationConfig<TModelName>,
1042
- ): <TQueryArgs extends ModelQuery<TModelName>>(
1043
- input: DeleteOperationInput<TModelName, TQueryArgs>,
1044
- ) => Promise<GetPayload<TModelName, TQueryArgs>> {
1045
- return async <TQueryArgs extends ModelQuery<TModelName>>({
1046
- where,
1047
- query,
1048
- context,
1049
- }: DeleteOperationInput<TModelName, TQueryArgs>) => {
1050
- // Throw error if query select is provided since we will not necessarily have a full result to return
1051
- if (query?.select) {
1052
- throw new Error(
1053
- 'Query select is not supported for delete operations. Use include instead.',
1054
- );
1055
- }
1056
-
1057
- const delegate = makeGenericPrismaDelegate(prisma, config.model);
1058
-
1059
- let existingItem: GetPayload<TModelName> | undefined;
1060
- const baseOperationContext: OperationContext<
1061
- GetPayload<TModelName>,
1062
- { hasResult: false }
1063
- > = {
1064
- operation: 'delete' as const,
1065
- serviceContext: context,
1066
- loadExisting: async () => {
1067
- if (existingItem) return existingItem;
1068
- const result = await delegate.findUnique({ where });
1069
- if (!result) throw new NotFoundError(`${config.model} not found`);
1070
- existingItem = result;
1071
- return result;
1072
- },
1073
- result: undefined,
1074
- };
1075
-
1076
- // Authorization - check roles, lazy loading instance only if needed
1077
- if (config.authorize && config.authorize.length > 0) {
1078
- await checkInstanceAuthorization(
1079
- context,
1080
- baseOperationContext.loadExisting as () => Promise<
1081
- GetPayload<TModelName>
1082
- >,
1083
- config.authorize,
1084
- );
1085
- }
1086
-
1087
- const allHooks: AnyOperationHooks = {
1088
- beforeExecute: config.hooks?.beforeExecute ?? [],
1089
- afterExecute: config.hooks?.afterExecute ?? [],
1090
- afterCommit: config.hooks?.afterCommit ?? [],
1091
- };
1092
-
1093
- // Execute in transaction
1094
- return prisma
1095
- .$transaction(async (tx) => {
1096
- const txContext: TransactionalOperationContext<
1097
- GetPayload<TModelName>,
1098
- { hasResult: false }
1099
- > = {
1100
- ...baseOperationContext,
1101
- tx,
1102
- };
1103
-
1104
- // Run beforeExecute hooks
1105
- await invokeHooks(allHooks.beforeExecute, txContext);
1106
-
1107
- // Execute delete operation
1108
- const result = await config.delete({
1109
- tx,
1110
- where,
1111
- query: (query ?? {}) as {
1112
- include: NonNullable<TQueryArgs['include']>;
1113
- },
1114
- serviceContext: context,
1115
- });
1116
-
1117
- // Run afterExecute hooks
1118
- await invokeHooks(allHooks.afterExecute, {
1119
- ...txContext,
1120
- result,
1121
- });
1122
-
1123
- return result;
1124
- })
1125
- .then(async (result) => {
1126
- // Run afterCommit hooks (outside transaction)
1127
- await invokeHooks(allHooks.afterCommit, {
1128
- ...baseOperationContext,
1129
- result,
1130
- });
1131
- return result as GetPayload<TModelName, TQueryArgs>;
1132
- });
1133
- };
1134
- }