@opensaas/stack-core 0.12.1 → 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 +291 -0
- package/README.md +6 -3
- 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 +341 -82
- package/dist/config/types.d.ts.map +1 -1
- package/dist/context/index.d.ts.map +1 -1
- package/dist/context/index.js +330 -60
- package/dist/context/index.js.map +1 -1
- package/dist/context/nested-operations.d.ts.map +1 -1
- package/dist/context/nested-operations.js +38 -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/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 +402 -91
- package/src/context/index.ts +421 -82
- package/src/context/nested-operations.ts +40 -25
- package/src/hooks/index.ts +66 -14
- package/src/index.ts +11 -0
- package/tests/access.test.ts +28 -28
- package/tests/config.test.ts +20 -3
- package/tests/nested-access-and-hooks.test.ts +8 -3
- package/tests/singleton.test.ts +329 -0
- package/tests/sudo.test.ts +2 -13
- package/tsconfig.tsbuildinfo +1 -1
package/dist/config/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/config/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/config/index.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAA;AAGnD;;;GAGG;AACH,SAAS,mBAAmB,CAC1B,MAAwC;IAExC,IAAI,CAAC,MAAM;QAAE,OAAO,SAAS,CAAA;IAE7B,wEAAwE;IACxE,IAAI,OAAO,MAAM,KAAK,UAAU,EAAE,CAAC;QACjC,MAAM,EAAE,GAAG,MAA0B,CAAA;QACrC,OAAO;YACL,SAAS,EAAE;gBACT,KAAK,EAAE,EAAE;gBACT,MAAM,EAAE,EAAE;gBACV,MAAM,EAAE,EAAE;gBACV,MAAM,EAAE,EAAE;aACX;SACF,CAAA;IACH,CAAC;IAED,yBAAyB;IACzB,OAAO,MAAM,CAAA;AACf,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,MAAM,CAAC,UAA0B;IAC/C,wEAAwE;IACxE,IAAI,CAAC,UAAU,CAAC,OAAO,IAAI,UAAU,CAAC,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC3D,OAAO,UAAU,CAAA;IACnB,CAAC;IAED,qCAAqC;IACrC,OAAO,cAAc,CAAC,UAAU,CAAC,CAAA;AACnC,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqDG;AACH,MAAM,UAAU,IAAI,CAClB,MAAkC;IAElC,oDAAoD;IACpD,MAAM,gBAAgB,GAAG;QACvB,GAAG,MAAM;QACT,MAAM,EAAE,mBAAmB,CAAC,MAAM,CAAC,MAAM,CAAC;KAC3C,CAAA;IAED,0CAA0C;IAC1C,8DAA8D;IAC9D,OAAO,gBAAyC,CAAA;AAClD,CAAC"}
|
package/dist/config/types.d.ts
CHANGED
|
@@ -4,6 +4,126 @@ import type { z } from 'zod';
|
|
|
4
4
|
* Field configuration types
|
|
5
5
|
*/
|
|
6
6
|
export type FieldType = 'text' | 'integer' | 'checkbox' | 'timestamp' | 'password' | 'select' | 'relationship' | string;
|
|
7
|
+
/**
|
|
8
|
+
* Field-level hook argument types (exported for user annotations)
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Arguments for field-level resolveInput hook
|
|
12
|
+
* Used to transform field values before database write
|
|
13
|
+
*/
|
|
14
|
+
export type FieldResolveInputHookArgs<TTypeInfo extends TypeInfo, TFieldKey extends FieldKeys<TTypeInfo['fields']> = FieldKeys<TTypeInfo['fields']>> = {
|
|
15
|
+
listKey: string;
|
|
16
|
+
fieldKey: TFieldKey;
|
|
17
|
+
operation: 'create';
|
|
18
|
+
inputData: TTypeInfo['inputs']['create'];
|
|
19
|
+
item: undefined;
|
|
20
|
+
resolvedData: TTypeInfo['inputs']['create'];
|
|
21
|
+
context: import('../access/types.js').AccessContext;
|
|
22
|
+
} | {
|
|
23
|
+
listKey: string;
|
|
24
|
+
fieldKey: TFieldKey;
|
|
25
|
+
operation: 'update';
|
|
26
|
+
inputData: TTypeInfo['inputs']['update'];
|
|
27
|
+
item: TTypeInfo['item'];
|
|
28
|
+
resolvedData: TTypeInfo['inputs']['update'];
|
|
29
|
+
context: import('../access/types.js').AccessContext;
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* Arguments for field-level validate hook
|
|
33
|
+
* Used for custom validation logic
|
|
34
|
+
*/
|
|
35
|
+
export type FieldValidateHookArgs<TTypeInfo extends TypeInfo, TFieldKey extends FieldKeys<TTypeInfo['fields']> = FieldKeys<TTypeInfo['fields']>> = {
|
|
36
|
+
listKey: string;
|
|
37
|
+
fieldKey: TFieldKey;
|
|
38
|
+
operation: 'create';
|
|
39
|
+
inputData: TTypeInfo['inputs']['create'];
|
|
40
|
+
item: undefined;
|
|
41
|
+
resolvedData: TTypeInfo['inputs']['create'];
|
|
42
|
+
context: import('../access/types.js').AccessContext;
|
|
43
|
+
addValidationError: (msg: string) => void;
|
|
44
|
+
} | {
|
|
45
|
+
listKey: string;
|
|
46
|
+
fieldKey: TFieldKey;
|
|
47
|
+
operation: 'update';
|
|
48
|
+
inputData: TTypeInfo['inputs']['update'];
|
|
49
|
+
item: TTypeInfo['item'];
|
|
50
|
+
resolvedData: TTypeInfo['inputs']['update'];
|
|
51
|
+
context: import('../access/types.js').AccessContext;
|
|
52
|
+
addValidationError: (msg: string) => void;
|
|
53
|
+
} | {
|
|
54
|
+
listKey: string;
|
|
55
|
+
fieldKey: TFieldKey;
|
|
56
|
+
operation: 'delete';
|
|
57
|
+
item: TTypeInfo['item'];
|
|
58
|
+
context: import('../access/types.js').AccessContext;
|
|
59
|
+
addValidationError: (msg: string) => void;
|
|
60
|
+
};
|
|
61
|
+
/**
|
|
62
|
+
* Arguments for field-level beforeOperation hook
|
|
63
|
+
* Used for side effects before database write
|
|
64
|
+
*/
|
|
65
|
+
export type FieldBeforeOperationHookArgs<TTypeInfo extends TypeInfo, TFieldKey extends FieldKeys<TTypeInfo['fields']> = FieldKeys<TTypeInfo['fields']>> = {
|
|
66
|
+
listKey: string;
|
|
67
|
+
fieldKey: TFieldKey;
|
|
68
|
+
operation: 'create';
|
|
69
|
+
inputData: TTypeInfo['inputs']['create'];
|
|
70
|
+
resolvedData: TTypeInfo['inputs']['create'];
|
|
71
|
+
context: import('../access/types.js').AccessContext;
|
|
72
|
+
} | {
|
|
73
|
+
listKey: string;
|
|
74
|
+
fieldKey: TFieldKey;
|
|
75
|
+
operation: 'update';
|
|
76
|
+
inputData: TTypeInfo['inputs']['update'];
|
|
77
|
+
item: TTypeInfo['item'];
|
|
78
|
+
resolvedData: TTypeInfo['inputs']['update'];
|
|
79
|
+
context: import('../access/types.js').AccessContext;
|
|
80
|
+
} | {
|
|
81
|
+
listKey: string;
|
|
82
|
+
fieldKey: TFieldKey;
|
|
83
|
+
operation: 'delete';
|
|
84
|
+
item: TTypeInfo['item'];
|
|
85
|
+
context: import('../access/types.js').AccessContext;
|
|
86
|
+
};
|
|
87
|
+
/**
|
|
88
|
+
* Arguments for field-level afterOperation hook
|
|
89
|
+
* Used for side effects after database operation
|
|
90
|
+
*/
|
|
91
|
+
export type FieldAfterOperationHookArgs<TTypeInfo extends TypeInfo, TFieldKey extends FieldKeys<TTypeInfo['fields']> = FieldKeys<TTypeInfo['fields']>> = {
|
|
92
|
+
listKey: string;
|
|
93
|
+
fieldKey: TFieldKey;
|
|
94
|
+
operation: 'create';
|
|
95
|
+
inputData: TTypeInfo['inputs']['create'];
|
|
96
|
+
item: TTypeInfo['item'];
|
|
97
|
+
resolvedData: TTypeInfo['inputs']['create'];
|
|
98
|
+
context: import('../access/types.js').AccessContext;
|
|
99
|
+
} | {
|
|
100
|
+
listKey: string;
|
|
101
|
+
fieldKey: TFieldKey;
|
|
102
|
+
operation: 'update';
|
|
103
|
+
inputData: TTypeInfo['inputs']['update'];
|
|
104
|
+
originalItem: TTypeInfo['item'];
|
|
105
|
+
item: TTypeInfo['item'];
|
|
106
|
+
resolvedData: TTypeInfo['inputs']['update'];
|
|
107
|
+
context: import('../access/types.js').AccessContext;
|
|
108
|
+
} | {
|
|
109
|
+
listKey: string;
|
|
110
|
+
fieldKey: TFieldKey;
|
|
111
|
+
operation: 'delete';
|
|
112
|
+
originalItem: TTypeInfo['item'];
|
|
113
|
+
context: import('../access/types.js').AccessContext;
|
|
114
|
+
};
|
|
115
|
+
/**
|
|
116
|
+
* Arguments for field-level resolveOutput hook
|
|
117
|
+
* Used to transform field values after database read
|
|
118
|
+
*/
|
|
119
|
+
export type FieldResolveOutputHookArgs<TTypeInfo extends TypeInfo, TFieldKey extends FieldKeys<TTypeInfo['fields']> = FieldKeys<TTypeInfo['fields']>> = {
|
|
120
|
+
operation: 'query';
|
|
121
|
+
value: GetFieldValueType<TTypeInfo['fields'], TFieldKey>;
|
|
122
|
+
item: TTypeInfo['item'];
|
|
123
|
+
listKey: string;
|
|
124
|
+
fieldName: TFieldKey;
|
|
125
|
+
context: import('../access/types.js').AccessContext;
|
|
126
|
+
};
|
|
7
127
|
/**
|
|
8
128
|
* Field-level hooks for data transformation and side effects
|
|
9
129
|
* Allows field types to define custom behavior during operations
|
|
@@ -30,31 +150,39 @@ export type FieldHooks<TTypeInfo extends TypeInfo, TFieldKey extends FieldKeys<T
|
|
|
30
150
|
*
|
|
31
151
|
* @example
|
|
32
152
|
* ```typescript
|
|
33
|
-
* resolveInput: async ({
|
|
153
|
+
* resolveInput: async ({ listKey, fieldKey, operation, inputData, item, resolvedData, context }) => {
|
|
34
154
|
* // For create operations, item is undefined
|
|
35
155
|
* // For update operations, item is the existing record
|
|
36
|
-
*
|
|
37
|
-
*
|
|
156
|
+
* const fieldValue = resolvedData[fieldKey]
|
|
157
|
+
* if (typeof fieldValue === 'string' && !isHashedPassword(fieldValue)) {
|
|
158
|
+
* return await hashPassword(fieldValue)
|
|
38
159
|
* }
|
|
39
|
-
* return
|
|
160
|
+
* return fieldValue
|
|
40
161
|
* }
|
|
41
162
|
* ```
|
|
42
163
|
*/
|
|
43
|
-
resolveInput?: (args:
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
164
|
+
resolveInput?: (args: FieldResolveInputHookArgs<TTypeInfo, TFieldKey>) => Promise<GetFieldValueType<TTypeInfo['fields'], TFieldKey> | undefined> | GetFieldValueType<TTypeInfo['fields'], TFieldKey> | undefined;
|
|
165
|
+
/**
|
|
166
|
+
* Validate field value after resolveInput
|
|
167
|
+
* Called during create/update operations after resolveInput hooks but before database write
|
|
168
|
+
* Use addValidationError to report validation failures
|
|
169
|
+
*
|
|
170
|
+
* @example
|
|
171
|
+
* ```typescript
|
|
172
|
+
* validate: async ({ listKey, fieldKey, operation, inputData, item, resolvedData, context, addValidationError }) => {
|
|
173
|
+
* if (operation === 'delete') return
|
|
174
|
+
* const fieldValue = resolvedData[fieldKey]
|
|
175
|
+
* if (typeof fieldValue === 'string' && fieldValue.includes('spam')) {
|
|
176
|
+
* addValidationError('Field cannot contain spam')
|
|
177
|
+
* }
|
|
178
|
+
* }
|
|
179
|
+
* ```
|
|
180
|
+
*/
|
|
181
|
+
validate?: (args: FieldValidateHookArgs<TTypeInfo, TFieldKey>) => Promise<void> | void;
|
|
182
|
+
/**
|
|
183
|
+
* @deprecated Use 'validate' instead. This alias is provided for backwards compatibility.
|
|
184
|
+
*/
|
|
185
|
+
validateInput?: (args: FieldValidateHookArgs<TTypeInfo, TFieldKey>) => Promise<void> | void;
|
|
58
186
|
/**
|
|
59
187
|
* Perform side effects before database write
|
|
60
188
|
* Called during create/update/delete operations after validation and access control
|
|
@@ -62,66 +190,37 @@ export type FieldHooks<TTypeInfo extends TypeInfo, TFieldKey extends FieldKeys<T
|
|
|
62
190
|
*
|
|
63
191
|
* @example
|
|
64
192
|
* ```typescript
|
|
65
|
-
* beforeOperation: async ({
|
|
193
|
+
* beforeOperation: async ({ listKey, fieldKey, operation, inputData, item, resolvedData, context }) => {
|
|
66
194
|
* // For create operations, item is undefined
|
|
67
195
|
* // For update/delete operations, item is the existing record
|
|
196
|
+
* const fieldValue = resolvedData?.[fieldKey]
|
|
68
197
|
* if (operation === 'update' && item) {
|
|
69
|
-
* console.log(`Updating field from ${item
|
|
198
|
+
* console.log(`Updating field from ${item[fieldKey]} to ${fieldValue}`)
|
|
70
199
|
* }
|
|
71
200
|
* await sendAuditLog({ operation, item })
|
|
72
201
|
* }
|
|
73
202
|
* ```
|
|
74
203
|
*/
|
|
75
|
-
beforeOperation?: (args:
|
|
76
|
-
operation: 'create';
|
|
77
|
-
resolvedValue: GetFieldValueType<TTypeInfo['fields'], TFieldKey> | undefined;
|
|
78
|
-
item: undefined;
|
|
79
|
-
listKey: string;
|
|
80
|
-
fieldName: TFieldKey;
|
|
81
|
-
context: import('../access/types.js').AccessContext;
|
|
82
|
-
} | {
|
|
83
|
-
operation: 'update' | 'delete';
|
|
84
|
-
resolvedValue: GetFieldValueType<TTypeInfo['fields'], TFieldKey> | undefined;
|
|
85
|
-
item: TTypeInfo['item'];
|
|
86
|
-
listKey: string;
|
|
87
|
-
fieldName: TFieldKey;
|
|
88
|
-
context: import('../access/types.js').AccessContext;
|
|
89
|
-
}) => Promise<void> | void;
|
|
204
|
+
beforeOperation?: (args: FieldBeforeOperationHookArgs<TTypeInfo, TFieldKey>) => Promise<void> | void;
|
|
90
205
|
/**
|
|
91
206
|
* Perform side effects after database operation
|
|
92
|
-
* Called after any database operation (create/update/delete
|
|
207
|
+
* Called after any database operation (create/update/delete)
|
|
93
208
|
* This should ONLY contain side effects (logging, cache invalidation, etc.), not data transformation
|
|
94
209
|
*
|
|
95
210
|
* @example
|
|
96
211
|
* ```typescript
|
|
97
|
-
* afterOperation: async ({ operation,
|
|
98
|
-
* // For
|
|
212
|
+
* afterOperation: async ({ listKey, fieldKey, operation, inputData, item, originalItem, resolvedData, context }) => {
|
|
213
|
+
* // For create operations, originalItem is undefined
|
|
99
214
|
* // For update/delete operations, originalItem is the item before the operation
|
|
100
215
|
* if (operation === 'update' && originalItem) {
|
|
101
|
-
* console.log('Changed from:', originalItem[
|
|
216
|
+
* console.log('Changed from:', originalItem[fieldKey], 'to:', item[fieldKey])
|
|
102
217
|
* }
|
|
103
218
|
* await invalidateCache({ listKey, itemId: item.id })
|
|
104
219
|
* await sendWebhook({ operation, item })
|
|
105
220
|
* }
|
|
106
221
|
* ```
|
|
107
222
|
*/
|
|
108
|
-
afterOperation?: (args:
|
|
109
|
-
operation: 'query' | 'create';
|
|
110
|
-
value: GetFieldValueType<TTypeInfo['fields'], TFieldKey> | undefined;
|
|
111
|
-
item: TTypeInfo['item'];
|
|
112
|
-
originalItem: undefined;
|
|
113
|
-
listKey: string;
|
|
114
|
-
fieldName: TFieldKey;
|
|
115
|
-
context: import('../access/types.js').AccessContext;
|
|
116
|
-
} | {
|
|
117
|
-
operation: 'update' | 'delete';
|
|
118
|
-
value: GetFieldValueType<TTypeInfo['fields'], TFieldKey> | undefined;
|
|
119
|
-
item: TTypeInfo['item'];
|
|
120
|
-
originalItem: TTypeInfo['item'];
|
|
121
|
-
listKey: string;
|
|
122
|
-
fieldName: TFieldKey;
|
|
123
|
-
context: import('../access/types.js').AccessContext;
|
|
124
|
-
}) => Promise<void> | void;
|
|
223
|
+
afterOperation?: (args: FieldAfterOperationHookArgs<TTypeInfo, TFieldKey>) => Promise<void> | void;
|
|
125
224
|
/**
|
|
126
225
|
* Transform field value after database read
|
|
127
226
|
* Called when returning results from query operations
|
|
@@ -137,14 +236,7 @@ export type FieldHooks<TTypeInfo extends TypeInfo, TFieldKey extends FieldKeys<T
|
|
|
137
236
|
* }
|
|
138
237
|
* ```
|
|
139
238
|
*/
|
|
140
|
-
resolveOutput?: (args:
|
|
141
|
-
operation: 'query';
|
|
142
|
-
value: GetFieldValueType<TTypeInfo['fields'], TFieldKey>;
|
|
143
|
-
item: TTypeInfo['item'];
|
|
144
|
-
listKey: string;
|
|
145
|
-
fieldName: TFieldKey;
|
|
146
|
-
context: import('../access/types.js').AccessContext;
|
|
147
|
-
}) => GetFieldValueType<TTypeInfo['fields'], TFieldKey> | undefined;
|
|
239
|
+
resolveOutput?: (args: FieldResolveOutputHookArgs<TTypeInfo, TFieldKey>) => GetFieldValueType<TTypeInfo['fields'], TFieldKey> | undefined;
|
|
148
240
|
};
|
|
149
241
|
/**
|
|
150
242
|
* Configuration for Prisma result extensions
|
|
@@ -173,7 +265,7 @@ export type ResultExtensionConfig = {
|
|
|
173
265
|
};
|
|
174
266
|
export type BaseFieldConfig<TTypeInfo extends TypeInfo> = {
|
|
175
267
|
type: string;
|
|
176
|
-
access?: FieldAccess
|
|
268
|
+
access?: FieldAccess<TTypeInfo['item'], TTypeInfo['inputs']['create'], TTypeInfo['inputs']['update']>;
|
|
177
269
|
defaultValue?: unknown;
|
|
178
270
|
hooks?: FieldHooks<TTypeInfo>;
|
|
179
271
|
/**
|
|
@@ -433,6 +525,37 @@ export type RelationshipField<TTypeInfo extends TypeInfo = TypeInfo> = BaseField
|
|
|
433
525
|
foreignKey?: boolean | {
|
|
434
526
|
map?: string;
|
|
435
527
|
};
|
|
528
|
+
/**
|
|
529
|
+
* Extend or modify the generated Prisma schema lines for this relationship field
|
|
530
|
+
* Receives the generated FK line (if applicable) and relation line
|
|
531
|
+
* Returns the modified lines
|
|
532
|
+
*
|
|
533
|
+
* @example Add onDelete cascade for self-referential relationship
|
|
534
|
+
* ```typescript
|
|
535
|
+
* parent: relationship({
|
|
536
|
+
* ref: 'Category.children',
|
|
537
|
+
* db: {
|
|
538
|
+
* foreignKey: true,
|
|
539
|
+
* extendPrismaSchema: ({ fkLine, relationLine }) => ({
|
|
540
|
+
* fkLine,
|
|
541
|
+
* relationLine: relationLine.replace(
|
|
542
|
+
* '@relation(',
|
|
543
|
+
* '@relation(onDelete: SetNull, '
|
|
544
|
+
* )
|
|
545
|
+
* })
|
|
546
|
+
* }
|
|
547
|
+
* })
|
|
548
|
+
* ```
|
|
549
|
+
*/
|
|
550
|
+
extendPrismaSchema?: (lines: {
|
|
551
|
+
/** The foreign key field line (e.g., "parentId String?"), only present for single relationships that own the FK */
|
|
552
|
+
fkLine?: string;
|
|
553
|
+
/** The relation field line (e.g., "parent Category? @relation(...)") */
|
|
554
|
+
relationLine: string;
|
|
555
|
+
}) => {
|
|
556
|
+
fkLine?: string;
|
|
557
|
+
relationLine: string;
|
|
558
|
+
};
|
|
436
559
|
};
|
|
437
560
|
ui?: {
|
|
438
561
|
displayMode?: 'select' | 'cards';
|
|
@@ -601,6 +724,44 @@ export type OperationAccess<T = any> = {
|
|
|
601
724
|
update?: AccessControl<T>;
|
|
602
725
|
delete?: AccessControl<T>;
|
|
603
726
|
};
|
|
727
|
+
/**
|
|
728
|
+
* List-level access control configuration
|
|
729
|
+
* Supports two patterns:
|
|
730
|
+
*
|
|
731
|
+
* 1. Function shorthand - applies to all CRUD operations:
|
|
732
|
+
* `access: isAdmin`
|
|
733
|
+
*
|
|
734
|
+
* 2. Object form - configure operations individually:
|
|
735
|
+
* `access: { operation: { query: () => true, create: isAdmin } }`
|
|
736
|
+
*
|
|
737
|
+
* @example Function shorthand
|
|
738
|
+
* ```typescript
|
|
739
|
+
* const isAdmin = ({ session }) => session?.role === 'admin'
|
|
740
|
+
*
|
|
741
|
+
* list({
|
|
742
|
+
* access: isAdmin, // Applies to query, create, update, delete
|
|
743
|
+
* fields: { ... }
|
|
744
|
+
* })
|
|
745
|
+
* ```
|
|
746
|
+
*
|
|
747
|
+
* @example Object form
|
|
748
|
+
* ```typescript
|
|
749
|
+
* list({
|
|
750
|
+
* access: {
|
|
751
|
+
* operation: {
|
|
752
|
+
* query: () => true,
|
|
753
|
+
* create: isAdmin,
|
|
754
|
+
* update: isOwner,
|
|
755
|
+
* delete: isAdmin,
|
|
756
|
+
* }
|
|
757
|
+
* },
|
|
758
|
+
* fields: { ... }
|
|
759
|
+
* })
|
|
760
|
+
* ```
|
|
761
|
+
*/
|
|
762
|
+
export type ListAccessControl<T = any> = AccessControl<T> | {
|
|
763
|
+
operation?: OperationAccess<T>;
|
|
764
|
+
};
|
|
604
765
|
/**
|
|
605
766
|
* Hook arguments for resolveInput hook
|
|
606
767
|
* Uses discriminated union to provide proper types based on operation
|
|
@@ -608,76 +769,125 @@ export type OperationAccess<T = any> = {
|
|
|
608
769
|
* - update: resolvedData is UpdateInput, item is the existing record
|
|
609
770
|
*/
|
|
610
771
|
export type ResolveInputHookArgs<TOutput = Record<string, unknown>, TCreateInput = Record<string, unknown>, TUpdateInput = Record<string, unknown>> = {
|
|
772
|
+
listKey: string;
|
|
611
773
|
operation: 'create';
|
|
774
|
+
inputData: TCreateInput;
|
|
612
775
|
resolvedData: TCreateInput;
|
|
613
776
|
item: undefined;
|
|
614
777
|
context: import('../access/types.js').AccessContext;
|
|
615
778
|
} | {
|
|
779
|
+
listKey: string;
|
|
616
780
|
operation: 'update';
|
|
781
|
+
inputData: TUpdateInput;
|
|
617
782
|
resolvedData: TUpdateInput;
|
|
618
783
|
item: TOutput;
|
|
619
784
|
context: import('../access/types.js').AccessContext;
|
|
620
785
|
};
|
|
621
786
|
/**
|
|
622
|
-
* Hook arguments for validateInput
|
|
787
|
+
* Hook arguments for validate hook (renamed from validateInput for Keystone compatibility)
|
|
623
788
|
* Uses discriminated union to provide proper types based on operation
|
|
624
789
|
* - create: resolvedData is CreateInput, item is undefined
|
|
625
790
|
* - update: resolvedData is UpdateInput, item is the existing record
|
|
791
|
+
* - delete: item is the item being deleted
|
|
626
792
|
*/
|
|
627
|
-
export type
|
|
793
|
+
export type ValidateHookArgs<TOutput = Record<string, unknown>, TCreateInput = Record<string, unknown>, TUpdateInput = Record<string, unknown>> = {
|
|
794
|
+
listKey: string;
|
|
628
795
|
operation: 'create';
|
|
796
|
+
inputData: TCreateInput;
|
|
629
797
|
resolvedData: TCreateInput;
|
|
630
798
|
item: undefined;
|
|
631
799
|
context: import('../access/types.js').AccessContext;
|
|
632
800
|
addValidationError: (msg: string) => void;
|
|
633
801
|
} | {
|
|
802
|
+
listKey: string;
|
|
634
803
|
operation: 'update';
|
|
804
|
+
inputData: TUpdateInput;
|
|
635
805
|
resolvedData: TUpdateInput;
|
|
636
806
|
item: TOutput;
|
|
637
807
|
context: import('../access/types.js').AccessContext;
|
|
638
808
|
addValidationError: (msg: string) => void;
|
|
809
|
+
} | {
|
|
810
|
+
listKey: string;
|
|
811
|
+
operation: 'delete';
|
|
812
|
+
item: TOutput;
|
|
813
|
+
context: import('../access/types.js').AccessContext;
|
|
814
|
+
addValidationError: (msg: string) => void;
|
|
639
815
|
};
|
|
640
816
|
/**
|
|
641
817
|
* Hook arguments for beforeOperation hook
|
|
642
818
|
* Uses discriminated union to provide proper types based on operation
|
|
643
|
-
* - create:
|
|
644
|
-
* - update:
|
|
645
|
-
* - delete:
|
|
819
|
+
* - create: has inputData and resolvedData, no item
|
|
820
|
+
* - update: has inputData, resolvedData, and item
|
|
821
|
+
* - delete: has item only
|
|
646
822
|
*/
|
|
647
|
-
export type BeforeOperationHookArgs<TOutput = Record<string, unknown>> = {
|
|
823
|
+
export type BeforeOperationHookArgs<TOutput = Record<string, unknown>, TCreateInput = Record<string, unknown>, TUpdateInput = Record<string, unknown>> = {
|
|
824
|
+
listKey: string;
|
|
648
825
|
operation: 'create';
|
|
826
|
+
inputData: TCreateInput;
|
|
827
|
+
resolvedData: TCreateInput;
|
|
649
828
|
context: import('../access/types.js').AccessContext;
|
|
650
829
|
} | {
|
|
651
|
-
|
|
830
|
+
listKey: string;
|
|
831
|
+
operation: 'update';
|
|
832
|
+
inputData: TUpdateInput;
|
|
833
|
+
item: TOutput;
|
|
834
|
+
resolvedData: TUpdateInput;
|
|
835
|
+
context: import('../access/types.js').AccessContext;
|
|
836
|
+
} | {
|
|
837
|
+
listKey: string;
|
|
838
|
+
operation: 'delete';
|
|
652
839
|
item: TOutput;
|
|
653
840
|
context: import('../access/types.js').AccessContext;
|
|
654
841
|
};
|
|
655
842
|
/**
|
|
656
843
|
* Hook arguments for afterOperation hook
|
|
657
844
|
* Uses discriminated union to provide proper types based on operation
|
|
658
|
-
* - create: has item, no originalItem
|
|
659
|
-
* - update: has item,
|
|
660
|
-
* - delete: has
|
|
845
|
+
* - create: has item, inputData, and resolvedData, no originalItem
|
|
846
|
+
* - update: has item, originalItem, inputData, and resolvedData
|
|
847
|
+
* - delete: has originalItem only
|
|
661
848
|
*/
|
|
662
|
-
export type AfterOperationHookArgs<TOutput = Record<string, unknown>> = {
|
|
849
|
+
export type AfterOperationHookArgs<TOutput = Record<string, unknown>, TCreateInput = Record<string, unknown>, TUpdateInput = Record<string, unknown>> = {
|
|
850
|
+
listKey: string;
|
|
663
851
|
operation: 'create';
|
|
852
|
+
inputData: TCreateInput;
|
|
664
853
|
item: TOutput;
|
|
665
|
-
|
|
854
|
+
resolvedData: TCreateInput;
|
|
666
855
|
context: import('../access/types.js').AccessContext;
|
|
667
856
|
} | {
|
|
668
|
-
|
|
857
|
+
listKey: string;
|
|
858
|
+
operation: 'update';
|
|
859
|
+
inputData: TUpdateInput;
|
|
860
|
+
originalItem: TOutput;
|
|
669
861
|
item: TOutput;
|
|
862
|
+
resolvedData: TUpdateInput;
|
|
863
|
+
context: import('../access/types.js').AccessContext;
|
|
864
|
+
} | {
|
|
865
|
+
listKey: string;
|
|
866
|
+
operation: 'delete';
|
|
670
867
|
originalItem: TOutput;
|
|
671
868
|
context: import('../access/types.js').AccessContext;
|
|
672
869
|
};
|
|
673
870
|
export type Hooks<TOutput = Record<string, unknown>, TCreateInput = Record<string, unknown>, TUpdateInput = Record<string, unknown>> = {
|
|
674
871
|
resolveInput?: (args: ResolveInputHookArgs<TOutput, TCreateInput, TUpdateInput>) => Promise<TCreateInput | TUpdateInput>;
|
|
675
|
-
|
|
676
|
-
beforeOperation?: (args: BeforeOperationHookArgs<TOutput>) => Promise<void>;
|
|
677
|
-
afterOperation?: (args: AfterOperationHookArgs<TOutput>) => Promise<void>;
|
|
872
|
+
validate?: (args: ValidateHookArgs<TOutput, TCreateInput, TUpdateInput>) => Promise<void>;
|
|
873
|
+
beforeOperation?: (args: BeforeOperationHookArgs<TOutput, TCreateInput, TUpdateInput>) => Promise<void>;
|
|
874
|
+
afterOperation?: (args: AfterOperationHookArgs<TOutput, TCreateInput, TUpdateInput>) => Promise<void>;
|
|
875
|
+
/**
|
|
876
|
+
* @deprecated Use 'validate' instead. This alias is provided for backwards compatibility.
|
|
877
|
+
*/
|
|
878
|
+
validateInput?: (args: ValidateHookArgs<TOutput, TCreateInput, TUpdateInput>) => Promise<void>;
|
|
678
879
|
};
|
|
880
|
+
/**
|
|
881
|
+
* Internal list configuration type (after normalization by list() function)
|
|
882
|
+
* Access control is always in object form internally.
|
|
883
|
+
* Use list() function which accepts both function shorthand and object form.
|
|
884
|
+
*/
|
|
679
885
|
export type ListConfig<TTypeInfo extends TypeInfo> = {
|
|
680
886
|
fields: FieldsWithTypeInfo<TTypeInfo>;
|
|
887
|
+
/**
|
|
888
|
+
* Access control configuration for this list (normalized object form).
|
|
889
|
+
* The list() function normalizes function shorthand to this object form.
|
|
890
|
+
*/
|
|
681
891
|
access?: {
|
|
682
892
|
operation?: OperationAccess<TTypeInfo['item']>;
|
|
683
893
|
};
|
|
@@ -686,6 +896,55 @@ export type ListConfig<TTypeInfo extends TypeInfo> = {
|
|
|
686
896
|
* MCP server configuration for this list
|
|
687
897
|
*/
|
|
688
898
|
mcp?: ListMcpConfig;
|
|
899
|
+
/**
|
|
900
|
+
* Restricts this list to a single record (singleton pattern)
|
|
901
|
+
* When true:
|
|
902
|
+
* - Prevents creating multiple records
|
|
903
|
+
* - Auto-creates the single record on first access (if autoCreate: true, which is the default)
|
|
904
|
+
* - Provides a get() method for easy access to the singleton
|
|
905
|
+
* - Blocks delete and findMany operations
|
|
906
|
+
* - Changes UI to show edit form instead of list view
|
|
907
|
+
*
|
|
908
|
+
* @example Simple boolean (auto-create enabled)
|
|
909
|
+
* ```typescript
|
|
910
|
+
* isSingleton: true
|
|
911
|
+
* ```
|
|
912
|
+
*
|
|
913
|
+
* @example With options
|
|
914
|
+
* ```typescript
|
|
915
|
+
* isSingleton: {
|
|
916
|
+
* autoCreate: false // Don't auto-create, must be created manually
|
|
917
|
+
* }
|
|
918
|
+
* ```
|
|
919
|
+
*/
|
|
920
|
+
isSingleton?: boolean | {
|
|
921
|
+
/**
|
|
922
|
+
* Auto-create the singleton record on first access using field defaults
|
|
923
|
+
* @default true
|
|
924
|
+
*/
|
|
925
|
+
autoCreate?: boolean;
|
|
926
|
+
};
|
|
927
|
+
};
|
|
928
|
+
/**
|
|
929
|
+
* Input type for the list() function
|
|
930
|
+
* Accepts both function shorthand and object form for access control.
|
|
931
|
+
*/
|
|
932
|
+
export type ListConfigInput<TTypeInfo extends TypeInfo> = Omit<ListConfig<TTypeInfo>, 'access'> & {
|
|
933
|
+
/**
|
|
934
|
+
* Access control configuration for this list.
|
|
935
|
+
* Supports both function shorthand and object form.
|
|
936
|
+
*
|
|
937
|
+
* @example Function shorthand (applies to all operations)
|
|
938
|
+
* ```typescript
|
|
939
|
+
* access: isAdmin
|
|
940
|
+
* ```
|
|
941
|
+
*
|
|
942
|
+
* @example Object form (per-operation)
|
|
943
|
+
* ```typescript
|
|
944
|
+
* access: { operation: { query: () => true, create: isAdmin } }
|
|
945
|
+
* ```
|
|
946
|
+
*/
|
|
947
|
+
access?: ListAccessControl<TTypeInfo['item']>;
|
|
689
948
|
};
|
|
690
949
|
/**
|
|
691
950
|
* Database configuration
|