@baseplate-dev/fastify-generators 0.6.3 → 0.6.5

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 (130) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/dist/constants/fastify-packages.d.ts +3 -3
  3. package/dist/constants/fastify-packages.js +3 -3
  4. package/dist/generators/auth/auth-roles/auth-roles.generator.d.ts +1 -0
  5. package/dist/generators/auth/auth-roles/auth-roles.generator.d.ts.map +1 -1
  6. package/dist/generators/auth/auth-roles/auth-roles.generator.js +5 -1
  7. package/dist/generators/auth/auth-roles/auth-roles.generator.js.map +1 -1
  8. package/dist/generators/auth/auth-roles/templates/module/constants/auth-roles.constants.ts +4 -3
  9. package/dist/generators/pothos/pothos-prisma-enum/pothos-prisma-enum.generator.d.ts +1 -0
  10. package/dist/generators/pothos/pothos-prisma-enum/pothos-prisma-enum.generator.d.ts.map +1 -1
  11. package/dist/generators/pothos/pothos-prisma-enum/pothos-prisma-enum.generator.js +11 -2
  12. package/dist/generators/pothos/pothos-prisma-enum/pothos-prisma-enum.generator.js.map +1 -1
  13. package/dist/generators/prisma/_shared/build-data-helpers/build-schema-fragments.d.ts +47 -0
  14. package/dist/generators/prisma/_shared/build-data-helpers/build-schema-fragments.d.ts.map +1 -0
  15. package/dist/generators/prisma/_shared/build-data-helpers/build-schema-fragments.js +56 -0
  16. package/dist/generators/prisma/_shared/build-data-helpers/build-schema-fragments.js.map +1 -0
  17. package/dist/generators/prisma/_shared/build-data-helpers/build-transform-operation-parts.d.ts +71 -0
  18. package/dist/generators/prisma/_shared/build-data-helpers/build-transform-operation-parts.d.ts.map +1 -0
  19. package/dist/generators/prisma/_shared/build-data-helpers/build-transform-operation-parts.js +153 -0
  20. package/dist/generators/prisma/_shared/build-data-helpers/build-transform-operation-parts.js.map +1 -0
  21. package/dist/generators/prisma/_shared/build-data-helpers/generate-authorization-statements.d.ts +46 -0
  22. package/dist/generators/prisma/_shared/build-data-helpers/generate-authorization-statements.d.ts.map +1 -0
  23. package/dist/generators/prisma/_shared/build-data-helpers/generate-authorization-statements.js +44 -0
  24. package/dist/generators/prisma/_shared/build-data-helpers/generate-authorization-statements.js.map +1 -0
  25. package/dist/generators/prisma/_shared/build-data-helpers/generate-relation-build-data.d.ts +6 -0
  26. package/dist/generators/prisma/_shared/build-data-helpers/generate-relation-build-data.d.ts.map +1 -1
  27. package/dist/generators/prisma/_shared/build-data-helpers/generate-relation-build-data.js +13 -1
  28. package/dist/generators/prisma/_shared/build-data-helpers/generate-relation-build-data.js.map +1 -1
  29. package/dist/generators/prisma/_shared/build-data-helpers/generate-where-type.d.ts +24 -0
  30. package/dist/generators/prisma/_shared/build-data-helpers/generate-where-type.d.ts.map +1 -0
  31. package/dist/generators/prisma/_shared/build-data-helpers/generate-where-type.js +49 -0
  32. package/dist/generators/prisma/_shared/build-data-helpers/generate-where-type.js.map +1 -0
  33. package/dist/generators/prisma/_shared/field-definition-generators/generate-scalar-input-field.d.ts +1 -4
  34. package/dist/generators/prisma/_shared/field-definition-generators/generate-scalar-input-field.d.ts.map +1 -1
  35. package/dist/generators/prisma/_shared/field-definition-generators/generate-scalar-input-field.js +10 -2
  36. package/dist/generators/prisma/_shared/field-definition-generators/generate-scalar-input-field.js.map +1 -1
  37. package/dist/generators/prisma/_shared/field-definition-generators/types.d.ts +47 -1
  38. package/dist/generators/prisma/_shared/field-definition-generators/types.d.ts.map +1 -1
  39. package/dist/generators/prisma/data-utils/data-utils.generator.d.ts +17 -74
  40. package/dist/generators/prisma/data-utils/data-utils.generator.d.ts.map +1 -1
  41. package/dist/generators/prisma/data-utils/generated/index.d.ts +50 -145
  42. package/dist/generators/prisma/data-utils/generated/index.d.ts.map +1 -1
  43. package/dist/generators/prisma/data-utils/generated/template-paths.d.ts +5 -6
  44. package/dist/generators/prisma/data-utils/generated/template-paths.d.ts.map +1 -1
  45. package/dist/generators/prisma/data-utils/generated/template-paths.js +5 -6
  46. package/dist/generators/prisma/data-utils/generated/template-paths.js.map +1 -1
  47. package/dist/generators/prisma/data-utils/generated/template-renderers.d.ts +0 -21
  48. package/dist/generators/prisma/data-utils/generated/template-renderers.d.ts.map +1 -1
  49. package/dist/generators/prisma/data-utils/generated/template-renderers.js +1 -7
  50. package/dist/generators/prisma/data-utils/generated/template-renderers.js.map +1 -1
  51. package/dist/generators/prisma/data-utils/generated/ts-import-providers.d.ts +51 -159
  52. package/dist/generators/prisma/data-utils/generated/ts-import-providers.d.ts.map +1 -1
  53. package/dist/generators/prisma/data-utils/generated/ts-import-providers.js +32 -64
  54. package/dist/generators/prisma/data-utils/generated/ts-import-providers.js.map +1 -1
  55. package/dist/generators/prisma/data-utils/generated/typed-templates.d.ts +66 -142
  56. package/dist/generators/prisma/data-utils/generated/typed-templates.d.ts.map +1 -1
  57. package/dist/generators/prisma/data-utils/generated/typed-templates.js +47 -98
  58. package/dist/generators/prisma/data-utils/generated/typed-templates.js.map +1 -1
  59. package/dist/generators/prisma/data-utils/templates/src/utils/data-operations/define-transformer.ts +130 -0
  60. package/dist/generators/prisma/data-utils/templates/src/utils/data-operations/execute-transform-plan.ts +108 -0
  61. package/dist/generators/prisma/data-utils/templates/src/utils/data-operations/nested-transformers.ts +364 -0
  62. package/dist/generators/prisma/data-utils/templates/src/utils/data-operations/prepare-transformers.ts +73 -0
  63. package/dist/generators/prisma/data-utils/templates/src/utils/data-operations/prisma-types.ts +6 -83
  64. package/dist/generators/prisma/data-utils/templates/src/utils/data-operations/transformer-types.ts +118 -0
  65. package/dist/generators/prisma/prisma-authorizer-utils/generated/index.d.ts +34 -106
  66. package/dist/generators/prisma/prisma-authorizer-utils/generated/index.d.ts.map +1 -1
  67. package/dist/generators/prisma/prisma-authorizer-utils/generated/template-renderers.d.ts +17 -53
  68. package/dist/generators/prisma/prisma-authorizer-utils/generated/template-renderers.d.ts.map +1 -1
  69. package/dist/generators/prisma/prisma-authorizer-utils/generated/typed-templates.d.ts +34 -106
  70. package/dist/generators/prisma/prisma-authorizer-utils/generated/typed-templates.d.ts.map +1 -1
  71. package/dist/generators/prisma/prisma-authorizer-utils/prisma-authorizer-utils.generator.d.ts +17 -53
  72. package/dist/generators/prisma/prisma-authorizer-utils/prisma-authorizer-utils.generator.d.ts.map +1 -1
  73. package/dist/generators/prisma/prisma-authorizer-utils/templates/src/utils/authorizers.ts +11 -11
  74. package/dist/generators/prisma/prisma-data-create/prisma-data-create.generator.d.ts +39 -49
  75. package/dist/generators/prisma/prisma-data-create/prisma-data-create.generator.d.ts.map +1 -1
  76. package/dist/generators/prisma/prisma-data-create/prisma-data-create.generator.js +82 -45
  77. package/dist/generators/prisma/prisma-data-create/prisma-data-create.generator.js.map +1 -1
  78. package/dist/generators/prisma/prisma-data-delete/prisma-data-delete.generator.d.ts +39 -52
  79. package/dist/generators/prisma/prisma-data-delete/prisma-data-delete.generator.d.ts.map +1 -1
  80. package/dist/generators/prisma/prisma-data-delete/prisma-data-delete.generator.js +48 -32
  81. package/dist/generators/prisma/prisma-data-delete/prisma-data-delete.generator.js.map +1 -1
  82. package/dist/generators/prisma/prisma-data-nested-field/nested-field-writer.d.ts +16 -2
  83. package/dist/generators/prisma/prisma-data-nested-field/nested-field-writer.d.ts.map +1 -1
  84. package/dist/generators/prisma/prisma-data-nested-field/nested-field-writer.js +282 -138
  85. package/dist/generators/prisma/prisma-data-nested-field/nested-field-writer.js.map +1 -1
  86. package/dist/generators/prisma/prisma-data-nested-field/prisma-data-nested-field.generator.d.ts +19 -55
  87. package/dist/generators/prisma/prisma-data-nested-field/prisma-data-nested-field.generator.d.ts.map +1 -1
  88. package/dist/generators/prisma/prisma-data-nested-field/prisma-data-nested-field.generator.js +18 -4
  89. package/dist/generators/prisma/prisma-data-nested-field/prisma-data-nested-field.generator.js.map +1 -1
  90. package/dist/generators/prisma/prisma-data-service/prisma-data-service.generator.d.ts +37 -92
  91. package/dist/generators/prisma/prisma-data-service/prisma-data-service.generator.d.ts.map +1 -1
  92. package/dist/generators/prisma/prisma-data-service/prisma-data-service.generator.js +86 -29
  93. package/dist/generators/prisma/prisma-data-service/prisma-data-service.generator.js.map +1 -1
  94. package/dist/generators/prisma/prisma-data-update/prisma-data-update.generator.d.ts +40 -52
  95. package/dist/generators/prisma/prisma-data-update/prisma-data-update.generator.d.ts.map +1 -1
  96. package/dist/generators/prisma/prisma-data-update/prisma-data-update.generator.js +102 -49
  97. package/dist/generators/prisma/prisma-data-update/prisma-data-update.generator.js.map +1 -1
  98. package/dist/generators/prisma/prisma-query-filter-utils/generated/index.d.ts +51 -159
  99. package/dist/generators/prisma/prisma-query-filter-utils/generated/index.d.ts.map +1 -1
  100. package/dist/generators/prisma/prisma-query-filter-utils/generated/template-renderers.d.ts +17 -53
  101. package/dist/generators/prisma/prisma-query-filter-utils/generated/template-renderers.d.ts.map +1 -1
  102. package/dist/generators/prisma/prisma-query-filter-utils/generated/typed-templates.d.ts +68 -212
  103. package/dist/generators/prisma/prisma-query-filter-utils/generated/typed-templates.d.ts.map +1 -1
  104. package/dist/generators/prisma/prisma-query-filter-utils/prisma-query-filter-utils.generator.d.ts +17 -53
  105. package/dist/generators/prisma/prisma-query-filter-utils/prisma-query-filter-utils.generator.d.ts.map +1 -1
  106. package/dist/generators/prisma/prisma-relation-field/prisma-relation-field.generator.js +1 -1
  107. package/dist/generators/prisma/prisma-relation-field/prisma-relation-field.generator.js.map +1 -1
  108. package/dist/writers/prisma-schema/fields.d.ts +5 -1
  109. package/dist/writers/prisma-schema/fields.d.ts.map +1 -1
  110. package/dist/writers/prisma-schema/fields.js +8 -2
  111. package/dist/writers/prisma-schema/fields.js.map +1 -1
  112. package/package.json +8 -8
  113. package/dist/generators/prisma/_shared/build-data-helpers/generate-authorize-fragment.d.ts +0 -20
  114. package/dist/generators/prisma/_shared/build-data-helpers/generate-authorize-fragment.d.ts.map +0 -1
  115. package/dist/generators/prisma/_shared/build-data-helpers/generate-authorize-fragment.js +0 -28
  116. package/dist/generators/prisma/_shared/build-data-helpers/generate-authorize-fragment.js.map +0 -1
  117. package/dist/generators/prisma/_shared/build-data-helpers/generate-operation-callbacks.d.ts +0 -130
  118. package/dist/generators/prisma/_shared/build-data-helpers/generate-operation-callbacks.d.ts.map +0 -1
  119. package/dist/generators/prisma/_shared/build-data-helpers/generate-operation-callbacks.js +0 -221
  120. package/dist/generators/prisma/_shared/build-data-helpers/generate-operation-callbacks.js.map +0 -1
  121. package/dist/generators/prisma/_shared/build-data-helpers/index.d.ts +0 -4
  122. package/dist/generators/prisma/_shared/build-data-helpers/index.d.ts.map +0 -1
  123. package/dist/generators/prisma/_shared/build-data-helpers/index.js +0 -4
  124. package/dist/generators/prisma/_shared/build-data-helpers/index.js.map +0 -1
  125. package/dist/generators/prisma/data-utils/templates/src/utils/data-operations/commit-operations.ts +0 -366
  126. package/dist/generators/prisma/data-utils/templates/src/utils/data-operations/compose-operations.ts +0 -131
  127. package/dist/generators/prisma/data-utils/templates/src/utils/data-operations/field-definitions.ts +0 -777
  128. package/dist/generators/prisma/data-utils/templates/src/utils/data-operations/field-utils.ts +0 -201
  129. package/dist/generators/prisma/data-utils/templates/src/utils/data-operations/prisma-utils.ts +0 -90
  130. package/dist/generators/prisma/data-utils/templates/src/utils/data-operations/types.ts +0 -721
@@ -0,0 +1,130 @@
1
+ // @ts-nocheck
2
+
3
+ import type {
4
+ AfterExecuteHook,
5
+ BoundTransformer,
6
+ MaybePromise,
7
+ Transformer,
8
+ TransformerResult,
9
+ } from '$transformerTypes';
10
+ import type { ServiceContext } from '%serviceContextImports';
11
+
12
+ /**
13
+ * Configuration for `defineTransformer`.
14
+ *
15
+ * Provides a single `processInput` function that handles both create and update
16
+ * operations via a discriminated union on the `context` parameter.
17
+ *
18
+ * @template TInput - The input value type
19
+ * @template TCreateArgs - Tuple of additional create args
20
+ * @template TUpdateArgs - Tuple of additional update args
21
+ * @template TCreateOutput - Prisma data type for create operations
22
+ * @template TUpdateOutput - Prisma data type for update operations
23
+ */
24
+ export interface DefineTransformerConfig<
25
+ TInput,
26
+ TCreateArgs extends unknown[],
27
+ TUpdateArgs extends unknown[],
28
+ TCreateOutput,
29
+ TUpdateOutput,
30
+ > {
31
+ /**
32
+ * Process the input value for either a create or update operation.
33
+ *
34
+ * The `context` parameter is a discriminated union:
35
+ * - `{ type: 'create', args: TCreateArgs }` — creating a new record
36
+ * - `{ type: 'update', args: TUpdateArgs }` — updating an existing record
37
+ *
38
+ * Return `{ data: { create?, update? } }` with the Prisma-compatible data
39
+ * for each operation type, plus optional `afterExecute` hooks.
40
+ */
41
+ processInput(
42
+ value: TInput,
43
+ context:
44
+ | { type: 'create'; args: TCreateArgs }
45
+ | { type: 'update'; args: TUpdateArgs },
46
+ ctx: { serviceContext: ServiceContext },
47
+ ): MaybePromise<{
48
+ data?: { create?: TCreateOutput; update?: TUpdateOutput };
49
+ afterExecute?: AfterExecuteHook[];
50
+ }>;
51
+ }
52
+
53
+ /**
54
+ * Define a transformer from a single `processInput` function.
55
+ *
56
+ * This helper generates `forCreate(input, ...args)` and `forUpdate(input, ...args)`
57
+ * methods from one function that uses a discriminated union to distinguish operations.
58
+ *
59
+ * @example
60
+ * ```typescript
61
+ * const passwordTransformer = defineTransformer<string, [], [], string, string>({
62
+ * async processInput(value) {
63
+ * const hashed = await hash(value);
64
+ * return { data: { create: hashed, update: hashed } };
65
+ * },
66
+ * });
67
+ *
68
+ * // Usage:
69
+ * passwordTransformer.forCreate(password)
70
+ * passwordTransformer.forUpdate(password)
71
+ * ```
72
+ */
73
+ export function defineTransformer<
74
+ TInput,
75
+ TCreateArgs extends unknown[],
76
+ TUpdateArgs extends unknown[],
77
+ TCreateOutput,
78
+ TUpdateOutput,
79
+ >(
80
+ config: DefineTransformerConfig<
81
+ TInput,
82
+ TCreateArgs,
83
+ TUpdateArgs,
84
+ TCreateOutput,
85
+ TUpdateOutput
86
+ >,
87
+ ): Transformer<TInput, TCreateArgs, TUpdateArgs, TCreateOutput, TUpdateOutput> {
88
+ return {
89
+ forCreate(
90
+ input: TInput,
91
+ ...args: TCreateArgs
92
+ ): BoundTransformer<TCreateOutput> {
93
+ return {
94
+ async process(ctx): Promise<TransformerResult<TCreateOutput>> {
95
+ const result = await config.processInput(
96
+ input,
97
+ { type: 'create', args },
98
+ ctx,
99
+ );
100
+ return {
101
+ data: result.data?.create,
102
+ afterExecute: result.afterExecute,
103
+ };
104
+ },
105
+ };
106
+ },
107
+
108
+ forUpdate(
109
+ input: TInput | undefined,
110
+ ...args: TUpdateArgs
111
+ ): BoundTransformer<TUpdateOutput> {
112
+ return {
113
+ async process(ctx): Promise<TransformerResult<TUpdateOutput>> {
114
+ if (input === undefined) {
115
+ return {};
116
+ }
117
+ const result = await config.processInput(
118
+ input,
119
+ { type: 'update', args },
120
+ ctx,
121
+ );
122
+ return {
123
+ data: result.data?.update,
124
+ afterExecute: result.afterExecute,
125
+ };
126
+ },
127
+ };
128
+ },
129
+ };
130
+ }
@@ -0,0 +1,108 @@
1
+ // @ts-nocheck
2
+
3
+ import type {
4
+ AnyBoundTransformer,
5
+ InferTransformed,
6
+ InferUnresolvedTransformed,
7
+ TransformPlan,
8
+ } from '$transformerTypes';
9
+ import type { Prisma } from '%prismaGeneratedImports';
10
+
11
+ import { prisma } from '%prismaImports';
12
+
13
+ /**
14
+ * Resolve any deferred data in the transformed map.
15
+ *
16
+ * Transformer data can be either a direct value or a deferred function
17
+ * `(tx) => Promise<value>`. This resolves all deferred values inside
18
+ * the transaction.
19
+ */
20
+ async function resolveTransformed<
21
+ TTransformers extends Record<string, AnyBoundTransformer>,
22
+ >(
23
+ transformed: InferUnresolvedTransformed<TTransformers>,
24
+ tx: Prisma.TransactionClient,
25
+ ): Promise<InferTransformed<TTransformers>> {
26
+ const resolved: Record<string, unknown> = {};
27
+
28
+ for (const key of Object.keys(transformed)) {
29
+ const value = (transformed as Record<string, unknown>)[key];
30
+ resolved[key] =
31
+ typeof value === 'function'
32
+ ? await (value as (tx: Prisma.TransactionClient) => Promise<unknown>)(
33
+ tx,
34
+ )
35
+ : value;
36
+ }
37
+
38
+ return resolved as InferTransformed<TTransformers>;
39
+ }
40
+
41
+ /**
42
+ * Execute a transform plan inside a Prisma transaction.
43
+ *
44
+ * Resolves any deferred transformer data (functions that need `tx`),
45
+ * calls the `execute` callback with resolved data, then runs all
46
+ * `afterExecute` hooks collected from the transformers.
47
+ *
48
+ * If `refetch` is provided, calls it after the transaction with the
49
+ * result to reload the record with includes/relations. Cannot be used
50
+ * with `tx` since the parent transaction may not have committed yet.
51
+ *
52
+ * If `tx` is provided, reuses that transaction (for nested plans).
53
+ * Otherwise opens a new `$transaction`.
54
+ *
55
+ * @example
56
+ * ```typescript
57
+ * const result = await executeTransformPlan(plan, {
58
+ * execute: async ({ tx, transformed }) =>
59
+ * tx.todoList.create({ data: { ...rest, ...transformed } }),
60
+ * refetch: (item) =>
61
+ * prisma.todoList.findUniqueOrThrow({ where: { id: item.id }, ...query }),
62
+ * });
63
+ * ```
64
+ */
65
+ export async function executeTransformPlan<
66
+ TTransformers extends Record<string, AnyBoundTransformer>,
67
+ TResult,
68
+ TRefetchResult = TResult,
69
+ >(
70
+ plan: TransformPlan<TTransformers>,
71
+ config: {
72
+ tx?: Prisma.TransactionClient;
73
+ execute: (args: {
74
+ tx: Prisma.TransactionClient;
75
+ transformed: InferTransformed<TTransformers>;
76
+ }) => Promise<TResult>;
77
+ refetch?: (result: TResult) => Promise<TRefetchResult>;
78
+ },
79
+ ): Promise<TResult | TRefetchResult> {
80
+ const { execute, refetch } = config;
81
+
82
+ if (config.tx && refetch) {
83
+ throw new Error(
84
+ 'Cannot use refetch with an existing transaction — the parent transaction may not have committed yet.',
85
+ );
86
+ }
87
+
88
+ const runInTx = async (tx: Prisma.TransactionClient): Promise<TResult> => {
89
+ const resolved = await resolveTransformed(plan.transformed, tx);
90
+ const result = await execute({ tx, transformed: resolved });
91
+
92
+ for (const hook of plan.afterExecute) {
93
+ await hook({ tx, result });
94
+ }
95
+
96
+ return result;
97
+ };
98
+
99
+ const txResult = config.tx
100
+ ? await runInTx(config.tx)
101
+ : await prisma.$transaction(async (tx) => runInTx(tx));
102
+
103
+ if (refetch) {
104
+ return refetch(txResult);
105
+ }
106
+
107
+ return txResult;
108
+ }
@@ -0,0 +1,364 @@
1
+ // @ts-nocheck
2
+
3
+ import type { GetResult, ModelPropName } from '$prismaTypes';
4
+ import type {
5
+ BoundTransformer,
6
+ MaybePromise,
7
+ Transformer,
8
+ TransformerResult,
9
+ } from '$transformerTypes';
10
+ import type { Prisma } from '%prismaGeneratedImports';
11
+ import type { ServiceContext } from '%serviceContextImports';
12
+ import type { z } from 'zod';
13
+
14
+ /**
15
+ * =========================================
16
+ * Shared Types
17
+ * =========================================
18
+ */
19
+
20
+ /** Context passed to nested processCreate / processUpdate callbacks */
21
+ interface NestedProcessContext {
22
+ serviceContext: ServiceContext;
23
+ }
24
+
25
+ /**
26
+ * A deferred operation returned by processCreate / processUpdate.
27
+ * Called inside the transaction after the parent record is created/updated.
28
+ *
29
+ * @template TParentResult - The type of the parent record
30
+ */
31
+ type DeferredOperation<TParentResult> = (
32
+ tx: Prisma.TransactionClient,
33
+ parent: TParentResult,
34
+ ) => Promise<void>;
35
+
36
+ /**
37
+ * =========================================
38
+ * One-to-One Transformer
39
+ * =========================================
40
+ */
41
+
42
+ /**
43
+ * Configuration for a one-to-one nested transformer.
44
+ * Types are inferred from `parentModel`, `model`, and `schema`.
45
+ *
46
+ * @template TParentModelName - Prisma parent model name (for typing parent in deferred ops)
47
+ * @template TModelName - Prisma child model name (for typing existing)
48
+ * @template TInputSchema - Zod schema (for typing input)
49
+ */
50
+ export interface OneToOneTransformerConfig<
51
+ TParentModelName extends ModelPropName,
52
+ TModelName extends ModelPropName,
53
+ TInputSchema extends z.ZodType,
54
+ > {
55
+ /** Prisma parent model name — used for type inference only */
56
+ parentModel: TParentModelName;
57
+ /** Prisma child model name — used for type inference only */
58
+ model: TModelName;
59
+ /** Zod schema for the nested entity input — used for type inference only */
60
+ schema: TInputSchema;
61
+
62
+ /** Process a create operation. Returns a deferred operation for the transaction. */
63
+ processCreate: (
64
+ input: z.output<TInputSchema>,
65
+ ctx: NestedProcessContext,
66
+ ) => MaybePromise<DeferredOperation<GetResult<TParentModelName>>>;
67
+
68
+ /**
69
+ * Process an update operation. Only called when existing child is defined.
70
+ * If no existing child, `processCreate` is called instead.
71
+ * If omitted, updates are a no-op.
72
+ */
73
+ processUpdate?: (
74
+ input: z.output<TInputSchema>,
75
+ existing: GetResult<TModelName>,
76
+ ctx: NestedProcessContext,
77
+ ) => MaybePromise<DeferredOperation<GetResult<TParentModelName>>>;
78
+
79
+ /** Delete the nested entity. Called when input is `null` on update and existing is defined. */
80
+ processDelete: (
81
+ existing: GetResult<TModelName>,
82
+ ctx: NestedProcessContext,
83
+ ) => DeferredOperation<GetResult<TParentModelName>>;
84
+ }
85
+
86
+ /**
87
+ * Create a one-to-one nested transformer.
88
+ *
89
+ * Types are inferred from config — no manual generics needed:
90
+ * - `parent` in deferred ops is typed from `parentModel`
91
+ * - `existing` in processUpdate is typed from `model`
92
+ * - `input` in processCreate/processUpdate is typed from `schema`
93
+ *
94
+ * @example
95
+ * ```typescript
96
+ * const profileTransformer = oneToOneTransformer({
97
+ * parentModel: 'user',
98
+ * model: 'userProfile',
99
+ * schema: profileInputSchema,
100
+ * processCreate: (input, ctx) => async (tx, parent) => {
101
+ * // parent is typed as GetResult<'user'>
102
+ * // input is typed from profileInputSchema
103
+ * await tx.userProfile.create({ data: { ...input, userId: parent.id } });
104
+ * },
105
+ * processUpdate: (input, existing, ctx) => async (tx, parent) => { ... },
106
+ * processDelete: (existing) => async (tx, parent) => { ... },
107
+ * });
108
+ * ```
109
+ */
110
+ export function oneToOneTransformer<
111
+ TParentModelName extends ModelPropName,
112
+ TModelName extends ModelPropName,
113
+ TInputSchema extends z.ZodType,
114
+ >(
115
+ config: OneToOneTransformerConfig<TParentModelName, TModelName, TInputSchema>,
116
+ ): Transformer<
117
+ z.output<TInputSchema> | null | undefined,
118
+ [],
119
+ [
120
+ context: {
121
+ loadExisting: () => Promise<GetResult<TModelName> | null>;
122
+ },
123
+ ],
124
+ undefined,
125
+ undefined
126
+ > {
127
+ return {
128
+ forCreate(
129
+ input: z.output<TInputSchema> | null | undefined,
130
+ ): BoundTransformer<undefined> {
131
+ return {
132
+ async process(ctx): Promise<TransformerResult<undefined>> {
133
+ if (input === null || input === undefined) return {};
134
+
135
+ const deferredOp = await config.processCreate(input, ctx);
136
+ return {
137
+ afterExecute: [
138
+ async ({ tx, result }) => {
139
+ await deferredOp(tx, result as GetResult<TParentModelName>);
140
+ },
141
+ ],
142
+ };
143
+ },
144
+ };
145
+ },
146
+
147
+ forUpdate(
148
+ input: z.output<TInputSchema> | null | undefined,
149
+ context: {
150
+ loadExisting: () => Promise<GetResult<TModelName> | null>;
151
+ },
152
+ ): BoundTransformer<undefined> {
153
+ return {
154
+ async process(ctx): Promise<TransformerResult<undefined>> {
155
+ if (input === undefined) return {};
156
+
157
+ const existing = (await context.loadExisting()) ?? undefined;
158
+
159
+ if (input === null) {
160
+ if (!existing) return {};
161
+ const deferredOp = config.processDelete(existing, ctx);
162
+ return {
163
+ afterExecute: [
164
+ async ({ tx, result }) => {
165
+ await deferredOp(tx, result as GetResult<TParentModelName>);
166
+ },
167
+ ],
168
+ };
169
+ }
170
+
171
+ const deferredOp =
172
+ existing && config.processUpdate
173
+ ? await config.processUpdate(input, existing, ctx)
174
+ : await config.processCreate(input, ctx);
175
+ return {
176
+ afterExecute: [
177
+ async ({ tx, result }) => {
178
+ await deferredOp(tx, result as GetResult<TParentModelName>);
179
+ },
180
+ ],
181
+ };
182
+ },
183
+ };
184
+ },
185
+ };
186
+ }
187
+
188
+ /**
189
+ * =========================================
190
+ * One-to-Many Transformer
191
+ * =========================================
192
+ */
193
+
194
+ /**
195
+ * Configuration for a one-to-many nested transformer.
196
+ * Types are inferred from `parentModel`, `model`, and `schema`.
197
+ *
198
+ * @template TParentModelName - Prisma parent model name
199
+ * @template TModelName - Prisma child model name
200
+ * @template TInputSchema - Zod schema for each item
201
+ */
202
+ export interface OneToManyTransformerConfig<
203
+ TParentModelName extends ModelPropName,
204
+ TModelName extends ModelPropName,
205
+ TInputSchema extends z.ZodType,
206
+ > {
207
+ /** Prisma parent model name — used for type inference only */
208
+ parentModel: TParentModelName;
209
+ /** Prisma child model name — used for type inference only */
210
+ model: TModelName;
211
+ /** Zod schema for each item's input — used for type inference only */
212
+ schema: TInputSchema;
213
+
214
+ /**
215
+ * Compare an input item to an existing item. Return true if they represent
216
+ * the same entity (e.g., matching IDs). If omitted, all items are treated
217
+ * as creates and all existing items are deleted (delete + recreate pattern).
218
+ */
219
+ compareItem?: (
220
+ input: z.output<TInputSchema>,
221
+ existing: GetResult<TModelName>,
222
+ ) => boolean;
223
+
224
+ /** Process a create operation for a single item. */
225
+ processCreate: (
226
+ itemInput: z.output<TInputSchema>,
227
+ ctx: NestedProcessContext,
228
+ ) => MaybePromise<DeferredOperation<GetResult<TParentModelName>>>;
229
+
230
+ /**
231
+ * Process an update operation for a single item.
232
+ * Only called when `compareItem` finds a match.
233
+ * If omitted, matched items fall through to `processCreate`.
234
+ */
235
+ processUpdate?: (
236
+ itemInput: z.output<TInputSchema>,
237
+ existingItem: GetResult<TModelName>,
238
+ ctx: NestedProcessContext,
239
+ ) => MaybePromise<DeferredOperation<GetResult<TParentModelName>>>;
240
+
241
+ /** Delete removed items not present in the input array. Called inside the transaction after the main operation. */
242
+ deleteRemoved: (
243
+ tx: Prisma.TransactionClient,
244
+ removedItems: GetResult<TModelName>[],
245
+ parent: GetResult<TParentModelName>,
246
+ ) => Promise<void>;
247
+ }
248
+
249
+ /**
250
+ * Create a one-to-many nested transformer.
251
+ *
252
+ * Types are inferred from config — no manual generics needed.
253
+ * Diff/match logic is provided by the caller via `loadExisting` in `.forUpdate()`.
254
+ *
255
+ * @example
256
+ * ```typescript
257
+ * const imagesTransformer = oneToManyTransformer({
258
+ * parentModel: 'user',
259
+ * model: 'userImage',
260
+ * schema: imageInputSchema,
261
+ * processCreate: (item, ctx) => async (tx, parent) => { ... },
262
+ * processUpdate: (item, existing, ctx) => async (tx, parent) => { ... },
263
+ * deleteRemoved: async (tx, removed, parent) => { ... },
264
+ * });
265
+ * ```
266
+ */
267
+ export function oneToManyTransformer<
268
+ TParentModelName extends ModelPropName,
269
+ TModelName extends ModelPropName,
270
+ TInputSchema extends z.ZodType,
271
+ >(
272
+ config: OneToManyTransformerConfig<
273
+ TParentModelName,
274
+ TModelName,
275
+ TInputSchema
276
+ >,
277
+ ): Transformer<
278
+ z.output<TInputSchema>[] | undefined,
279
+ [],
280
+ [
281
+ context: {
282
+ loadExisting: () => Promise<GetResult<TModelName>[]>;
283
+ },
284
+ ],
285
+ undefined,
286
+ undefined
287
+ > {
288
+ return {
289
+ forCreate(
290
+ items: z.output<TInputSchema>[] | undefined,
291
+ ): BoundTransformer<undefined> {
292
+ return {
293
+ async process(ctx): Promise<TransformerResult<undefined>> {
294
+ if (!items || items.length === 0) return {};
295
+
296
+ const deferredOps = await Promise.all(
297
+ items.map(async (item) => config.processCreate(item, ctx)),
298
+ );
299
+
300
+ return {
301
+ afterExecute: [
302
+ async ({ tx, result }) => {
303
+ for (const op of deferredOps) {
304
+ await op(tx, result as GetResult<TParentModelName>);
305
+ }
306
+ },
307
+ ],
308
+ };
309
+ },
310
+ };
311
+ },
312
+
313
+ forUpdate(
314
+ items: z.output<TInputSchema>[] | undefined,
315
+ context: {
316
+ loadExisting: () => Promise<GetResult<TModelName>[]>;
317
+ },
318
+ ): BoundTransformer<undefined> {
319
+ return {
320
+ async process(ctx): Promise<TransformerResult<undefined>> {
321
+ if (items === undefined) return {};
322
+
323
+ const existing = await context.loadExisting();
324
+
325
+ const { compareItem } = config;
326
+ const matched = items.map((item) => ({
327
+ item,
328
+ existingItem: compareItem
329
+ ? existing.find((e) => compareItem(item, e))
330
+ : undefined,
331
+ }));
332
+
333
+ const matchedExisting = new Set(
334
+ matched.map((m) => m.existingItem).filter(Boolean),
335
+ );
336
+ const removedItems = existing.filter((e) => !matchedExisting.has(e));
337
+
338
+ const deferredOps = await Promise.all(
339
+ matched.map(async ({ item, existingItem }) =>
340
+ existingItem && config.processUpdate
341
+ ? config.processUpdate(item, existingItem, ctx)
342
+ : config.processCreate(item, ctx),
343
+ ),
344
+ );
345
+
346
+ return {
347
+ afterExecute: [
348
+ async ({ tx, result }) => {
349
+ await config.deleteRemoved(
350
+ tx,
351
+ removedItems,
352
+ result as GetResult<TParentModelName>,
353
+ );
354
+ for (const op of deferredOps) {
355
+ await op(tx, result as GetResult<TParentModelName>);
356
+ }
357
+ },
358
+ ],
359
+ };
360
+ },
361
+ };
362
+ },
363
+ };
364
+ }
@@ -0,0 +1,73 @@
1
+ // @ts-nocheck
2
+
3
+ import type {
4
+ AfterExecuteHook,
5
+ AnyBoundTransformer,
6
+ InferUnresolvedTransformed,
7
+ TransformPlan,
8
+ } from '$transformerTypes';
9
+ import type { ServiceContext } from '%serviceContextImports';
10
+
11
+ /**
12
+ * Configuration for `prepareTransformers`.
13
+ *
14
+ * @template TTransformers - Record of bound transformers (from `.forCreate()` / `.forUpdate()`)
15
+ */
16
+ export interface PrepareTransformersConfig<
17
+ TTransformers extends Record<string, AnyBoundTransformer>,
18
+ > {
19
+ /** Bound transformers to process. Each should be the result of `.forCreate()` or `.forUpdate()`. */
20
+ transformers: TTransformers;
21
+ /** Service context with user info, request details, etc. */
22
+ serviceContext: ServiceContext;
23
+ }
24
+
25
+ /**
26
+ * Process all bound transformers and collect their results into a `TransformPlan`.
27
+ *
28
+ * Each bound transformer's `process` method is called to produce Prisma-compatible
29
+ * data and optional afterExecute hooks. The results are aggregated into a plan
30
+ * that can be passed to `executeTransformPlan`.
31
+ *
32
+ * @template TTransformers - Record of bound transformers
33
+ * @param config - Configuration with bound transformers and service context
34
+ * @returns A `TransformPlan` with resolved data and collected hooks
35
+ *
36
+ * @example
37
+ * ```typescript
38
+ * const plan = await prepareTransformers({
39
+ * transformers: {
40
+ * coverPhoto: coverPhotoTransformer.forCreate(coverPhoto),
41
+ * },
42
+ * serviceContext,
43
+ * });
44
+ * ```
45
+ */
46
+ export async function prepareTransformers<
47
+ TTransformers extends Record<string, AnyBoundTransformer>,
48
+ >(
49
+ config: PrepareTransformersConfig<TTransformers>,
50
+ ): Promise<TransformPlan<TTransformers>> {
51
+ const { transformers, serviceContext } = config;
52
+
53
+ const entries = Object.entries(transformers);
54
+ const results = await Promise.all(
55
+ entries.map(async ([, boundTransformer]) =>
56
+ boundTransformer.process({ serviceContext }),
57
+ ),
58
+ );
59
+
60
+ const transformed = {} as InferUnresolvedTransformed<TTransformers>;
61
+ const afterExecute: AfterExecuteHook[] = [];
62
+
63
+ for (const [index, result] of results.entries()) {
64
+ const key = entries[index][0];
65
+ (transformed as Record<string, unknown>)[key] = result.data;
66
+
67
+ if (result.afterExecute) {
68
+ afterExecute.push(...result.afterExecute);
69
+ }
70
+ }
71
+
72
+ return { transformed, afterExecute };
73
+ }