@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
@@ -0,0 +1,366 @@
1
+ // @ts-nocheck
2
+
3
+ import type {
4
+ GetPayload,
5
+ ModelInclude,
6
+ ModelPropName,
7
+ WhereUniqueInput,
8
+ } from '$prismaTypes';
9
+ import type {
10
+ AnyFieldDefinition,
11
+ AnyOperationHooks,
12
+ CommitCreateConfig,
13
+ CommitDeleteConfig,
14
+ CommitUpdateConfig,
15
+ CreatePlan,
16
+ OperationContext,
17
+ TransactionalOperationContext,
18
+ UpdatePlan,
19
+ } from '$types';
20
+
21
+ import { invokeHooks } from '$fieldUtils';
22
+ import { makeGenericPrismaDelegate } from '$prismaUtils';
23
+ import { checkInstanceAuthorization } from '%authorizerUtilsImports';
24
+ import { NotFoundError } from '%errorHandlerServiceImports';
25
+ import { prisma } from '%prismaImports';
26
+
27
+ /**
28
+ * Validate that query does not use `select` — only `include` is supported.
29
+ * Prisma `select` changes the return shape, which is incompatible with our
30
+ * typed return values and re-fetch logic.
31
+ */
32
+ function validateQuery(query: unknown, operation: string): void {
33
+ if (query && typeof query === 'object') {
34
+ const unsupportedKeys = Object.entries(query)
35
+ .filter(([key, value]) => key !== 'include' && value !== undefined)
36
+ .map(([key]) => key);
37
+ if (unsupportedKeys.length > 0) {
38
+ throw new Error(
39
+ `Unsupported query keys for ${operation} operations: ${unsupportedKeys.join(', ')}. Only 'include' is supported.`,
40
+ );
41
+ }
42
+ }
43
+ }
44
+
45
+ /**
46
+ * Checks if any hooks are present that could modify data after the initial operation.
47
+ * Used to determine if a re-fetch is needed after operation completes.
48
+ */
49
+ function hasPostExecuteHooks(hooks: AnyOperationHooks): boolean {
50
+ return (
51
+ (hooks.afterExecute?.length ?? 0) > 0 ||
52
+ (hooks.afterCommit?.length ?? 0) > 0
53
+ );
54
+ }
55
+
56
+ /**
57
+ * Re-fetch a record after hooks have run, ensuring included relations are fresh.
58
+ *
59
+ * Uses the caller's `refetchWithQuery` if provided, otherwise falls back to
60
+ * `makeGenericPrismaDelegate` with `{ id: result.id }`. Throws an explicit
61
+ * error if the result has no `id` field and no custom refetch was provided.
62
+ */
63
+ async function refetchResult<
64
+ TModelName extends ModelPropName,
65
+ TIncludeArgs extends ModelInclude<TModelName>,
66
+ >(
67
+ model: TModelName,
68
+ result: GetPayload<TModelName>,
69
+ query: TIncludeArgs,
70
+ refetchWithQuery?: (
71
+ result: GetPayload<TModelName>,
72
+ query: TIncludeArgs,
73
+ ) => Promise<GetPayload<TModelName, TIncludeArgs>>,
74
+ ): Promise<GetPayload<TModelName, TIncludeArgs>> {
75
+ if (refetchWithQuery) {
76
+ return refetchWithQuery(result, query);
77
+ }
78
+
79
+ const resultRecord = result as Record<string, unknown>;
80
+ if (!('id' in resultRecord) || !resultRecord.id) {
81
+ throw new Error(
82
+ `Cannot refetch ${model}: result has no 'id' field. Provide a custom refetchWithQuery.`,
83
+ );
84
+ }
85
+
86
+ const delegate = makeGenericPrismaDelegate(prisma, model);
87
+ const freshResult = await delegate.findUnique({
88
+ where: { id: resultRecord.id } as WhereUniqueInput<TModelName>,
89
+ include: query.include as NonNullable<ModelInclude<TModelName>['include']>,
90
+ });
91
+ if (!freshResult) {
92
+ throw new NotFoundError(`${model} not found after operation`);
93
+ }
94
+ return freshResult as GetPayload<TModelName, TIncludeArgs>;
95
+ }
96
+
97
+ /**
98
+ * Commit a create operation plan.
99
+ *
100
+ * Opens a Prisma transaction and executes the full lifecycle:
101
+ * 1. Validate query (no `select`)
102
+ * 2. Run beforeExecute hooks (inside transaction)
103
+ * 3. Call the execute callback with plan data
104
+ * 4. Run afterExecute hooks (inside transaction, with result)
105
+ * 5. Run afterCommit hooks (outside transaction, with result)
106
+ * 6. Re-fetch if hooks modified related records and query includes relations
107
+ */
108
+ export async function commitCreate<
109
+ TModelName extends ModelPropName,
110
+ TFields extends Record<string, AnyFieldDefinition>,
111
+ TIncludeArgs extends ModelInclude<TModelName> = ModelInclude<TModelName>,
112
+ >(
113
+ plan: CreatePlan<TModelName, TFields>,
114
+ config: CommitCreateConfig<TModelName, TFields, TIncludeArgs>,
115
+ ): Promise<GetPayload<TModelName, TIncludeArgs>> {
116
+ validateQuery(config.query, 'create');
117
+
118
+ const { execute } = config;
119
+ const needsRefetch = hasPostExecuteHooks(plan.hooks);
120
+
121
+ const baseOperationContext: OperationContext<
122
+ GetPayload<TModelName>,
123
+ { hasResult: false }
124
+ > = {
125
+ operation: 'create',
126
+ serviceContext: plan.serviceContext,
127
+ loadExisting: () => Promise.resolve(undefined),
128
+ result: undefined,
129
+ };
130
+
131
+ const transactionResult = await prisma.$transaction(async (tx) => {
132
+ const txContext: TransactionalOperationContext<
133
+ GetPayload<TModelName>,
134
+ { hasResult: false }
135
+ > = {
136
+ ...baseOperationContext,
137
+ tx,
138
+ };
139
+
140
+ // Run beforeExecute hooks
141
+ await invokeHooks(plan.hooks.beforeExecute, txContext);
142
+
143
+ // If re-fetching, don't include relations in initial create
144
+ const createQuery =
145
+ needsRefetch || !config.query
146
+ ? ({} as ModelInclude<TModelName>)
147
+ : (config.query as ModelInclude<TModelName>);
148
+
149
+ const result = await execute({
150
+ tx,
151
+ data: plan.data,
152
+ query: createQuery,
153
+ serviceContext: plan.serviceContext,
154
+ });
155
+
156
+ // Run afterExecute hooks
157
+ await invokeHooks(plan.hooks.afterExecute, {
158
+ ...txContext,
159
+ result,
160
+ });
161
+
162
+ return result;
163
+ });
164
+
165
+ // Run afterCommit hooks (outside transaction)
166
+ await invokeHooks(plan.hooks.afterCommit, {
167
+ ...baseOperationContext,
168
+ result: transactionResult,
169
+ });
170
+
171
+ // Re-fetch if hooks modified related records and query includes relations
172
+ if (needsRefetch && config.query?.include) {
173
+ return refetchResult(
174
+ plan.model,
175
+ transactionResult as GetPayload<TModelName>,
176
+ config.query,
177
+ config.refetchWithQuery,
178
+ );
179
+ }
180
+
181
+ return transactionResult as GetPayload<TModelName, TIncludeArgs>;
182
+ }
183
+
184
+ /**
185
+ * Commit an update operation plan.
186
+ *
187
+ * Opens a Prisma transaction and executes the full lifecycle:
188
+ * 1. Validate query (no `select`)
189
+ * 2. Run beforeExecute hooks (inside transaction)
190
+ * 3. Call the execute callback with plan data
191
+ * 4. Run afterExecute hooks (inside transaction, with result)
192
+ * 5. Run afterCommit hooks (outside transaction, with result)
193
+ * 6. Re-fetch if hooks modified related records and query includes relations
194
+ */
195
+ export async function commitUpdate<
196
+ TModelName extends ModelPropName,
197
+ TFields extends Record<string, AnyFieldDefinition>,
198
+ TIncludeArgs extends ModelInclude<TModelName> = ModelInclude<TModelName>,
199
+ >(
200
+ plan: UpdatePlan<TModelName, TFields>,
201
+ config: CommitUpdateConfig<TModelName, TFields, TIncludeArgs>,
202
+ ): Promise<GetPayload<TModelName, TIncludeArgs>> {
203
+ validateQuery(config.query, 'update');
204
+
205
+ const { execute } = config;
206
+ const needsRefetch = hasPostExecuteHooks(plan.hooks);
207
+
208
+ const baseOperationContext: OperationContext<
209
+ GetPayload<TModelName>,
210
+ { hasResult: false }
211
+ > = {
212
+ operation: 'update',
213
+ serviceContext: plan.serviceContext,
214
+ loadExisting: plan.loadExisting as () => Promise<
215
+ GetPayload<TModelName> | undefined
216
+ >,
217
+ result: undefined,
218
+ };
219
+
220
+ const transactionResult = await prisma.$transaction(async (tx) => {
221
+ const txContext: TransactionalOperationContext<
222
+ GetPayload<TModelName>,
223
+ { hasResult: false }
224
+ > = {
225
+ ...baseOperationContext,
226
+ tx,
227
+ };
228
+
229
+ // Run beforeExecute hooks
230
+ await invokeHooks(plan.hooks.beforeExecute, txContext);
231
+
232
+ // If re-fetching, don't include relations in initial update
233
+ const updateQuery =
234
+ needsRefetch || !config.query
235
+ ? ({} as ModelInclude<TModelName>)
236
+ : (config.query as ModelInclude<TModelName>);
237
+
238
+ const result = await execute({
239
+ tx,
240
+ data: plan.data,
241
+ query: updateQuery,
242
+ serviceContext: plan.serviceContext,
243
+ });
244
+
245
+ // Run afterExecute hooks
246
+ await invokeHooks(plan.hooks.afterExecute, {
247
+ ...txContext,
248
+ result,
249
+ });
250
+
251
+ return result;
252
+ });
253
+
254
+ // Run afterCommit hooks (outside transaction)
255
+ await invokeHooks(plan.hooks.afterCommit, {
256
+ ...baseOperationContext,
257
+ result: transactionResult,
258
+ });
259
+
260
+ // Re-fetch if hooks modified related records and query includes relations
261
+ if (needsRefetch && config.query?.include) {
262
+ return refetchResult(
263
+ plan.model,
264
+ transactionResult as GetPayload<TModelName>,
265
+ config.query,
266
+ config.refetchWithQuery,
267
+ );
268
+ }
269
+
270
+ return transactionResult as GetPayload<TModelName, TIncludeArgs>;
271
+ }
272
+
273
+ /**
274
+ * Execute a delete operation.
275
+ *
276
+ * No compose step is needed since there are no fields to process.
277
+ * Handles authorization, hooks, transaction, and execution directly.
278
+ *
279
+ * 1. Validate query (no `select`)
280
+ * 2. Run authorization checks (if configured)
281
+ * 3. Open transaction
282
+ * 4. Run beforeExecute hooks (inside transaction)
283
+ * 5. Call the execute callback
284
+ * 6. Run afterExecute hooks (inside transaction, with result)
285
+ * 7. Run afterCommit hooks (outside transaction, with result)
286
+ */
287
+ export async function commitDelete<
288
+ TModelName extends ModelPropName,
289
+ TIncludeArgs extends ModelInclude<TModelName> = ModelInclude<TModelName>,
290
+ >(
291
+ config: CommitDeleteConfig<TModelName, TIncludeArgs>,
292
+ ): Promise<GetPayload<TModelName, TIncludeArgs>> {
293
+ validateQuery(config.query, 'delete');
294
+
295
+ const { context, execute, loadExisting: rawLoadExisting } = config;
296
+
297
+ // Memoize loadExisting if provided
298
+ let cached: GetPayload<TModelName> | undefined;
299
+ const loadExisting: () => Promise<GetPayload<TModelName> | undefined> =
300
+ rawLoadExisting
301
+ ? async () => {
302
+ cached ??= await rawLoadExisting();
303
+ return cached;
304
+ }
305
+ : () => Promise.resolve(undefined);
306
+
307
+ // Authorization
308
+ if (config.authorize && config.authorize.length > 0) {
309
+ await checkInstanceAuthorization(
310
+ context,
311
+ loadExisting as () => Promise<GetPayload<TModelName>>,
312
+ config.authorize,
313
+ );
314
+ }
315
+
316
+ const hooks: Required<AnyOperationHooks> = {
317
+ beforeExecute: [...(config.hooks?.beforeExecute ?? [])],
318
+ afterExecute: [...(config.hooks?.afterExecute ?? [])],
319
+ afterCommit: [...(config.hooks?.afterCommit ?? [])],
320
+ };
321
+
322
+ const baseOperationContext: OperationContext<
323
+ GetPayload<TModelName>,
324
+ { hasResult: false }
325
+ > = {
326
+ operation: 'delete',
327
+ serviceContext: context,
328
+ loadExisting,
329
+ result: undefined,
330
+ };
331
+
332
+ const transactionResult = await prisma.$transaction(async (tx) => {
333
+ const txContext: TransactionalOperationContext<
334
+ GetPayload<TModelName>,
335
+ { hasResult: false }
336
+ > = {
337
+ ...baseOperationContext,
338
+ tx,
339
+ };
340
+
341
+ // Run beforeExecute hooks
342
+ await invokeHooks(hooks.beforeExecute, txContext);
343
+
344
+ const result = await execute({
345
+ tx,
346
+ query: (config.query ?? {}) as ModelInclude<TModelName>,
347
+ serviceContext: context,
348
+ });
349
+
350
+ // Run afterExecute hooks
351
+ await invokeHooks(hooks.afterExecute, {
352
+ ...txContext,
353
+ result,
354
+ });
355
+
356
+ return result;
357
+ });
358
+
359
+ // Run afterCommit hooks (outside transaction)
360
+ await invokeHooks(hooks.afterCommit, {
361
+ ...baseOperationContext,
362
+ result: transactionResult,
363
+ });
364
+
365
+ return transactionResult as GetPayload<TModelName, TIncludeArgs>;
366
+ }
@@ -0,0 +1,131 @@
1
+ // @ts-nocheck
2
+
3
+ import type { GetPayload, ModelPropName } from '$prismaTypes';
4
+ import type {
5
+ AnyFieldDefinition,
6
+ AnyOperationHooks,
7
+ ComposeCreateConfig,
8
+ ComposeUpdateConfig,
9
+ InferInput,
10
+ } from '$types';
11
+
12
+ import { transformFields } from '$fieldUtils';
13
+ import { CreatePlan, UpdatePlan } from '$types';
14
+ import {
15
+ checkGlobalAuthorization,
16
+ checkInstanceAuthorization,
17
+ } from '%authorizerUtilsImports';
18
+
19
+ /**
20
+ * Compose a create operation plan.
21
+ *
22
+ * Processes field definitions via `transformFields`, collecting data transformations
23
+ * and hooks. Returns an immutable plan that can be transformed via `mapData` and
24
+ * `addHook` before committing.
25
+ *
26
+ * This is the pre-commit phase — field processing like `fileField` may do DB reads
27
+ * for validation, so this is not a pure function.
28
+ */
29
+ export async function composeCreate<
30
+ TModelName extends ModelPropName,
31
+ TFields extends Record<string, AnyFieldDefinition>,
32
+ >(
33
+ config: ComposeCreateConfig<TModelName, TFields>,
34
+ ): Promise<CreatePlan<TModelName, TFields>> {
35
+ const { fields, input, context, authorize } = config;
36
+
37
+ // Authorization — fail fast before field processing
38
+ if (authorize && authorize.length > 0) {
39
+ checkGlobalAuthorization(context, authorize);
40
+ }
41
+
42
+ const { data: fieldsData, hooks: fieldsHooks } = await transformFields(
43
+ fields,
44
+ input,
45
+ {
46
+ operation: 'create',
47
+ serviceContext: context,
48
+ allowOptionalFields: false,
49
+ loadExisting: () => Promise.resolve(undefined),
50
+ },
51
+ );
52
+
53
+ const hooks: Required<AnyOperationHooks> = {
54
+ beforeExecute: [...(fieldsHooks.beforeExecute ?? [])],
55
+ afterExecute: [...(fieldsHooks.afterExecute ?? [])],
56
+ afterCommit: [...(fieldsHooks.afterCommit ?? [])],
57
+ };
58
+
59
+ return new CreatePlan({
60
+ model: config.model,
61
+ data: fieldsData.create,
62
+ hooks,
63
+ serviceContext: context,
64
+ });
65
+ }
66
+
67
+ /**
68
+ * Compose an update operation plan.
69
+ *
70
+ * Processes field definitions via `transformFields`, collecting data transformations
71
+ * and hooks. The caller provides a `loadExisting` function that controls how the
72
+ * existing item is fetched (e.g., with extra includes for authorization or diffing).
73
+ *
74
+ * Returns an immutable plan that can be transformed via `mapData` and `addHook`.
75
+ */
76
+ export async function composeUpdate<
77
+ TModelName extends ModelPropName,
78
+ TFields extends Record<string, AnyFieldDefinition>,
79
+ >(
80
+ config: ComposeUpdateConfig<TModelName, TFields>,
81
+ ): Promise<UpdatePlan<TModelName, TFields>> {
82
+ const {
83
+ fields,
84
+ input,
85
+ context,
86
+ loadExisting: rawLoadExisting,
87
+ authorize,
88
+ } = config;
89
+
90
+ // Memoize loadExisting so multiple callers (authorization, field processing, developer inspection) share one fetch
91
+ let cached: GetPayload<TModelName> | undefined;
92
+ const loadExisting = async (): Promise<GetPayload<TModelName>> => {
93
+ cached ??= await rawLoadExisting();
94
+ return cached;
95
+ };
96
+
97
+ // Authorization — fail fast before field processing
98
+ if (authorize && authorize.length > 0) {
99
+ await checkInstanceAuthorization(context, loadExisting, authorize);
100
+ }
101
+
102
+ // Only transform fields present in input
103
+ const fieldsToTransform = Object.fromEntries(
104
+ Object.entries(fields).filter(([key]) => key in (input as object)),
105
+ ) as TFields;
106
+
107
+ const { data: fieldsData, hooks: fieldsHooks } = await transformFields(
108
+ fieldsToTransform,
109
+ input as InferInput<TFields>,
110
+ {
111
+ operation: 'update',
112
+ serviceContext: context,
113
+ allowOptionalFields: true,
114
+ loadExisting: loadExisting as () => Promise<Record<string, unknown>>,
115
+ },
116
+ );
117
+
118
+ const hooks: Required<AnyOperationHooks> = {
119
+ beforeExecute: [...(fieldsHooks.beforeExecute ?? [])],
120
+ afterExecute: [...(fieldsHooks.afterExecute ?? [])],
121
+ afterCommit: [...(fieldsHooks.afterCommit ?? [])],
122
+ };
123
+
124
+ return new UpdatePlan({
125
+ model: config.model,
126
+ data: fieldsData.update,
127
+ hooks,
128
+ serviceContext: context,
129
+ loadExisting,
130
+ });
131
+ }
@@ -25,7 +25,7 @@ import {
25
25
  generateCreateSchema,
26
26
  invokeHooks,
27
27
  transformFields,
28
- } from '$defineOperations';
28
+ } from '$fieldUtils';
29
29
  import { makeGenericPrismaDelegate } from '$prismaUtils';
30
30
  import { prisma } from '%prismaImports';
31
31
 
@@ -37,7 +37,7 @@ import { prisma } from '%prismaImports';
37
37
  *
38
38
  * @template TSchema - The Zod schema type for validation
39
39
  * @param schema - Zod schema for validation
40
- * @returns Field definition
40
+ * @returns Scalar field definition that maps validated input directly to Prisma create/update data
41
41
  *
42
42
  * @example
43
43
  * ```typescript
@@ -213,15 +213,13 @@ export interface NestedOneToOneFieldConfig<
213
213
  * avatar: fileField(avatarFileCategory),
214
214
  * },
215
215
  * getWhereUnique: (parent) => ({ userId: parent.id }),
216
- * buildData: (data) => ({
217
- * create: {
218
- * bio: data.create.bio,
219
- * avatar: data.create.avatar ? { connect: { id: data.create.avatar } } : undefined,
220
- * },
221
- * update: {
222
- * bio: data.update.bio,
223
- * avatar: data.update.avatar ? { connect: { id: data.update.avatar } } : undefined,
224
- * },
216
+ * buildCreateData: (data) => ({
217
+ * bio: data.bio,
218
+ * avatar: data.avatar ? { connect: { id: data.avatar } } : undefined,
219
+ * }),
220
+ * buildUpdateData: (data) => ({
221
+ * bio: data.bio,
222
+ * avatar: data.avatar ? { connect: { id: data.avatar } } : undefined,
225
223
  * }),
226
224
  * }),
227
225
  * };
@@ -315,8 +313,6 @@ export function nestedOneToOneField<
315
313
  ],
316
314
  afterExecute: [
317
315
  async (ctx) => {
318
- const awaitedData =
319
- typeof data === 'function' ? await data(ctx.tx) : data;
320
316
  const whereUnique = config.getWhereUnique(
321
317
  ctx.result as GetPayload<TParentModelName>,
322
318
  );
@@ -331,7 +327,7 @@ export function nestedOneToOneField<
331
327
  const [builtCreate, builtUpdate] = await Promise.all([
332
328
  config.buildCreateData(
333
329
  {
334
- ...awaitedData.create,
330
+ ...data.create,
335
331
  ...({
336
332
  [config.relationName]: { connect: parentWhereUnique },
337
333
  } as Record<
@@ -343,7 +339,7 @@ export function nestedOneToOneField<
343
339
  sharedCtx,
344
340
  ),
345
341
  config.buildUpdateData(
346
- awaitedData.update,
342
+ data.update,
347
343
  ctx.result as GetPayload<TParentModelName>,
348
344
  sharedCtx,
349
345
  ),
@@ -433,7 +429,8 @@ export interface NestedOneToManyFieldConfig<
433
429
 
434
430
  /**
435
431
  * Transform validated create field data into final Prisma structure for a single item.
436
- * The returned payload should not include the parent relation field, as it will be added automatically.
432
+ * The parent relation connect is included in the data parameter. You can pass it
433
+ * through directly (e.g., `(data) => data`) or restructure as needed.
437
434
  */
438
435
  buildCreateData: (
439
436
  data: InferFieldsCreateOutput<TFields> &
@@ -502,6 +499,9 @@ function expandWhereUnique<TModelName extends ModelPropName>(
502
499
  * - **Delete**: Existing items not present in the input array are removed
503
500
  * - **No Change**: Passing `undefined` leaves the collection unchanged
504
501
  *
502
+ * **Important:** When a value is provided (not `undefined`), the input array is treated
503
+ * as the complete collection. Existing items not present in the array will be deleted.
504
+ *
505
505
  * All operations are performed atomically within the parent operation's transaction,
506
506
  * ensuring data consistency even if the operation fails.
507
507
  *
@@ -510,7 +510,7 @@ function expandWhereUnique<TModelName extends ModelPropName>(
510
510
  * @template TRelationName - Relation field name on child model
511
511
  * @template TFields - Field definitions for each child item
512
512
  * @param config - Configuration object for the one-to-many relationship
513
- * @returns Field definition for use in `defineCreateOperation` or `defineUpdateOperation`
513
+ * @returns Field definition for use with `composeCreate`/`composeUpdate` and `commitCreate`/`commitUpdate`
514
514
  *
515
515
  * @example
516
516
  * ```typescript
@@ -524,10 +524,8 @@ function expandWhereUnique<TModelName extends ModelPropName>(
524
524
  * caption: scalarField(z.string()),
525
525
  * },
526
526
  * getWhereUnique: (input) => input.id ? { id: input.id } : undefined,
527
- * buildData: (data) => ({
528
- * create: { caption: data.caption },
529
- * update: { caption: data.caption },
530
- * }),
527
+ * buildCreateData: (data) => ({ caption: data.caption }),
528
+ * buildUpdateData: (data) => ({ caption: data.caption }),
531
529
  * }),
532
530
  * };
533
531
  *
@@ -628,7 +626,7 @@ export function nestedOneToManyField<
628
626
  const processedItems = await Promise.all(
629
627
  value.map(async (itemInput, idx) => {
630
628
  const whereUnique =
631
- existingModel && config.getWhereUnique(itemInput, existingModel);
629
+ existingModel && getWhereUnique(itemInput, existingModel);
632
630
 
633
631
  const { data, hooks } = await transformFields(
634
632
  config.fields,
@@ -697,11 +695,6 @@ export function nestedOneToManyField<
697
695
  // Upsert items
698
696
  await Promise.all(
699
697
  processedItems.map(async (item, idx) => {
700
- const awaitedData =
701
- typeof item.data === 'function'
702
- ? await item.data(ctx.tx)
703
- : item.data;
704
-
705
698
  const parentWhereUnique = config.parentModel.getWhereUnique(
706
699
  ctx.result,
707
700
  );
@@ -718,7 +711,7 @@ export function nestedOneToManyField<
718
711
  const [builtCreate, builtUpdate] = await Promise.all([
719
712
  config.buildCreateData(
720
713
  {
721
- ...awaitedData.create,
714
+ ...item.data.create,
722
715
  ...({
723
716
  [config.relationName]: { connect: parentWhereUnique },
724
717
  } as Record<
@@ -729,12 +722,15 @@ export function nestedOneToManyField<
729
722
  ctx.result,
730
723
  sharedCtx,
731
724
  ),
732
- config.buildUpdateData(awaitedData.update, ctx.result, sharedCtx),
725
+ config.buildUpdateData(item.data.update, ctx.result, sharedCtx),
733
726
  ]);
734
727
 
735
728
  results[idx] = item.whereUnique
736
729
  ? await prismaDelegate.upsert({
737
- where: item.whereUnique,
730
+ where: {
731
+ ...item.whereUnique,
732
+ ...whereFromOriginalModel,
733
+ } as WhereUniqueInput<TModelName>,
738
734
  create: builtCreate,
739
735
  update: builtUpdate,
740
736
  })