@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.
- package/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +88 -0
- package/README.md +6 -3
- package/dist/config/types.d.ts +147 -47
- package/dist/config/types.d.ts.map +1 -1
- package/dist/context/index.d.ts.map +1 -1
- package/dist/context/index.js +202 -58
- package/dist/context/index.js.map +1 -1
- package/dist/context/nested-operations.d.ts.map +1 -1
- package/dist/context/nested-operations.js +36 -25
- package/dist/context/nested-operations.js.map +1 -1
- package/dist/hooks/index.d.ts +45 -7
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/index.js +10 -4
- package/dist/hooks/index.js.map +1 -1
- package/package.json +1 -1
- package/src/config/types.ts +170 -49
- package/src/context/index.ts +253 -80
- package/src/context/nested-operations.ts +38 -25
- package/src/hooks/index.ts +66 -14
- package/tests/nested-access-and-hooks.test.ts +8 -3
- package/tests/sudo.test.ts +2 -13
- package/tsconfig.tsbuildinfo +1 -1
package/src/hooks/index.ts
CHANGED
|
@@ -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
|
|
74
|
+
* Execute validate hook (supports both 'validate' and deprecated 'validateInput')
|
|
71
75
|
* Allows custom validation logic
|
|
72
76
|
*/
|
|
73
|
-
export async function
|
|
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
|
-
|
|
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
|
|
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<
|
|
118
|
-
|
|
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
|
-
|
|
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<
|
|
142
|
-
|
|
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
|
-
|
|
190
|
+
resolvedData: TCreateInput
|
|
148
191
|
context: AccessContext
|
|
149
192
|
}
|
|
150
193
|
| {
|
|
151
|
-
|
|
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 ({
|
|
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
|
-
|
|
146
|
+
fieldKey: 'name',
|
|
144
147
|
operation: 'create',
|
|
145
|
-
|
|
148
|
+
resolvedData: expect.objectContaining({
|
|
149
|
+
name: 'john',
|
|
150
|
+
}),
|
|
146
151
|
}),
|
|
147
152
|
)
|
|
148
153
|
|
package/tests/sudo.test.ts
CHANGED
|
@@ -31,9 +31,9 @@ describe('Sudo Context', () => {
|
|
|
31
31
|
title: text({
|
|
32
32
|
validation: { isRequired: true },
|
|
33
33
|
hooks: {
|
|
34
|
-
resolveInput: async ({
|
|
34
|
+
resolveInput: async ({ resolvedData, fieldKey }) => {
|
|
35
35
|
hookExecutions.push('field-resolveInput')
|
|
36
|
-
return
|
|
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', () => {
|