@opensaas/stack-core 0.13.0 → 0.14.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 +205 -0
- package/dist/access/engine.d.ts +2 -0
- package/dist/access/engine.d.ts.map +1 -1
- package/dist/access/engine.js +8 -6
- package/dist/access/engine.js.map +1 -1
- package/dist/access/engine.test.js +4 -0
- package/dist/access/engine.test.js.map +1 -1
- package/dist/access/types.d.ts +31 -4
- package/dist/access/types.d.ts.map +1 -1
- package/dist/config/index.d.ts +12 -10
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/index.js +37 -1
- package/dist/config/index.js.map +1 -1
- package/dist/config/types.d.ts +257 -98
- package/dist/config/types.d.ts.map +1 -1
- package/dist/context/index.d.ts.map +1 -1
- package/dist/context/index.js +133 -7
- 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/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/access/engine.test.ts +4 -0
- package/src/access/engine.ts +10 -7
- package/src/access/types.ts +45 -4
- package/src/config/index.ts +65 -9
- package/src/config/types.ts +303 -113
- package/src/context/index.ts +176 -10
- package/src/context/nested-operations.ts +2 -0
- package/src/index.ts +11 -0
- package/tests/access.test.ts +28 -28
- package/tests/config.test.ts +20 -3
- package/tests/singleton.test.ts +329 -0
- package/tsconfig.tsbuildinfo +1 -1
package/src/config/index.ts
CHANGED
|
@@ -1,5 +1,38 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type {
|
|
2
|
+
OpenSaasConfig,
|
|
3
|
+
ListConfig,
|
|
4
|
+
ListConfigInput,
|
|
5
|
+
OperationAccess,
|
|
6
|
+
ListAccessControl,
|
|
7
|
+
} from './types.js'
|
|
2
8
|
import { executePlugins } from './plugin-engine.js'
|
|
9
|
+
import type { AccessControl } from '../access/types.js'
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Normalize access control shorthand to object form
|
|
13
|
+
* Converts function shorthand to { operation: { query, create, update, delete } } form
|
|
14
|
+
*/
|
|
15
|
+
function normalizeListAccess<T>(
|
|
16
|
+
access: ListAccessControl<T> | undefined,
|
|
17
|
+
): { operation?: OperationAccess<T> } | undefined {
|
|
18
|
+
if (!access) return undefined
|
|
19
|
+
|
|
20
|
+
// If it's a function, convert to object form applying to all operations
|
|
21
|
+
if (typeof access === 'function') {
|
|
22
|
+
const fn = access as AccessControl<T>
|
|
23
|
+
return {
|
|
24
|
+
operation: {
|
|
25
|
+
query: fn,
|
|
26
|
+
create: fn,
|
|
27
|
+
update: fn,
|
|
28
|
+
delete: fn,
|
|
29
|
+
},
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Already in object form
|
|
34
|
+
return access
|
|
35
|
+
}
|
|
3
36
|
|
|
4
37
|
/**
|
|
5
38
|
* Helper function to define configuration with type safety
|
|
@@ -61,25 +94,37 @@ export function config(userConfig: OpenSaasConfig): OpenSaasConfig | Promise<Ope
|
|
|
61
94
|
* fields: { title: text() },
|
|
62
95
|
* hooks: { ... }
|
|
63
96
|
* })
|
|
97
|
+
*
|
|
98
|
+
* // Access control shorthand
|
|
99
|
+
* const isAdmin = ({ session }) => session?.role === 'admin'
|
|
100
|
+
*
|
|
101
|
+
* Settings: list({
|
|
102
|
+
* access: isAdmin, // Applies to all operations
|
|
103
|
+
* isSingleton: true,
|
|
104
|
+
* fields: { ... }
|
|
105
|
+
* })
|
|
64
106
|
* ```
|
|
65
107
|
*/
|
|
66
|
-
export function list<TTypeInfo extends import('./types.js').TypeInfo>(
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
108
|
+
export function list<TTypeInfo extends import('./types.js').TypeInfo>(
|
|
109
|
+
config: ListConfigInput<TTypeInfo>,
|
|
110
|
+
): ListConfig<TTypeInfo> {
|
|
111
|
+
// Normalize access control shorthand to object form
|
|
112
|
+
const normalizedConfig = {
|
|
113
|
+
...config,
|
|
114
|
+
access: normalizeListAccess(config.access),
|
|
70
115
|
}
|
|
71
|
-
|
|
72
|
-
mcp?: import('./types.js').ListMcpConfig
|
|
73
|
-
}): ListConfig<TTypeInfo> {
|
|
116
|
+
|
|
74
117
|
// At runtime, field configs are unchanged
|
|
75
118
|
// At type level, they're transformed to inject TypeInfo types
|
|
76
|
-
return
|
|
119
|
+
return normalizedConfig as ListConfig<TTypeInfo>
|
|
77
120
|
}
|
|
78
121
|
|
|
79
122
|
// Re-export all types
|
|
80
123
|
export type {
|
|
81
124
|
OpenSaasConfig,
|
|
82
125
|
ListConfig,
|
|
126
|
+
ListConfigInput,
|
|
127
|
+
ListAccessControl,
|
|
83
128
|
FieldConfig,
|
|
84
129
|
BaseFieldConfig,
|
|
85
130
|
TextField,
|
|
@@ -115,4 +160,15 @@ export type {
|
|
|
115
160
|
Plugin,
|
|
116
161
|
PluginContext,
|
|
117
162
|
GeneratedFiles,
|
|
163
|
+
// List-level hook argument types
|
|
164
|
+
ResolveInputHookArgs,
|
|
165
|
+
ValidateHookArgs,
|
|
166
|
+
BeforeOperationHookArgs,
|
|
167
|
+
AfterOperationHookArgs,
|
|
168
|
+
// Field-level hook argument types
|
|
169
|
+
FieldResolveInputHookArgs,
|
|
170
|
+
FieldValidateHookArgs,
|
|
171
|
+
FieldBeforeOperationHookArgs,
|
|
172
|
+
FieldAfterOperationHookArgs,
|
|
173
|
+
FieldResolveOutputHookArgs,
|
|
118
174
|
} from './types.js'
|
package/src/config/types.ts
CHANGED
|
@@ -14,6 +14,158 @@ export type FieldType =
|
|
|
14
14
|
| 'relationship'
|
|
15
15
|
| string // Allow custom field types from third-party packages
|
|
16
16
|
|
|
17
|
+
/**
|
|
18
|
+
* Field-level hook argument types (exported for user annotations)
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Arguments for field-level resolveInput hook
|
|
23
|
+
* Used to transform field values before database write
|
|
24
|
+
*/
|
|
25
|
+
export type FieldResolveInputHookArgs<
|
|
26
|
+
TTypeInfo extends TypeInfo,
|
|
27
|
+
TFieldKey extends FieldKeys<TTypeInfo['fields']> = FieldKeys<TTypeInfo['fields']>,
|
|
28
|
+
> =
|
|
29
|
+
| {
|
|
30
|
+
listKey: string
|
|
31
|
+
fieldKey: TFieldKey
|
|
32
|
+
operation: 'create'
|
|
33
|
+
inputData: TTypeInfo['inputs']['create']
|
|
34
|
+
item: undefined
|
|
35
|
+
resolvedData: TTypeInfo['inputs']['create']
|
|
36
|
+
context: import('../access/types.js').AccessContext
|
|
37
|
+
}
|
|
38
|
+
| {
|
|
39
|
+
listKey: string
|
|
40
|
+
fieldKey: TFieldKey
|
|
41
|
+
operation: 'update'
|
|
42
|
+
inputData: TTypeInfo['inputs']['update']
|
|
43
|
+
item: TTypeInfo['item']
|
|
44
|
+
resolvedData: TTypeInfo['inputs']['update']
|
|
45
|
+
context: import('../access/types.js').AccessContext
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Arguments for field-level validate hook
|
|
50
|
+
* Used for custom validation logic
|
|
51
|
+
*/
|
|
52
|
+
export type FieldValidateHookArgs<
|
|
53
|
+
TTypeInfo extends TypeInfo,
|
|
54
|
+
TFieldKey extends FieldKeys<TTypeInfo['fields']> = FieldKeys<TTypeInfo['fields']>,
|
|
55
|
+
> =
|
|
56
|
+
| {
|
|
57
|
+
listKey: string
|
|
58
|
+
fieldKey: TFieldKey
|
|
59
|
+
operation: 'create'
|
|
60
|
+
inputData: TTypeInfo['inputs']['create']
|
|
61
|
+
item: undefined
|
|
62
|
+
resolvedData: TTypeInfo['inputs']['create']
|
|
63
|
+
context: import('../access/types.js').AccessContext
|
|
64
|
+
addValidationError: (msg: string) => void
|
|
65
|
+
}
|
|
66
|
+
| {
|
|
67
|
+
listKey: string
|
|
68
|
+
fieldKey: TFieldKey
|
|
69
|
+
operation: 'update'
|
|
70
|
+
inputData: TTypeInfo['inputs']['update']
|
|
71
|
+
item: TTypeInfo['item']
|
|
72
|
+
resolvedData: TTypeInfo['inputs']['update']
|
|
73
|
+
context: import('../access/types.js').AccessContext
|
|
74
|
+
addValidationError: (msg: string) => void
|
|
75
|
+
}
|
|
76
|
+
| {
|
|
77
|
+
listKey: string
|
|
78
|
+
fieldKey: TFieldKey
|
|
79
|
+
operation: 'delete'
|
|
80
|
+
item: TTypeInfo['item']
|
|
81
|
+
context: import('../access/types.js').AccessContext
|
|
82
|
+
addValidationError: (msg: string) => void
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Arguments for field-level beforeOperation hook
|
|
87
|
+
* Used for side effects before database write
|
|
88
|
+
*/
|
|
89
|
+
export type FieldBeforeOperationHookArgs<
|
|
90
|
+
TTypeInfo extends TypeInfo,
|
|
91
|
+
TFieldKey extends FieldKeys<TTypeInfo['fields']> = FieldKeys<TTypeInfo['fields']>,
|
|
92
|
+
> =
|
|
93
|
+
| {
|
|
94
|
+
listKey: string
|
|
95
|
+
fieldKey: TFieldKey
|
|
96
|
+
operation: 'create'
|
|
97
|
+
inputData: TTypeInfo['inputs']['create']
|
|
98
|
+
resolvedData: TTypeInfo['inputs']['create']
|
|
99
|
+
context: import('../access/types.js').AccessContext
|
|
100
|
+
}
|
|
101
|
+
| {
|
|
102
|
+
listKey: string
|
|
103
|
+
fieldKey: TFieldKey
|
|
104
|
+
operation: 'update'
|
|
105
|
+
inputData: TTypeInfo['inputs']['update']
|
|
106
|
+
item: TTypeInfo['item']
|
|
107
|
+
resolvedData: TTypeInfo['inputs']['update']
|
|
108
|
+
context: import('../access/types.js').AccessContext
|
|
109
|
+
}
|
|
110
|
+
| {
|
|
111
|
+
listKey: string
|
|
112
|
+
fieldKey: TFieldKey
|
|
113
|
+
operation: 'delete'
|
|
114
|
+
item: TTypeInfo['item']
|
|
115
|
+
context: import('../access/types.js').AccessContext
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Arguments for field-level afterOperation hook
|
|
120
|
+
* Used for side effects after database operation
|
|
121
|
+
*/
|
|
122
|
+
export type FieldAfterOperationHookArgs<
|
|
123
|
+
TTypeInfo extends TypeInfo,
|
|
124
|
+
TFieldKey extends FieldKeys<TTypeInfo['fields']> = FieldKeys<TTypeInfo['fields']>,
|
|
125
|
+
> =
|
|
126
|
+
| {
|
|
127
|
+
listKey: string
|
|
128
|
+
fieldKey: TFieldKey
|
|
129
|
+
operation: 'create'
|
|
130
|
+
inputData: TTypeInfo['inputs']['create']
|
|
131
|
+
item: TTypeInfo['item']
|
|
132
|
+
resolvedData: TTypeInfo['inputs']['create']
|
|
133
|
+
context: import('../access/types.js').AccessContext
|
|
134
|
+
}
|
|
135
|
+
| {
|
|
136
|
+
listKey: string
|
|
137
|
+
fieldKey: TFieldKey
|
|
138
|
+
operation: 'update'
|
|
139
|
+
inputData: TTypeInfo['inputs']['update']
|
|
140
|
+
originalItem: TTypeInfo['item']
|
|
141
|
+
item: TTypeInfo['item']
|
|
142
|
+
resolvedData: TTypeInfo['inputs']['update']
|
|
143
|
+
context: import('../access/types.js').AccessContext
|
|
144
|
+
}
|
|
145
|
+
| {
|
|
146
|
+
listKey: string
|
|
147
|
+
fieldKey: TFieldKey
|
|
148
|
+
operation: 'delete'
|
|
149
|
+
originalItem: TTypeInfo['item']
|
|
150
|
+
context: import('../access/types.js').AccessContext
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Arguments for field-level resolveOutput hook
|
|
155
|
+
* Used to transform field values after database read
|
|
156
|
+
*/
|
|
157
|
+
export type FieldResolveOutputHookArgs<
|
|
158
|
+
TTypeInfo extends TypeInfo,
|
|
159
|
+
TFieldKey extends FieldKeys<TTypeInfo['fields']> = FieldKeys<TTypeInfo['fields']>,
|
|
160
|
+
> = {
|
|
161
|
+
operation: 'query'
|
|
162
|
+
value: GetFieldValueType<TTypeInfo['fields'], TFieldKey>
|
|
163
|
+
item: TTypeInfo['item']
|
|
164
|
+
listKey: string
|
|
165
|
+
fieldName: TFieldKey
|
|
166
|
+
context: import('../access/types.js').AccessContext
|
|
167
|
+
}
|
|
168
|
+
|
|
17
169
|
/**
|
|
18
170
|
* Field-level hooks for data transformation and side effects
|
|
19
171
|
* Allows field types to define custom behavior during operations
|
|
@@ -55,25 +207,7 @@ export type FieldHooks<
|
|
|
55
207
|
* ```
|
|
56
208
|
*/
|
|
57
209
|
resolveInput?: (
|
|
58
|
-
args:
|
|
59
|
-
| {
|
|
60
|
-
listKey: string
|
|
61
|
-
fieldKey: TFieldKey
|
|
62
|
-
operation: 'create'
|
|
63
|
-
inputData: TTypeInfo['inputs']['create']
|
|
64
|
-
item: undefined
|
|
65
|
-
resolvedData: TTypeInfo['inputs']['create']
|
|
66
|
-
context: import('../access/types.js').AccessContext
|
|
67
|
-
}
|
|
68
|
-
| {
|
|
69
|
-
listKey: string
|
|
70
|
-
fieldKey: TFieldKey
|
|
71
|
-
operation: 'update'
|
|
72
|
-
inputData: TTypeInfo['inputs']['update']
|
|
73
|
-
item: TTypeInfo['item']
|
|
74
|
-
resolvedData: TTypeInfo['inputs']['update']
|
|
75
|
-
context: import('../access/types.js').AccessContext
|
|
76
|
-
},
|
|
210
|
+
args: FieldResolveInputHookArgs<TTypeInfo, TFieldKey>,
|
|
77
211
|
) =>
|
|
78
212
|
| Promise<GetFieldValueType<TTypeInfo['fields'], TFieldKey> | undefined>
|
|
79
213
|
| GetFieldValueType<TTypeInfo['fields'], TFieldKey>
|
|
@@ -95,37 +229,12 @@ export type FieldHooks<
|
|
|
95
229
|
* }
|
|
96
230
|
* ```
|
|
97
231
|
*/
|
|
98
|
-
validate?: (
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
inputData: TTypeInfo['inputs']['create']
|
|
105
|
-
item: undefined
|
|
106
|
-
resolvedData: TTypeInfo['inputs']['create']
|
|
107
|
-
context: import('../access/types.js').AccessContext
|
|
108
|
-
addValidationError: (msg: string) => void
|
|
109
|
-
}
|
|
110
|
-
| {
|
|
111
|
-
listKey: string
|
|
112
|
-
fieldKey: TFieldKey
|
|
113
|
-
operation: 'update'
|
|
114
|
-
inputData: TTypeInfo['inputs']['update']
|
|
115
|
-
item: TTypeInfo['item']
|
|
116
|
-
resolvedData: TTypeInfo['inputs']['update']
|
|
117
|
-
context: import('../access/types.js').AccessContext
|
|
118
|
-
addValidationError: (msg: string) => void
|
|
119
|
-
}
|
|
120
|
-
| {
|
|
121
|
-
listKey: string
|
|
122
|
-
fieldKey: TFieldKey
|
|
123
|
-
operation: 'delete'
|
|
124
|
-
item: TTypeInfo['item']
|
|
125
|
-
context: import('../access/types.js').AccessContext
|
|
126
|
-
addValidationError: (msg: string) => void
|
|
127
|
-
},
|
|
128
|
-
) => Promise<void> | void
|
|
232
|
+
validate?: (args: FieldValidateHookArgs<TTypeInfo, TFieldKey>) => Promise<void> | void
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* @deprecated Use 'validate' instead. This alias is provided for backwards compatibility.
|
|
236
|
+
*/
|
|
237
|
+
validateInput?: (args: FieldValidateHookArgs<TTypeInfo, TFieldKey>) => Promise<void> | void
|
|
129
238
|
|
|
130
239
|
/**
|
|
131
240
|
* Perform side effects before database write
|
|
@@ -146,31 +255,7 @@ export type FieldHooks<
|
|
|
146
255
|
* ```
|
|
147
256
|
*/
|
|
148
257
|
beforeOperation?: (
|
|
149
|
-
args:
|
|
150
|
-
| {
|
|
151
|
-
listKey: string
|
|
152
|
-
fieldKey: TFieldKey
|
|
153
|
-
operation: 'create'
|
|
154
|
-
inputData: TTypeInfo['inputs']['create']
|
|
155
|
-
resolvedData: TTypeInfo['inputs']['create']
|
|
156
|
-
context: import('../access/types.js').AccessContext
|
|
157
|
-
}
|
|
158
|
-
| {
|
|
159
|
-
listKey: string
|
|
160
|
-
fieldKey: TFieldKey
|
|
161
|
-
operation: 'update'
|
|
162
|
-
inputData: TTypeInfo['inputs']['update']
|
|
163
|
-
item: TTypeInfo['item']
|
|
164
|
-
resolvedData: TTypeInfo['inputs']['update']
|
|
165
|
-
context: import('../access/types.js').AccessContext
|
|
166
|
-
}
|
|
167
|
-
| {
|
|
168
|
-
listKey: string
|
|
169
|
-
fieldKey: TFieldKey
|
|
170
|
-
operation: 'delete'
|
|
171
|
-
item: TTypeInfo['item']
|
|
172
|
-
context: import('../access/types.js').AccessContext
|
|
173
|
-
},
|
|
258
|
+
args: FieldBeforeOperationHookArgs<TTypeInfo, TFieldKey>,
|
|
174
259
|
) => Promise<void> | void
|
|
175
260
|
|
|
176
261
|
/**
|
|
@@ -191,35 +276,7 @@ export type FieldHooks<
|
|
|
191
276
|
* }
|
|
192
277
|
* ```
|
|
193
278
|
*/
|
|
194
|
-
afterOperation?: (
|
|
195
|
-
args:
|
|
196
|
-
| {
|
|
197
|
-
listKey: string
|
|
198
|
-
fieldKey: TFieldKey
|
|
199
|
-
operation: 'create'
|
|
200
|
-
inputData: TTypeInfo['inputs']['create']
|
|
201
|
-
item: TTypeInfo['item']
|
|
202
|
-
resolvedData: TTypeInfo['inputs']['create']
|
|
203
|
-
context: import('../access/types.js').AccessContext
|
|
204
|
-
}
|
|
205
|
-
| {
|
|
206
|
-
listKey: string
|
|
207
|
-
fieldKey: TFieldKey
|
|
208
|
-
operation: 'update'
|
|
209
|
-
inputData: TTypeInfo['inputs']['update']
|
|
210
|
-
originalItem: TTypeInfo['item']
|
|
211
|
-
item: TTypeInfo['item']
|
|
212
|
-
resolvedData: TTypeInfo['inputs']['update']
|
|
213
|
-
context: import('../access/types.js').AccessContext
|
|
214
|
-
}
|
|
215
|
-
| {
|
|
216
|
-
listKey: string
|
|
217
|
-
fieldKey: TFieldKey
|
|
218
|
-
operation: 'delete'
|
|
219
|
-
originalItem: TTypeInfo['item']
|
|
220
|
-
context: import('../access/types.js').AccessContext
|
|
221
|
-
},
|
|
222
|
-
) => Promise<void> | void
|
|
279
|
+
afterOperation?: (args: FieldAfterOperationHookArgs<TTypeInfo, TFieldKey>) => Promise<void> | void
|
|
223
280
|
|
|
224
281
|
/**
|
|
225
282
|
* Transform field value after database read
|
|
@@ -236,14 +293,9 @@ export type FieldHooks<
|
|
|
236
293
|
* }
|
|
237
294
|
* ```
|
|
238
295
|
*/
|
|
239
|
-
resolveOutput?: (
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
item: TTypeInfo['item']
|
|
243
|
-
listKey: string
|
|
244
|
-
fieldName: TFieldKey
|
|
245
|
-
context: import('../access/types.js').AccessContext
|
|
246
|
-
}) => GetFieldValueType<TTypeInfo['fields'], TFieldKey> | undefined
|
|
296
|
+
resolveOutput?: (
|
|
297
|
+
args: FieldResolveOutputHookArgs<TTypeInfo, TFieldKey>,
|
|
298
|
+
) => GetFieldValueType<TTypeInfo['fields'], TFieldKey> | undefined
|
|
247
299
|
}
|
|
248
300
|
|
|
249
301
|
/**
|
|
@@ -274,7 +326,11 @@ export type ResultExtensionConfig = {
|
|
|
274
326
|
|
|
275
327
|
export type BaseFieldConfig<TTypeInfo extends TypeInfo> = {
|
|
276
328
|
type: string
|
|
277
|
-
access?: FieldAccess
|
|
329
|
+
access?: FieldAccess<
|
|
330
|
+
TTypeInfo['item'],
|
|
331
|
+
TTypeInfo['inputs']['create'],
|
|
332
|
+
TTypeInfo['inputs']['update']
|
|
333
|
+
>
|
|
278
334
|
defaultValue?: unknown
|
|
279
335
|
hooks?: FieldHooks<TTypeInfo>
|
|
280
336
|
/**
|
|
@@ -539,6 +595,37 @@ export type RelationshipField<TTypeInfo extends TypeInfo = TypeInfo> =
|
|
|
539
595
|
* ```
|
|
540
596
|
*/
|
|
541
597
|
foreignKey?: boolean | { map?: string }
|
|
598
|
+
/**
|
|
599
|
+
* Extend or modify the generated Prisma schema lines for this relationship field
|
|
600
|
+
* Receives the generated FK line (if applicable) and relation line
|
|
601
|
+
* Returns the modified lines
|
|
602
|
+
*
|
|
603
|
+
* @example Add onDelete cascade for self-referential relationship
|
|
604
|
+
* ```typescript
|
|
605
|
+
* parent: relationship({
|
|
606
|
+
* ref: 'Category.children',
|
|
607
|
+
* db: {
|
|
608
|
+
* foreignKey: true,
|
|
609
|
+
* extendPrismaSchema: ({ fkLine, relationLine }) => ({
|
|
610
|
+
* fkLine,
|
|
611
|
+
* relationLine: relationLine.replace(
|
|
612
|
+
* '@relation(',
|
|
613
|
+
* '@relation(onDelete: SetNull, '
|
|
614
|
+
* )
|
|
615
|
+
* })
|
|
616
|
+
* }
|
|
617
|
+
* })
|
|
618
|
+
* ```
|
|
619
|
+
*/
|
|
620
|
+
extendPrismaSchema?: (lines: {
|
|
621
|
+
/** The foreign key field line (e.g., "parentId String?"), only present for single relationships that own the FK */
|
|
622
|
+
fkLine?: string
|
|
623
|
+
/** The relation field line (e.g., "parent Category? @relation(...)") */
|
|
624
|
+
relationLine: string
|
|
625
|
+
}) => {
|
|
626
|
+
fkLine?: string
|
|
627
|
+
relationLine: string
|
|
628
|
+
}
|
|
542
629
|
}
|
|
543
630
|
ui?: {
|
|
544
631
|
displayMode?: 'select' | 'cards'
|
|
@@ -752,6 +839,48 @@ export type OperationAccess<T = any> = {
|
|
|
752
839
|
delete?: AccessControl<T>
|
|
753
840
|
}
|
|
754
841
|
|
|
842
|
+
/**
|
|
843
|
+
* List-level access control configuration
|
|
844
|
+
* Supports two patterns:
|
|
845
|
+
*
|
|
846
|
+
* 1. Function shorthand - applies to all CRUD operations:
|
|
847
|
+
* `access: isAdmin`
|
|
848
|
+
*
|
|
849
|
+
* 2. Object form - configure operations individually:
|
|
850
|
+
* `access: { operation: { query: () => true, create: isAdmin } }`
|
|
851
|
+
*
|
|
852
|
+
* @example Function shorthand
|
|
853
|
+
* ```typescript
|
|
854
|
+
* const isAdmin = ({ session }) => session?.role === 'admin'
|
|
855
|
+
*
|
|
856
|
+
* list({
|
|
857
|
+
* access: isAdmin, // Applies to query, create, update, delete
|
|
858
|
+
* fields: { ... }
|
|
859
|
+
* })
|
|
860
|
+
* ```
|
|
861
|
+
*
|
|
862
|
+
* @example Object form
|
|
863
|
+
* ```typescript
|
|
864
|
+
* list({
|
|
865
|
+
* access: {
|
|
866
|
+
* operation: {
|
|
867
|
+
* query: () => true,
|
|
868
|
+
* create: isAdmin,
|
|
869
|
+
* update: isOwner,
|
|
870
|
+
* delete: isAdmin,
|
|
871
|
+
* }
|
|
872
|
+
* },
|
|
873
|
+
* fields: { ... }
|
|
874
|
+
* })
|
|
875
|
+
* ```
|
|
876
|
+
*/
|
|
877
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
878
|
+
export type ListAccessControl<T = any> =
|
|
879
|
+
| AccessControl<T>
|
|
880
|
+
| {
|
|
881
|
+
operation?: OperationAccess<T>
|
|
882
|
+
}
|
|
883
|
+
|
|
755
884
|
/**
|
|
756
885
|
* Hook arguments for resolveInput hook
|
|
757
886
|
* Uses discriminated union to provide proper types based on operation
|
|
@@ -911,10 +1040,19 @@ export type Hooks<
|
|
|
911
1040
|
|
|
912
1041
|
// Generic `any` default allows ListConfig to work with any list item type
|
|
913
1042
|
// This is needed because the item type varies per list and is inferred from Prisma models
|
|
1043
|
+
/**
|
|
1044
|
+
* Internal list configuration type (after normalization by list() function)
|
|
1045
|
+
* Access control is always in object form internally.
|
|
1046
|
+
* Use list() function which accepts both function shorthand and object form.
|
|
1047
|
+
*/
|
|
914
1048
|
export type ListConfig<TTypeInfo extends TypeInfo> = {
|
|
915
1049
|
// Field configs are automatically transformed to inject the full TypeInfo
|
|
916
1050
|
// This enables proper typing in field hooks where item, create input, and update input are all typed
|
|
917
1051
|
fields: FieldsWithTypeInfo<TTypeInfo>
|
|
1052
|
+
/**
|
|
1053
|
+
* Access control configuration for this list (normalized object form).
|
|
1054
|
+
* The list() function normalizes function shorthand to this object form.
|
|
1055
|
+
*/
|
|
918
1056
|
access?: {
|
|
919
1057
|
operation?: OperationAccess<TTypeInfo['item']>
|
|
920
1058
|
}
|
|
@@ -923,6 +1061,58 @@ export type ListConfig<TTypeInfo extends TypeInfo> = {
|
|
|
923
1061
|
* MCP server configuration for this list
|
|
924
1062
|
*/
|
|
925
1063
|
mcp?: ListMcpConfig
|
|
1064
|
+
/**
|
|
1065
|
+
* Restricts this list to a single record (singleton pattern)
|
|
1066
|
+
* When true:
|
|
1067
|
+
* - Prevents creating multiple records
|
|
1068
|
+
* - Auto-creates the single record on first access (if autoCreate: true, which is the default)
|
|
1069
|
+
* - Provides a get() method for easy access to the singleton
|
|
1070
|
+
* - Blocks delete and findMany operations
|
|
1071
|
+
* - Changes UI to show edit form instead of list view
|
|
1072
|
+
*
|
|
1073
|
+
* @example Simple boolean (auto-create enabled)
|
|
1074
|
+
* ```typescript
|
|
1075
|
+
* isSingleton: true
|
|
1076
|
+
* ```
|
|
1077
|
+
*
|
|
1078
|
+
* @example With options
|
|
1079
|
+
* ```typescript
|
|
1080
|
+
* isSingleton: {
|
|
1081
|
+
* autoCreate: false // Don't auto-create, must be created manually
|
|
1082
|
+
* }
|
|
1083
|
+
* ```
|
|
1084
|
+
*/
|
|
1085
|
+
isSingleton?:
|
|
1086
|
+
| boolean
|
|
1087
|
+
| {
|
|
1088
|
+
/**
|
|
1089
|
+
* Auto-create the singleton record on first access using field defaults
|
|
1090
|
+
* @default true
|
|
1091
|
+
*/
|
|
1092
|
+
autoCreate?: boolean
|
|
1093
|
+
}
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
/**
|
|
1097
|
+
* Input type for the list() function
|
|
1098
|
+
* Accepts both function shorthand and object form for access control.
|
|
1099
|
+
*/
|
|
1100
|
+
export type ListConfigInput<TTypeInfo extends TypeInfo> = Omit<ListConfig<TTypeInfo>, 'access'> & {
|
|
1101
|
+
/**
|
|
1102
|
+
* Access control configuration for this list.
|
|
1103
|
+
* Supports both function shorthand and object form.
|
|
1104
|
+
*
|
|
1105
|
+
* @example Function shorthand (applies to all operations)
|
|
1106
|
+
* ```typescript
|
|
1107
|
+
* access: isAdmin
|
|
1108
|
+
* ```
|
|
1109
|
+
*
|
|
1110
|
+
* @example Object form (per-operation)
|
|
1111
|
+
* ```typescript
|
|
1112
|
+
* access: { operation: { query: () => true, create: isAdmin } }
|
|
1113
|
+
* ```
|
|
1114
|
+
*/
|
|
1115
|
+
access?: ListAccessControl<TTypeInfo['item']>
|
|
926
1116
|
}
|
|
927
1117
|
|
|
928
1118
|
/**
|