@opensaas/stack-core 0.12.0 → 0.13.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.
@@ -46,13 +46,17 @@ export async function executeResolveInput<
46
46
  hooks: Hooks<TOutput, TCreateInput, TUpdateInput> | undefined,
47
47
  args:
48
48
  | {
49
+ listKey: string
49
50
  operation: 'create'
51
+ inputData: TCreateInput
50
52
  resolvedData: TCreateInput
51
53
  item: undefined
52
54
  context: AccessContext
53
55
  }
54
56
  | {
57
+ listKey: string
55
58
  operation: 'update'
59
+ inputData: TUpdateInput
56
60
  resolvedData: TUpdateInput
57
61
  item: TOutput
58
62
  context: AccessContext
@@ -67,10 +71,10 @@ export async function executeResolveInput<
67
71
  }
68
72
 
69
73
  /**
70
- * Execute validateInput hook
74
+ * Execute validate hook (supports both 'validate' and deprecated 'validateInput')
71
75
  * Allows custom validation logic
72
76
  */
73
- export async function executeValidateInput<
77
+ export async function executeValidate<
74
78
  TOutput = Record<string, unknown>,
75
79
  TCreateInput = Record<string, unknown>,
76
80
  TUpdateInput = Record<string, unknown>,
@@ -78,19 +82,31 @@ export async function executeValidateInput<
78
82
  hooks: Hooks<TOutput, TCreateInput, TUpdateInput> | undefined,
79
83
  args:
80
84
  | {
85
+ listKey: string
81
86
  operation: 'create'
87
+ inputData: TCreateInput
82
88
  resolvedData: TCreateInput
83
89
  item: undefined
84
90
  context: AccessContext
85
91
  }
86
92
  | {
93
+ listKey: string
87
94
  operation: 'update'
95
+ inputData: TUpdateInput
88
96
  resolvedData: TUpdateInput
89
97
  item: TOutput
90
98
  context: AccessContext
99
+ }
100
+ | {
101
+ listKey: string
102
+ operation: 'delete'
103
+ item: TOutput
104
+ context: AccessContext
91
105
  },
92
106
  ): Promise<void> {
93
- if (!hooks?.validateInput) {
107
+ // Support both 'validate' (new) and 'validateInput' (deprecated) for backwards compatibility
108
+ const validateHook = hooks?.validate || hooks?.validateInput
109
+ if (!validateHook) {
94
110
  return
95
111
  }
96
112
 
@@ -100,29 +116,50 @@ export async function executeValidateInput<
100
116
  errors.push(msg)
101
117
  }
102
118
 
103
- await hooks.validateInput({
119
+ await validateHook({
104
120
  ...args,
105
121
  addValidationError,
106
- })
122
+ } as Parameters<typeof validateHook>[0])
107
123
 
108
124
  if (errors.length > 0) {
109
125
  throw new ValidationError(errors)
110
126
  }
111
127
  }
112
128
 
129
+ /**
130
+ * @deprecated Use executeValidate instead. This alias is provided for backwards compatibility.
131
+ */
132
+ export const executeValidateInput = executeValidate
133
+
113
134
  /**
114
135
  * Execute beforeOperation hook
115
136
  * Runs before database operation (cannot modify data)
116
137
  */
117
- export async function executeBeforeOperation<TOutput = Record<string, unknown>>(
118
- hooks: Hooks<TOutput> | undefined,
138
+ export async function executeBeforeOperation<
139
+ TOutput = Record<string, unknown>,
140
+ TCreateInput = Record<string, unknown>,
141
+ TUpdateInput = Record<string, unknown>,
142
+ >(
143
+ hooks: Hooks<TOutput, TCreateInput, TUpdateInput> | undefined,
119
144
  args:
120
145
  | {
146
+ listKey: string
121
147
  operation: 'create'
148
+ inputData: TCreateInput
149
+ resolvedData: TCreateInput
122
150
  context: AccessContext
123
151
  }
124
152
  | {
125
- operation: 'update' | 'delete'
153
+ listKey: string
154
+ operation: 'update'
155
+ inputData: TUpdateInput
156
+ item: TOutput
157
+ resolvedData: TUpdateInput
158
+ context: AccessContext
159
+ }
160
+ | {
161
+ listKey: string
162
+ operation: 'delete'
126
163
  item: TOutput
127
164
  context: AccessContext
128
165
  },
@@ -131,25 +168,40 @@ export async function executeBeforeOperation<TOutput = Record<string, unknown>>(
131
168
  return
132
169
  }
133
170
 
134
- await hooks.beforeOperation(args)
171
+ await hooks.beforeOperation(args as Parameters<typeof hooks.beforeOperation>[0])
135
172
  }
136
173
 
137
174
  /**
138
175
  * Execute afterOperation hook
139
176
  * Runs after database operation
140
177
  */
141
- export async function executeAfterOperation<TOutput = Record<string, unknown>>(
142
- hooks: Hooks<TOutput> | undefined,
178
+ export async function executeAfterOperation<
179
+ TOutput = Record<string, unknown>,
180
+ TCreateInput = Record<string, unknown>,
181
+ TUpdateInput = Record<string, unknown>,
182
+ >(
183
+ hooks: Hooks<TOutput, TCreateInput, TUpdateInput> | undefined,
143
184
  args:
144
185
  | {
186
+ listKey: string
145
187
  operation: 'create'
188
+ inputData: TCreateInput
146
189
  item: TOutput
147
- originalItem: undefined
190
+ resolvedData: TCreateInput
148
191
  context: AccessContext
149
192
  }
150
193
  | {
151
- operation: 'update' | 'delete'
194
+ listKey: string
195
+ operation: 'update'
196
+ inputData: TUpdateInput
197
+ originalItem: TOutput
152
198
  item: TOutput
199
+ resolvedData: TUpdateInput
200
+ context: AccessContext
201
+ }
202
+ | {
203
+ listKey: string
204
+ operation: 'delete'
153
205
  originalItem: TOutput
154
206
  context: AccessContext
155
207
  },
@@ -158,7 +210,7 @@ export async function executeAfterOperation<TOutput = Record<string, unknown>>(
158
210
  return
159
211
  }
160
212
 
161
- await hooks.afterOperation(args)
213
+ await hooks.afterOperation(args as Parameters<typeof hooks.afterOperation>[0])
162
214
  }
163
215
 
164
216
  /**
@@ -50,7 +50,10 @@ describe('Nested Operations - Access Control and Hooks', () => {
50
50
 
51
51
  describe('Nested Create Operations', () => {
52
52
  it('should run hooks and access control for nested create', async () => {
53
- const userResolveInputHook = vi.fn(async ({ inputValue }) => inputValue?.toUpperCase())
53
+ const userResolveInputHook = vi.fn(async ({ resolvedData, fieldKey }) => {
54
+ const value = resolvedData[fieldKey]
55
+ return typeof value === 'string' ? value.toUpperCase() : value
56
+ })
54
57
  const userListResolveInputHook = vi.fn(async ({ resolvedData }) => resolvedData)
55
58
  const userValidateInputHook = vi.fn(async () => {})
56
59
  const postResolveInputHook = vi.fn(async ({ resolvedData }) => resolvedData)
@@ -140,9 +143,11 @@ describe('Nested Operations - Access Control and Hooks', () => {
140
143
 
141
144
  expect(userResolveInputHook).toHaveBeenCalledWith(
142
145
  expect.objectContaining({
143
- inputValue: 'john',
146
+ fieldKey: 'name',
144
147
  operation: 'create',
145
- fieldName: 'name',
148
+ resolvedData: expect.objectContaining({
149
+ name: 'john',
150
+ }),
146
151
  }),
147
152
  )
148
153
 
@@ -31,9 +31,9 @@ describe('Sudo Context', () => {
31
31
  title: text({
32
32
  validation: { isRequired: true },
33
33
  hooks: {
34
- resolveInput: async ({ inputValue }) => {
34
+ resolveInput: async ({ resolvedData, fieldKey }) => {
35
35
  hookExecutions.push('field-resolveInput')
36
- return inputValue
36
+ return resolvedData[fieldKey]
37
37
  },
38
38
  beforeOperation: async () => {
39
39
  hookExecutions.push('field-beforeOperation')
@@ -116,17 +116,6 @@ describe('Sudo Context', () => {
116
116
  const sudoResult = await sudoContext.db.post.findUnique({ where: { id: '1' } })
117
117
  expect(sudoResult?.secretField).toBe('secret-value')
118
118
  })
119
-
120
- it('should execute field afterOperation hooks with sudo()', async () => {
121
- const context = getContext(testConfig, mockPrisma, null)
122
- const sudoContext = context.sudo()
123
-
124
- mockPrisma.post.findMany.mockResolvedValue([{ id: '1', title: 'Test Post' }])
125
-
126
- await sudoContext.db.post.findMany()
127
-
128
- expect(hookExecutions).toContain('field-afterOperation')
129
- })
130
119
  })
131
120
 
132
121
  describe('Create Operations', () => {