@opensaas/stack-core 0.10.0 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +177 -0
- package/dist/config/types.d.ts +190 -24
- package/dist/config/types.d.ts.map +1 -1
- package/dist/context/index.d.ts.map +1 -1
- package/dist/context/index.js +14 -6
- package/dist/context/index.js.map +1 -1
- package/dist/context/nested-operations.d.ts.map +1 -1
- package/dist/context/nested-operations.js +2 -0
- package/dist/context/nested-operations.js.map +1 -1
- package/dist/fields/index.d.ts +86 -1
- package/dist/fields/index.d.ts.map +1 -1
- package/dist/fields/index.js +323 -13
- package/dist/fields/index.js.map +1 -1
- package/dist/hooks/index.d.ts +24 -12
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/index.js +0 -2
- package/dist/hooks/index.js.map +1 -1
- package/package.json +1 -1
- package/src/config/types.ts +235 -46
- package/src/context/index.ts +13 -0
- package/src/context/nested-operations.ts +2 -0
- package/src/fields/index.ts +372 -13
- package/src/hooks/index.ts +43 -35
- package/tests/hooks.test.ts +64 -0
- package/tsconfig.tsbuildinfo +1 -1
package/src/config/types.ts
CHANGED
|
@@ -43,7 +43,9 @@ export type FieldHooks<
|
|
|
43
43
|
*
|
|
44
44
|
* @example
|
|
45
45
|
* ```typescript
|
|
46
|
-
* resolveInput: async ({ inputValue, operation }) => {
|
|
46
|
+
* resolveInput: async ({ inputValue, operation, item }) => {
|
|
47
|
+
* // For create operations, item is undefined
|
|
48
|
+
* // For update operations, item is the existing record
|
|
47
49
|
* if (typeof inputValue === 'string' && !isHashedPassword(inputValue)) {
|
|
48
50
|
* return await hashPassword(inputValue)
|
|
49
51
|
* }
|
|
@@ -51,14 +53,25 @@ export type FieldHooks<
|
|
|
51
53
|
* }
|
|
52
54
|
* ```
|
|
53
55
|
*/
|
|
54
|
-
resolveInput?: (
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
56
|
+
resolveInput?: (
|
|
57
|
+
args:
|
|
58
|
+
| {
|
|
59
|
+
operation: 'create'
|
|
60
|
+
inputValue: GetFieldValueType<TTypeInfo['fields'], TFieldKey> | undefined
|
|
61
|
+
item: undefined
|
|
62
|
+
listKey: string
|
|
63
|
+
fieldName: TFieldKey
|
|
64
|
+
context: import('../access/types.js').AccessContext
|
|
65
|
+
}
|
|
66
|
+
| {
|
|
67
|
+
operation: 'update'
|
|
68
|
+
inputValue: GetFieldValueType<TTypeInfo['fields'], TFieldKey> | undefined
|
|
69
|
+
item: TTypeInfo['item']
|
|
70
|
+
listKey: string
|
|
71
|
+
fieldName: TFieldKey
|
|
72
|
+
context: import('../access/types.js').AccessContext
|
|
73
|
+
},
|
|
74
|
+
) =>
|
|
62
75
|
| Promise<GetFieldValueType<TTypeInfo['fields'], TFieldKey> | undefined>
|
|
63
76
|
| GetFieldValueType<TTypeInfo['fields'], TFieldKey>
|
|
64
77
|
| undefined
|
|
@@ -71,19 +84,34 @@ export type FieldHooks<
|
|
|
71
84
|
* @example
|
|
72
85
|
* ```typescript
|
|
73
86
|
* beforeOperation: async ({ resolvedValue, operation, item }) => {
|
|
74
|
-
*
|
|
87
|
+
* // For create operations, item is undefined
|
|
88
|
+
* // For update/delete operations, item is the existing record
|
|
89
|
+
* if (operation === 'update' && item) {
|
|
90
|
+
* console.log(`Updating field from ${item.fieldName} to ${resolvedValue}`)
|
|
91
|
+
* }
|
|
75
92
|
* await sendAuditLog({ operation, item })
|
|
76
93
|
* }
|
|
77
94
|
* ```
|
|
78
95
|
*/
|
|
79
|
-
beforeOperation?: (
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
96
|
+
beforeOperation?: (
|
|
97
|
+
args:
|
|
98
|
+
| {
|
|
99
|
+
operation: 'create'
|
|
100
|
+
resolvedValue: GetFieldValueType<TTypeInfo['fields'], TFieldKey> | undefined
|
|
101
|
+
item: undefined
|
|
102
|
+
listKey: string
|
|
103
|
+
fieldName: TFieldKey
|
|
104
|
+
context: import('../access/types.js').AccessContext
|
|
105
|
+
}
|
|
106
|
+
| {
|
|
107
|
+
operation: 'update' | 'delete'
|
|
108
|
+
resolvedValue: GetFieldValueType<TTypeInfo['fields'], TFieldKey> | undefined
|
|
109
|
+
item: TTypeInfo['item']
|
|
110
|
+
listKey: string
|
|
111
|
+
fieldName: TFieldKey
|
|
112
|
+
context: import('../access/types.js').AccessContext
|
|
113
|
+
},
|
|
114
|
+
) => Promise<void> | void
|
|
87
115
|
|
|
88
116
|
/**
|
|
89
117
|
* Perform side effects after database operation
|
|
@@ -92,20 +120,38 @@ export type FieldHooks<
|
|
|
92
120
|
*
|
|
93
121
|
* @example
|
|
94
122
|
* ```typescript
|
|
95
|
-
* afterOperation: async ({ operation, value, item }) => {
|
|
123
|
+
* afterOperation: async ({ operation, value, item, originalItem }) => {
|
|
124
|
+
* // For query/create operations, originalItem is undefined
|
|
125
|
+
* // For update/delete operations, originalItem is the item before the operation
|
|
126
|
+
* if (operation === 'update' && originalItem) {
|
|
127
|
+
* console.log('Changed from:', originalItem[fieldName], 'to:', value)
|
|
128
|
+
* }
|
|
96
129
|
* await invalidateCache({ listKey, itemId: item.id })
|
|
97
130
|
* await sendWebhook({ operation, item })
|
|
98
131
|
* }
|
|
99
132
|
* ```
|
|
100
133
|
*/
|
|
101
|
-
afterOperation?: (
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
134
|
+
afterOperation?: (
|
|
135
|
+
args:
|
|
136
|
+
| {
|
|
137
|
+
operation: 'query' | 'create'
|
|
138
|
+
value: GetFieldValueType<TTypeInfo['fields'], TFieldKey> | undefined
|
|
139
|
+
item: TTypeInfo['item']
|
|
140
|
+
originalItem: undefined
|
|
141
|
+
listKey: string
|
|
142
|
+
fieldName: TFieldKey
|
|
143
|
+
context: import('../access/types.js').AccessContext
|
|
144
|
+
}
|
|
145
|
+
| {
|
|
146
|
+
operation: 'update' | 'delete'
|
|
147
|
+
value: GetFieldValueType<TTypeInfo['fields'], TFieldKey> | undefined
|
|
148
|
+
item: TTypeInfo['item']
|
|
149
|
+
originalItem: TTypeInfo['item']
|
|
150
|
+
listKey: string
|
|
151
|
+
fieldName: TFieldKey
|
|
152
|
+
context: import('../access/types.js').AccessContext
|
|
153
|
+
},
|
|
154
|
+
) => Promise<void> | void
|
|
109
155
|
|
|
110
156
|
/**
|
|
111
157
|
* Transform field value after database read
|
|
@@ -175,6 +221,23 @@ export type BaseFieldConfig<TTypeInfo extends TypeInfo> = {
|
|
|
175
221
|
* Transforms field values and types in query results using Prisma's native extension system
|
|
176
222
|
*/
|
|
177
223
|
resultExtension?: ResultExtensionConfig
|
|
224
|
+
/**
|
|
225
|
+
* Database configuration
|
|
226
|
+
*/
|
|
227
|
+
db?: {
|
|
228
|
+
/**
|
|
229
|
+
* Custom database column name
|
|
230
|
+
* Adds a @map attribute in Prisma schema
|
|
231
|
+
* @example
|
|
232
|
+
* ```typescript
|
|
233
|
+
* fields: {
|
|
234
|
+
* firstName: text({ db: { map: 'first_name' } })
|
|
235
|
+
* }
|
|
236
|
+
* // Generates: firstName String @map("first_name")
|
|
237
|
+
* ```
|
|
238
|
+
*/
|
|
239
|
+
map?: string
|
|
240
|
+
}
|
|
178
241
|
ui?: {
|
|
179
242
|
/**
|
|
180
243
|
* Custom React component to render this field
|
|
@@ -214,9 +277,13 @@ export type BaseFieldConfig<TTypeInfo extends TypeInfo> = {
|
|
|
214
277
|
/**
|
|
215
278
|
* Get Prisma type and modifiers for schema generation
|
|
216
279
|
* @param fieldName - The name of the field (for generating modifiers)
|
|
280
|
+
* @param provider - Optional database provider ('sqlite', 'postgresql', 'mysql', etc.)
|
|
217
281
|
* @returns Prisma type string and optional modifiers
|
|
218
282
|
*/
|
|
219
|
-
getPrismaType?: (
|
|
283
|
+
getPrismaType?: (
|
|
284
|
+
fieldName: string,
|
|
285
|
+
provider?: string,
|
|
286
|
+
) => {
|
|
220
287
|
type: string
|
|
221
288
|
modifiers?: string
|
|
222
289
|
}
|
|
@@ -261,6 +328,37 @@ export type TextField<TTypeInfo extends TypeInfo = TypeInfo> = BaseFieldConfig<T
|
|
|
261
328
|
}
|
|
262
329
|
}
|
|
263
330
|
isIndexed?: boolean | 'unique'
|
|
331
|
+
db?: {
|
|
332
|
+
map?: string
|
|
333
|
+
/**
|
|
334
|
+
* Prisma native database type attribute
|
|
335
|
+
* Allows overriding the default String type for the database provider
|
|
336
|
+
* @example
|
|
337
|
+
* ```typescript
|
|
338
|
+
* // PostgreSQL/MySQL
|
|
339
|
+
* fields: {
|
|
340
|
+
* description: text({ db: { nativeType: 'Text' } })
|
|
341
|
+
* // Generates: description String @db.Text
|
|
342
|
+
* }
|
|
343
|
+
* ```
|
|
344
|
+
*/
|
|
345
|
+
nativeType?: string
|
|
346
|
+
/**
|
|
347
|
+
* Controls nullability in the database schema
|
|
348
|
+
* When specified, overrides the default behavior (isRequired determines nullability)
|
|
349
|
+
* @example
|
|
350
|
+
* ```typescript
|
|
351
|
+
* fields: {
|
|
352
|
+
* description: text({
|
|
353
|
+
* validation: { isRequired: true },
|
|
354
|
+
* db: { isNullable: false }
|
|
355
|
+
* })
|
|
356
|
+
* // Generates: description String (non-nullable)
|
|
357
|
+
* }
|
|
358
|
+
* ```
|
|
359
|
+
*/
|
|
360
|
+
isNullable?: boolean
|
|
361
|
+
}
|
|
264
362
|
ui?: {
|
|
265
363
|
displayMode?: 'input' | 'textarea'
|
|
266
364
|
}
|
|
@@ -275,6 +373,23 @@ export type IntegerField<TTypeInfo extends TypeInfo = TypeInfo> = BaseFieldConfi
|
|
|
275
373
|
}
|
|
276
374
|
}
|
|
277
375
|
|
|
376
|
+
export type DecimalField<TTypeInfo extends TypeInfo = TypeInfo> = BaseFieldConfig<TTypeInfo> & {
|
|
377
|
+
type: 'decimal'
|
|
378
|
+
defaultValue?: string
|
|
379
|
+
precision?: number
|
|
380
|
+
scale?: number
|
|
381
|
+
db?: {
|
|
382
|
+
map?: string
|
|
383
|
+
isNullable?: boolean
|
|
384
|
+
}
|
|
385
|
+
validation?: {
|
|
386
|
+
isRequired?: boolean
|
|
387
|
+
min?: string
|
|
388
|
+
max?: string
|
|
389
|
+
}
|
|
390
|
+
isIndexed?: boolean | 'unique'
|
|
391
|
+
}
|
|
392
|
+
|
|
278
393
|
export type CheckboxField<TTypeInfo extends TypeInfo = TypeInfo> = BaseFieldConfig<TTypeInfo> & {
|
|
279
394
|
type: 'checkbox'
|
|
280
395
|
}
|
|
@@ -284,6 +399,19 @@ export type TimestampField<TTypeInfo extends TypeInfo = TypeInfo> = BaseFieldCon
|
|
|
284
399
|
defaultValue?: { kind: 'now' } | Date
|
|
285
400
|
}
|
|
286
401
|
|
|
402
|
+
export type CalendarDayField<TTypeInfo extends TypeInfo = TypeInfo> = BaseFieldConfig<TTypeInfo> & {
|
|
403
|
+
type: 'calendarDay'
|
|
404
|
+
defaultValue?: string
|
|
405
|
+
db?: {
|
|
406
|
+
map?: string
|
|
407
|
+
isNullable?: boolean
|
|
408
|
+
}
|
|
409
|
+
validation?: {
|
|
410
|
+
isRequired?: boolean
|
|
411
|
+
}
|
|
412
|
+
isIndexed?: boolean | 'unique'
|
|
413
|
+
}
|
|
414
|
+
|
|
287
415
|
export type PasswordField<TTypeInfo extends TypeInfo = TypeInfo> = BaseFieldConfig<TTypeInfo> & {
|
|
288
416
|
type: 'password'
|
|
289
417
|
validation?: {
|
|
@@ -309,18 +437,32 @@ export type RelationshipField<TTypeInfo extends TypeInfo = TypeInfo> =
|
|
|
309
437
|
many?: boolean
|
|
310
438
|
db?: {
|
|
311
439
|
/**
|
|
312
|
-
* Controls foreign key placement for bidirectional relationships
|
|
440
|
+
* Controls foreign key placement and column name for bidirectional relationships
|
|
441
|
+
* Can be a boolean or an object with a map property
|
|
313
442
|
* Only valid on single (non-many) relationships
|
|
314
443
|
* Cannot be true on both sides of a one-to-one relationship
|
|
315
444
|
*
|
|
445
|
+
* When a boolean, defaults the foreign key column name to the field name
|
|
446
|
+
* When an object with map, uses the provided column name
|
|
447
|
+
*
|
|
316
448
|
* @example
|
|
317
449
|
* ```typescript
|
|
318
|
-
* // One-to-one: User has one Account
|
|
450
|
+
* // One-to-one: User has one Account (default foreign key name)
|
|
319
451
|
* User: list({
|
|
320
452
|
* fields: {
|
|
321
453
|
* account: relationship({ ref: 'Account.user', db: { foreignKey: true } })
|
|
454
|
+
* // Generates: accountId String? @unique
|
|
455
|
+
* }
|
|
456
|
+
* })
|
|
457
|
+
*
|
|
458
|
+
* // One-to-one: User has one Account (custom foreign key name)
|
|
459
|
+
* User: list({
|
|
460
|
+
* fields: {
|
|
461
|
+
* account: relationship({ ref: 'Account.user', db: { foreignKey: { map: 'account_id' } } })
|
|
462
|
+
* // Generates: accountId String? @unique @map("account_id")
|
|
322
463
|
* }
|
|
323
464
|
* })
|
|
465
|
+
*
|
|
324
466
|
* Account: list({
|
|
325
467
|
* fields: {
|
|
326
468
|
* user: relationship({ ref: 'User.account' }) // No foreign key on this side
|
|
@@ -328,7 +470,7 @@ export type RelationshipField<TTypeInfo extends TypeInfo = TypeInfo> =
|
|
|
328
470
|
* })
|
|
329
471
|
* ```
|
|
330
472
|
*/
|
|
331
|
-
foreignKey?: boolean
|
|
473
|
+
foreignKey?: boolean | { map?: string }
|
|
332
474
|
}
|
|
333
475
|
ui?: {
|
|
334
476
|
displayMode?: 'select' | 'cards'
|
|
@@ -567,19 +709,69 @@ export type ResolveInputHookArgs<
|
|
|
567
709
|
}
|
|
568
710
|
|
|
569
711
|
/**
|
|
570
|
-
* Hook arguments for
|
|
571
|
-
*
|
|
712
|
+
* Hook arguments for validateInput hook
|
|
713
|
+
* Uses discriminated union to provide proper types based on operation
|
|
714
|
+
* - create: resolvedData is CreateInput, item is undefined
|
|
715
|
+
* - update: resolvedData is UpdateInput, item is the existing record
|
|
572
716
|
*/
|
|
573
|
-
export type
|
|
717
|
+
export type ValidateInputHookArgs<
|
|
574
718
|
TOutput = Record<string, unknown>,
|
|
575
719
|
TCreateInput = Record<string, unknown>,
|
|
576
720
|
TUpdateInput = Record<string, unknown>,
|
|
577
|
-
> =
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
721
|
+
> =
|
|
722
|
+
| {
|
|
723
|
+
operation: 'create'
|
|
724
|
+
resolvedData: TCreateInput
|
|
725
|
+
item: undefined
|
|
726
|
+
context: import('../access/types.js').AccessContext
|
|
727
|
+
addValidationError: (msg: string) => void
|
|
728
|
+
}
|
|
729
|
+
| {
|
|
730
|
+
operation: 'update'
|
|
731
|
+
resolvedData: TUpdateInput
|
|
732
|
+
item: TOutput
|
|
733
|
+
context: import('../access/types.js').AccessContext
|
|
734
|
+
addValidationError: (msg: string) => void
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
/**
|
|
738
|
+
* Hook arguments for beforeOperation hook
|
|
739
|
+
* Uses discriminated union to provide proper types based on operation
|
|
740
|
+
* - create: no resolvedData, no item
|
|
741
|
+
* - update: no resolvedData, has item
|
|
742
|
+
* - delete: no resolvedData, has item
|
|
743
|
+
*/
|
|
744
|
+
export type BeforeOperationHookArgs<TOutput = Record<string, unknown>> =
|
|
745
|
+
| {
|
|
746
|
+
operation: 'create'
|
|
747
|
+
context: import('../access/types.js').AccessContext
|
|
748
|
+
}
|
|
749
|
+
| {
|
|
750
|
+
operation: 'update' | 'delete'
|
|
751
|
+
item: TOutput
|
|
752
|
+
context: import('../access/types.js').AccessContext
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
/**
|
|
756
|
+
* Hook arguments for afterOperation hook
|
|
757
|
+
* Uses discriminated union to provide proper types based on operation
|
|
758
|
+
* - create: has item, no originalItem
|
|
759
|
+
* - update: has item, has originalItem
|
|
760
|
+
* - delete: has item, has originalItem
|
|
761
|
+
*/
|
|
762
|
+
export type AfterOperationHookArgs<TOutput = Record<string, unknown>> =
|
|
763
|
+
| {
|
|
764
|
+
operation: 'create'
|
|
765
|
+
item: TOutput
|
|
766
|
+
originalItem: undefined
|
|
767
|
+
context: import('../access/types.js').AccessContext
|
|
768
|
+
}
|
|
769
|
+
| {
|
|
770
|
+
operation: 'update' | 'delete'
|
|
771
|
+
item: TOutput
|
|
772
|
+
originalItem: TOutput
|
|
773
|
+
context: import('../access/types.js').AccessContext
|
|
774
|
+
}
|
|
583
775
|
|
|
584
776
|
export type Hooks<
|
|
585
777
|
TOutput = Record<string, unknown>,
|
|
@@ -590,13 +782,10 @@ export type Hooks<
|
|
|
590
782
|
args: ResolveInputHookArgs<TOutput, TCreateInput, TUpdateInput>,
|
|
591
783
|
) => Promise<TCreateInput | TUpdateInput>
|
|
592
784
|
validateInput?: (
|
|
593
|
-
args:
|
|
594
|
-
operation: 'create' | 'update'
|
|
595
|
-
addValidationError: (msg: string) => void
|
|
596
|
-
},
|
|
785
|
+
args: ValidateInputHookArgs<TOutput, TCreateInput, TUpdateInput>,
|
|
597
786
|
) => Promise<void>
|
|
598
|
-
beforeOperation?: (args:
|
|
599
|
-
afterOperation?: (args:
|
|
787
|
+
beforeOperation?: (args: BeforeOperationHookArgs<TOutput>) => Promise<void>
|
|
788
|
+
afterOperation?: (args: AfterOperationHookArgs<TOutput>) => Promise<void>
|
|
600
789
|
}
|
|
601
790
|
|
|
602
791
|
// Generic `any` default allows ListConfig to work with any list item type
|
package/src/context/index.ts
CHANGED
|
@@ -107,6 +107,8 @@ async function executeFieldAfterOperationHooks(
|
|
|
107
107
|
operation: 'create' | 'update' | 'delete' | 'query',
|
|
108
108
|
context: AccessContext,
|
|
109
109
|
listKey: string,
|
|
110
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
111
|
+
originalItem?: any,
|
|
110
112
|
): Promise<void> {
|
|
111
113
|
for (const [fieldName, fieldConfig] of Object.entries(fields)) {
|
|
112
114
|
// Skip if no hooks defined
|
|
@@ -122,6 +124,7 @@ async function executeFieldAfterOperationHooks(
|
|
|
122
124
|
fieldName,
|
|
123
125
|
listKey,
|
|
124
126
|
item,
|
|
127
|
+
originalItem,
|
|
125
128
|
context,
|
|
126
129
|
})
|
|
127
130
|
}
|
|
@@ -483,6 +486,7 @@ function createFindUnique<TPrisma extends PrismaClientLike>(
|
|
|
483
486
|
'query',
|
|
484
487
|
context,
|
|
485
488
|
listName,
|
|
489
|
+
undefined, // originalItem is undefined for query operations
|
|
486
490
|
)
|
|
487
491
|
|
|
488
492
|
return filtered
|
|
@@ -579,6 +583,7 @@ function createFindMany<TPrisma extends PrismaClientLike>(
|
|
|
579
583
|
'query',
|
|
580
584
|
context,
|
|
581
585
|
listName,
|
|
586
|
+
undefined, // originalItem is undefined for query operations
|
|
582
587
|
),
|
|
583
588
|
),
|
|
584
589
|
)
|
|
@@ -616,6 +621,7 @@ function createCreate<TPrisma extends PrismaClientLike>(
|
|
|
616
621
|
let resolvedData = await executeResolveInput(listConfig.hooks, {
|
|
617
622
|
operation: 'create',
|
|
618
623
|
resolvedData: args.data,
|
|
624
|
+
item: undefined,
|
|
619
625
|
context,
|
|
620
626
|
})
|
|
621
627
|
|
|
@@ -632,6 +638,7 @@ function createCreate<TPrisma extends PrismaClientLike>(
|
|
|
632
638
|
await executeValidateInput(listConfig.hooks, {
|
|
633
639
|
operation: 'create',
|
|
634
640
|
resolvedData,
|
|
641
|
+
item: undefined,
|
|
635
642
|
context,
|
|
636
643
|
})
|
|
637
644
|
|
|
@@ -677,6 +684,7 @@ function createCreate<TPrisma extends PrismaClientLike>(
|
|
|
677
684
|
await executeAfterOperation(listConfig.hooks, {
|
|
678
685
|
operation: 'create',
|
|
679
686
|
item,
|
|
687
|
+
originalItem: undefined,
|
|
680
688
|
context,
|
|
681
689
|
})
|
|
682
690
|
|
|
@@ -688,6 +696,7 @@ function createCreate<TPrisma extends PrismaClientLike>(
|
|
|
688
696
|
'create',
|
|
689
697
|
context,
|
|
690
698
|
listName,
|
|
699
|
+
undefined, // originalItem is undefined for create operations
|
|
691
700
|
)
|
|
692
701
|
|
|
693
702
|
// 11. Filter readable fields and apply resolveOutput hooks (including nested relationships)
|
|
@@ -832,6 +841,7 @@ function createUpdate<TPrisma extends PrismaClientLike>(
|
|
|
832
841
|
await executeAfterOperation(listConfig.hooks, {
|
|
833
842
|
operation: 'update',
|
|
834
843
|
item: updated,
|
|
844
|
+
originalItem: item, // item is the original item before the update
|
|
835
845
|
context,
|
|
836
846
|
})
|
|
837
847
|
|
|
@@ -843,6 +853,7 @@ function createUpdate<TPrisma extends PrismaClientLike>(
|
|
|
843
853
|
'update',
|
|
844
854
|
context,
|
|
845
855
|
listName,
|
|
856
|
+
item, // item is the original item before the update
|
|
846
857
|
)
|
|
847
858
|
|
|
848
859
|
// 12. Filter readable fields and apply resolveOutput hooks (including nested relationships)
|
|
@@ -930,6 +941,7 @@ function createDelete<TPrisma extends PrismaClientLike>(
|
|
|
930
941
|
await executeAfterOperation(listConfig.hooks, {
|
|
931
942
|
operation: 'delete',
|
|
932
943
|
item: deleted,
|
|
944
|
+
originalItem: item, // item is the original item before deletion
|
|
933
945
|
context,
|
|
934
946
|
})
|
|
935
947
|
|
|
@@ -941,6 +953,7 @@ function createDelete<TPrisma extends PrismaClientLike>(
|
|
|
941
953
|
'delete',
|
|
942
954
|
context,
|
|
943
955
|
listName,
|
|
956
|
+
item, // item is the original item before deletion
|
|
944
957
|
)
|
|
945
958
|
|
|
946
959
|
return deleted
|
|
@@ -87,6 +87,7 @@ async function processNestedCreate(
|
|
|
87
87
|
let resolvedData = await executeResolveInput(relatedListConfig.hooks, {
|
|
88
88
|
operation: 'create',
|
|
89
89
|
resolvedData: item,
|
|
90
|
+
item: undefined,
|
|
90
91
|
context,
|
|
91
92
|
})
|
|
92
93
|
|
|
@@ -113,6 +114,7 @@ async function processNestedCreate(
|
|
|
113
114
|
await executeValidateInput(relatedListConfig.hooks, {
|
|
114
115
|
operation: 'create',
|
|
115
116
|
resolvedData,
|
|
117
|
+
item: undefined,
|
|
116
118
|
context,
|
|
117
119
|
})
|
|
118
120
|
|