@opensaas/stack-core 0.3.0 → 0.4.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 +150 -0
- package/dist/access/engine.d.ts +1 -1
- package/dist/access/engine.d.ts.map +1 -1
- package/dist/access/engine.js +38 -0
- package/dist/access/engine.js.map +1 -1
- package/dist/config/index.d.ts +5 -5
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/index.js +0 -1
- package/dist/config/index.js.map +1 -1
- package/dist/config/plugin-engine.d.ts.map +1 -1
- package/dist/config/plugin-engine.js +3 -0
- package/dist/config/plugin-engine.js.map +1 -1
- package/dist/config/types.d.ts +159 -73
- package/dist/config/types.d.ts.map +1 -1
- package/dist/context/index.d.ts.map +1 -1
- package/dist/context/index.js +19 -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 +88 -72
- package/dist/context/nested-operations.js.map +1 -1
- package/dist/fields/index.d.ts +65 -9
- package/dist/fields/index.d.ts.map +1 -1
- package/dist/fields/index.js +89 -8
- package/dist/fields/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/dist/mcp/handler.js +1 -0
- package/dist/mcp/handler.js.map +1 -1
- package/dist/validation/schema.d.ts.map +1 -1
- package/dist/validation/schema.js +4 -2
- package/dist/validation/schema.js.map +1 -1
- package/package.json +7 -7
- package/src/access/engine.ts +48 -3
- package/src/config/index.ts +8 -13
- package/src/config/plugin-engine.ts +6 -3
- package/src/config/types.ts +208 -109
- package/src/context/index.ts +14 -7
- package/src/context/nested-operations.ts +83 -71
- package/src/fields/index.ts +124 -20
- package/src/index.ts +9 -0
- package/src/mcp/handler.ts +2 -1
- package/src/validation/schema.ts +4 -2
- package/tests/field-types.test.ts +6 -5
- package/tests/sudo.test.ts +230 -1
- package/tsconfig.tsbuildinfo +1 -1
package/src/config/types.ts
CHANGED
|
@@ -18,12 +18,24 @@ export type FieldType =
|
|
|
18
18
|
* Field-level hooks for data transformation and side effects
|
|
19
19
|
* Allows field types to define custom behavior during operations
|
|
20
20
|
*
|
|
21
|
-
* @template
|
|
22
|
-
* @template
|
|
23
|
-
*
|
|
21
|
+
* @template TTypeInfo - List type information including item and input types
|
|
22
|
+
* @template TFieldKey - The specific field name (defaults to any field in the list)
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* ```typescript
|
|
26
|
+
* // For a 'title' field on Post list:
|
|
27
|
+
* FieldHooks<Lists.Post.TypeInfo, 'title'>
|
|
28
|
+
* // resolveOutput returns: string | undefined (field-specific)
|
|
29
|
+
*
|
|
30
|
+
* // Generic (for field builders):
|
|
31
|
+
* FieldHooks<TTypeInfo>
|
|
32
|
+
* // resolveOutput returns: union of all field types
|
|
33
|
+
* ```
|
|
24
34
|
*/
|
|
25
|
-
|
|
26
|
-
|
|
35
|
+
export type FieldHooks<
|
|
36
|
+
TTypeInfo extends TypeInfo,
|
|
37
|
+
TFieldKey extends FieldKeys<TTypeInfo['fields']> = FieldKeys<TTypeInfo['fields']>,
|
|
38
|
+
> = {
|
|
27
39
|
/**
|
|
28
40
|
* Transform field value before database write
|
|
29
41
|
* Called during create/update operations after list-level resolveInput but before validation
|
|
@@ -41,12 +53,15 @@ export type FieldHooks<TInput = any, TOutput = TInput, TItem = any> = {
|
|
|
41
53
|
*/
|
|
42
54
|
resolveInput?: (args: {
|
|
43
55
|
operation: 'create' | 'update'
|
|
44
|
-
inputValue:
|
|
45
|
-
item?:
|
|
56
|
+
inputValue: GetFieldValueType<TTypeInfo['fields'], TFieldKey> | undefined
|
|
57
|
+
item?: TTypeInfo['item']
|
|
46
58
|
listKey: string
|
|
47
|
-
fieldName:
|
|
59
|
+
fieldName: TFieldKey
|
|
48
60
|
context: import('../access/types.js').AccessContext
|
|
49
|
-
}) =>
|
|
61
|
+
}) =>
|
|
62
|
+
| Promise<GetFieldValueType<TTypeInfo['fields'], TFieldKey> | undefined>
|
|
63
|
+
| GetFieldValueType<TTypeInfo['fields'], TFieldKey>
|
|
64
|
+
| undefined
|
|
50
65
|
|
|
51
66
|
/**
|
|
52
67
|
* Perform side effects before database write
|
|
@@ -63,10 +78,10 @@ export type FieldHooks<TInput = any, TOutput = TInput, TItem = any> = {
|
|
|
63
78
|
*/
|
|
64
79
|
beforeOperation?: (args: {
|
|
65
80
|
operation: 'create' | 'update' | 'delete'
|
|
66
|
-
resolvedValue:
|
|
67
|
-
item?:
|
|
81
|
+
resolvedValue: GetFieldValueType<TTypeInfo['fields'], TFieldKey> | undefined
|
|
82
|
+
item?: TTypeInfo['item']
|
|
68
83
|
listKey: string
|
|
69
|
-
fieldName:
|
|
84
|
+
fieldName: TFieldKey
|
|
70
85
|
context: import('../access/types.js').AccessContext
|
|
71
86
|
}) => Promise<void> | void
|
|
72
87
|
|
|
@@ -83,25 +98,14 @@ export type FieldHooks<TInput = any, TOutput = TInput, TItem = any> = {
|
|
|
83
98
|
* }
|
|
84
99
|
* ```
|
|
85
100
|
*/
|
|
86
|
-
afterOperation?: (
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
context: import('../access/types.js').AccessContext
|
|
95
|
-
}
|
|
96
|
-
| {
|
|
97
|
-
operation: 'query'
|
|
98
|
-
value: TOutput | undefined
|
|
99
|
-
item: TItem
|
|
100
|
-
listKey: string
|
|
101
|
-
fieldName: string
|
|
102
|
-
context: import('../access/types.js').AccessContext
|
|
103
|
-
},
|
|
104
|
-
) => Promise<void> | void
|
|
101
|
+
afterOperation?: (args: {
|
|
102
|
+
operation: 'create' | 'update' | 'delete' | 'query'
|
|
103
|
+
value: GetFieldValueType<TTypeInfo['fields'], TFieldKey> | undefined
|
|
104
|
+
item: TTypeInfo['item']
|
|
105
|
+
listKey: string
|
|
106
|
+
fieldName: TFieldKey
|
|
107
|
+
context: import('../access/types.js').AccessContext
|
|
108
|
+
}) => Promise<void> | void
|
|
105
109
|
|
|
106
110
|
/**
|
|
107
111
|
* Transform field value after database read
|
|
@@ -120,45 +124,57 @@ export type FieldHooks<TInput = any, TOutput = TInput, TItem = any> = {
|
|
|
120
124
|
*/
|
|
121
125
|
resolveOutput?: (args: {
|
|
122
126
|
operation: 'query'
|
|
123
|
-
value:
|
|
124
|
-
item:
|
|
127
|
+
value: GetFieldValueType<TTypeInfo['fields'], TFieldKey>
|
|
128
|
+
item: TTypeInfo['item']
|
|
125
129
|
listKey: string
|
|
126
|
-
fieldName:
|
|
130
|
+
fieldName: TFieldKey
|
|
127
131
|
context: import('../access/types.js').AccessContext
|
|
128
|
-
}) =>
|
|
132
|
+
}) => GetFieldValueType<TTypeInfo['fields'], TFieldKey> | undefined
|
|
129
133
|
}
|
|
130
134
|
|
|
131
135
|
/**
|
|
132
|
-
* Configuration for
|
|
133
|
-
* Allows fields to transform their types in query results
|
|
136
|
+
* Configuration for Prisma result extensions
|
|
137
|
+
* Allows fields to transform their runtime values and types in query results
|
|
138
|
+
*
|
|
139
|
+
* Runtime transformation is delegated to the field's resolveOutput hook.
|
|
140
|
+
* This config only specifies the TypeScript output type for generated types.
|
|
134
141
|
*/
|
|
135
|
-
export type
|
|
142
|
+
export type ResultExtensionConfig = {
|
|
136
143
|
/**
|
|
137
|
-
* The TypeScript type to use in
|
|
138
|
-
* This is
|
|
144
|
+
* The TypeScript type to use in query result types
|
|
145
|
+
* This is a type expression like: "import('@opensaas/stack-core').HashedPassword"
|
|
146
|
+
*
|
|
147
|
+
* The actual runtime transformation is performed by the field's resolveOutput hook.
|
|
148
|
+
* The Prisma extension will automatically call the hook if it exists.
|
|
149
|
+
*
|
|
150
|
+
* @example "import('@opensaas/stack-core').HashedPassword"
|
|
151
|
+
* @example "import('./types').MyCustomType"
|
|
139
152
|
*/
|
|
140
|
-
|
|
153
|
+
outputType: string
|
|
141
154
|
/**
|
|
142
|
-
*
|
|
143
|
-
*
|
|
144
|
-
* - 'all': Patch everywhere the field appears (including inputs)
|
|
155
|
+
* @deprecated No longer used. Runtime transformations are handled by resolveOutput hooks.
|
|
156
|
+
* This field is kept for backwards compatibility but should not be used in new code.
|
|
145
157
|
*/
|
|
146
|
-
|
|
158
|
+
compute?: string
|
|
147
159
|
}
|
|
148
160
|
|
|
149
|
-
|
|
150
|
-
export type BaseFieldConfig<TInput = any, TOutput = TInput> = {
|
|
161
|
+
export type BaseFieldConfig<TTypeInfo extends TypeInfo> = {
|
|
151
162
|
type: string
|
|
152
163
|
access?: FieldAccess
|
|
153
164
|
defaultValue?: unknown
|
|
154
|
-
|
|
155
|
-
|
|
165
|
+
hooks?: FieldHooks<TTypeInfo>
|
|
166
|
+
/**
|
|
167
|
+
* Marks this field as virtual - not stored in database
|
|
168
|
+
* Virtual fields use resolveInput/resolveOutput hooks for computation
|
|
169
|
+
* They are excluded from Prisma schema and input types
|
|
170
|
+
* Only computed when explicitly selected/included in queries
|
|
171
|
+
*/
|
|
172
|
+
virtual?: boolean
|
|
156
173
|
/**
|
|
157
|
-
*
|
|
158
|
-
*
|
|
159
|
-
* the specified type in query results instead of the original type
|
|
174
|
+
* Prisma result extension configuration
|
|
175
|
+
* Transforms field values and types in query results using Prisma's native extension system
|
|
160
176
|
*/
|
|
161
|
-
|
|
177
|
+
resultExtension?: ResultExtensionConfig
|
|
162
178
|
ui?: {
|
|
163
179
|
/**
|
|
164
180
|
* Custom React component to render this field
|
|
@@ -235,7 +251,7 @@ export type BaseFieldConfig<TInput = any, TOutput = TInput> = {
|
|
|
235
251
|
}>
|
|
236
252
|
}
|
|
237
253
|
|
|
238
|
-
export type TextField = BaseFieldConfig<
|
|
254
|
+
export type TextField<TTypeInfo extends TypeInfo = TypeInfo> = BaseFieldConfig<TTypeInfo> & {
|
|
239
255
|
type: 'text'
|
|
240
256
|
validation?: {
|
|
241
257
|
isRequired?: boolean
|
|
@@ -250,7 +266,7 @@ export type TextField = BaseFieldConfig<string, string> & {
|
|
|
250
266
|
}
|
|
251
267
|
}
|
|
252
268
|
|
|
253
|
-
export type IntegerField = BaseFieldConfig<
|
|
269
|
+
export type IntegerField<TTypeInfo extends TypeInfo = TypeInfo> = BaseFieldConfig<TTypeInfo> & {
|
|
254
270
|
type: 'integer'
|
|
255
271
|
validation?: {
|
|
256
272
|
isRequired?: boolean
|
|
@@ -259,26 +275,23 @@ export type IntegerField = BaseFieldConfig<number, number> & {
|
|
|
259
275
|
}
|
|
260
276
|
}
|
|
261
277
|
|
|
262
|
-
export type CheckboxField = BaseFieldConfig<
|
|
278
|
+
export type CheckboxField<TTypeInfo extends TypeInfo = TypeInfo> = BaseFieldConfig<TTypeInfo> & {
|
|
263
279
|
type: 'checkbox'
|
|
264
280
|
}
|
|
265
281
|
|
|
266
|
-
export type TimestampField = BaseFieldConfig<
|
|
282
|
+
export type TimestampField<TTypeInfo extends TypeInfo = TypeInfo> = BaseFieldConfig<TTypeInfo> & {
|
|
267
283
|
type: 'timestamp'
|
|
268
284
|
defaultValue?: { kind: 'now' } | Date
|
|
269
285
|
}
|
|
270
286
|
|
|
271
|
-
export type PasswordField = BaseFieldConfig<
|
|
272
|
-
string,
|
|
273
|
-
import('../utils/password.js').HashedPassword
|
|
274
|
-
> & {
|
|
287
|
+
export type PasswordField<TTypeInfo extends TypeInfo = TypeInfo> = BaseFieldConfig<TTypeInfo> & {
|
|
275
288
|
type: 'password'
|
|
276
289
|
validation?: {
|
|
277
290
|
isRequired?: boolean
|
|
278
291
|
}
|
|
279
292
|
}
|
|
280
293
|
|
|
281
|
-
export type SelectField = BaseFieldConfig<
|
|
294
|
+
export type SelectField<TTypeInfo extends TypeInfo = TypeInfo> = BaseFieldConfig<TTypeInfo> & {
|
|
282
295
|
type: 'select'
|
|
283
296
|
options: Array<{ label: string; value: string }>
|
|
284
297
|
validation?: {
|
|
@@ -289,16 +302,17 @@ export type SelectField = BaseFieldConfig<string, string> & {
|
|
|
289
302
|
}
|
|
290
303
|
}
|
|
291
304
|
|
|
292
|
-
export type RelationshipField
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
305
|
+
export type RelationshipField<TTypeInfo extends TypeInfo = TypeInfo> =
|
|
306
|
+
BaseFieldConfig<TTypeInfo> & {
|
|
307
|
+
type: 'relationship'
|
|
308
|
+
ref: string // Format: 'ListName.fieldName'
|
|
309
|
+
many?: boolean
|
|
310
|
+
ui?: {
|
|
311
|
+
displayMode?: 'select' | 'cards'
|
|
312
|
+
}
|
|
298
313
|
}
|
|
299
|
-
}
|
|
300
314
|
|
|
301
|
-
export type JsonField = BaseFieldConfig<
|
|
315
|
+
export type JsonField<TTypeInfo extends TypeInfo = TypeInfo> = BaseFieldConfig<TTypeInfo> & {
|
|
302
316
|
type: 'json'
|
|
303
317
|
validation?: {
|
|
304
318
|
isRequired?: boolean
|
|
@@ -310,47 +324,130 @@ export type JsonField = BaseFieldConfig<unknown, unknown> & {
|
|
|
310
324
|
}
|
|
311
325
|
}
|
|
312
326
|
|
|
313
|
-
export type
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
327
|
+
export type VirtualField<TTypeInfo extends TypeInfo> = BaseFieldConfig<TTypeInfo> & {
|
|
328
|
+
type: 'virtual'
|
|
329
|
+
virtual: true
|
|
330
|
+
/**
|
|
331
|
+
* TypeScript type string for the virtual field output
|
|
332
|
+
* e.g., 'string', 'number', 'boolean', 'string[]', etc.
|
|
333
|
+
*/
|
|
334
|
+
outputType: string
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* Generic field configuration type
|
|
339
|
+
* Simplified to just BaseFieldConfig to reduce type complexity
|
|
340
|
+
* Specific field types (TextField, IntegerField, etc.) are used by field builders
|
|
341
|
+
* but at the config level we treat all fields uniformly
|
|
342
|
+
*/
|
|
343
|
+
export type FieldConfig = BaseFieldConfig<TypeInfo>
|
|
323
344
|
|
|
324
345
|
/**
|
|
325
346
|
* List configuration types
|
|
326
347
|
*/
|
|
327
348
|
|
|
328
349
|
/**
|
|
329
|
-
* Utility type to inject
|
|
330
|
-
* Extracts TInput and TOutput from BaseFieldConfig
|
|
350
|
+
* Utility type to inject TypeInfo into a single field config
|
|
351
|
+
* Extracts TInput and TOutput from BaseFieldConfig and reconstructs with new TypeInfo
|
|
331
352
|
*/
|
|
332
|
-
type
|
|
333
|
-
TField extends BaseFieldConfig<infer TInput, infer TOutput>
|
|
334
|
-
? Omit<TField, 'hooks'> & {
|
|
335
|
-
hooks?: FieldHooks<TInput, TOutput, TItem>
|
|
336
|
-
}
|
|
337
|
-
: TField
|
|
353
|
+
type WithTypeInfo<TTypeInfo extends TypeInfo> = BaseFieldConfig<TTypeInfo>
|
|
338
354
|
|
|
339
355
|
/**
|
|
340
|
-
* Utility type to transform all fields in a record to inject
|
|
341
|
-
* Maps over each field and applies
|
|
356
|
+
* Utility type to transform all fields in a record to inject TypeInfo
|
|
357
|
+
* Maps over each field and applies WithTypeInfo transformation
|
|
342
358
|
*/
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
[K in keyof TFields]: WithItemType<TFields[K], TItem>
|
|
359
|
+
export type FieldsWithTypeInfo<TTypeInfo extends TypeInfo> = {
|
|
360
|
+
[key: string]: WithTypeInfo<TTypeInfo>
|
|
346
361
|
}
|
|
347
362
|
|
|
363
|
+
/**
|
|
364
|
+
* Parse TypeScript type string to actual type
|
|
365
|
+
* Handles: 'string', 'number', 'boolean', 'Date', unions, string literals, imports
|
|
366
|
+
*
|
|
367
|
+
* @example
|
|
368
|
+
* ParseTypeString<'string'> => string
|
|
369
|
+
* ParseTypeString<'number'> => number
|
|
370
|
+
* ParseTypeString<"'draft' | 'published'"> => 'draft' | 'published'
|
|
371
|
+
* ParseTypeString<"import('@opensaas/stack-core').HashedPassword"> => any (fallback for imports)
|
|
372
|
+
*/
|
|
373
|
+
type ParseTypeString<T extends string> = T extends 'string'
|
|
374
|
+
? string
|
|
375
|
+
: T extends 'number'
|
|
376
|
+
? number
|
|
377
|
+
: T extends 'boolean'
|
|
378
|
+
? boolean
|
|
379
|
+
: T extends 'Date'
|
|
380
|
+
? Date
|
|
381
|
+
: T extends 'unknown'
|
|
382
|
+
? unknown
|
|
383
|
+
: T extends `'${infer U}'`
|
|
384
|
+
? U // String literal
|
|
385
|
+
: T extends `${infer U} | ${infer V}`
|
|
386
|
+
? ParseTypeString<U> | ParseTypeString<V> // Union
|
|
387
|
+
: T extends `import(${string}).${string}`
|
|
388
|
+
? any // eslint-disable-line @typescript-eslint/no-explicit-any -- Import types can't be resolved at compile time
|
|
389
|
+
: unknown // Fallback
|
|
390
|
+
|
|
391
|
+
/**
|
|
392
|
+
* Extract field value type from a field config
|
|
393
|
+
* Uses the field's getTypeScriptType() method result
|
|
394
|
+
* If resultExtension is present, uses its outputType instead
|
|
395
|
+
*
|
|
396
|
+
* @example
|
|
397
|
+
* ExtractFieldValueType<TextField> => string | null | undefined (if optional)
|
|
398
|
+
* ExtractFieldValueType<IntegerField> => number
|
|
399
|
+
* ExtractFieldValueType<PasswordField> => HashedPassword (from resultExtension)
|
|
400
|
+
*/
|
|
401
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Generic utility type needs to accept any BaseFieldConfig
|
|
402
|
+
type ExtractFieldValueType<TField extends BaseFieldConfig<any>> = TField extends {
|
|
403
|
+
resultExtension: { outputType: infer O }
|
|
404
|
+
}
|
|
405
|
+
? ParseTypeString<O & string>
|
|
406
|
+
: TField extends { getTypeScriptType(): { type: infer T; optional: infer Opt } }
|
|
407
|
+
? Opt extends true
|
|
408
|
+
? ParseTypeString<T & string> | null | undefined
|
|
409
|
+
: ParseTypeString<T & string>
|
|
410
|
+
: unknown
|
|
411
|
+
|
|
412
|
+
/**
|
|
413
|
+
* Extract field names as union of string literals
|
|
414
|
+
*
|
|
415
|
+
* @example
|
|
416
|
+
* FieldKeys<{ title: TextField, content: TextField }> => 'title' | 'content'
|
|
417
|
+
*/
|
|
418
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Generic utility type needs to accept any field record
|
|
419
|
+
export type FieldKeys<TFields extends Record<string, any>> = keyof TFields & string
|
|
420
|
+
|
|
421
|
+
/**
|
|
422
|
+
* Get field config for a specific field name
|
|
423
|
+
* Preserves the specific field type (TextField, PasswordField, etc.)
|
|
424
|
+
*
|
|
425
|
+
* @example
|
|
426
|
+
* GetFieldConfig<{ title: TextField }, 'title'> => TextField
|
|
427
|
+
*/
|
|
428
|
+
export type GetFieldConfig<
|
|
429
|
+
TFields extends Record<string, any>, // eslint-disable-line @typescript-eslint/no-explicit-any -- Generic utility type needs to accept any field record
|
|
430
|
+
TFieldKey extends FieldKeys<TFields>,
|
|
431
|
+
> = TFields[TFieldKey]
|
|
432
|
+
|
|
433
|
+
/**
|
|
434
|
+
* Get value type for a specific field
|
|
435
|
+
*
|
|
436
|
+
* @example
|
|
437
|
+
* GetFieldValueType<{ title: TextField }, 'title'> => string
|
|
438
|
+
*/
|
|
439
|
+
export type GetFieldValueType<
|
|
440
|
+
TFields extends Record<string, any>, // eslint-disable-line @typescript-eslint/no-explicit-any -- Generic utility type needs to accept any field record
|
|
441
|
+
TFieldKey extends FieldKeys<TFields>,
|
|
442
|
+
> = ExtractFieldValueType<GetFieldConfig<TFields, TFieldKey>>
|
|
443
|
+
|
|
348
444
|
/**
|
|
349
445
|
* TypeInfo interface for list type information
|
|
350
446
|
* Provides a structured way to pass all type information for a list
|
|
351
447
|
* Inspired by Keystone's TypeInfo pattern
|
|
352
448
|
*
|
|
353
449
|
* @template TKey - The list key/name (e.g., 'Post', 'User')
|
|
450
|
+
* @template TFields - The fields configuration for the list
|
|
354
451
|
* @template TItem - The output type (Prisma model type)
|
|
355
452
|
* @template TCreateInput - The Prisma create input type
|
|
356
453
|
* @template TUpdateInput - The Prisma update input type
|
|
@@ -359,6 +456,7 @@ export type FieldsWithItemType<TFields extends Record<string, FieldConfig>, TIte
|
|
|
359
456
|
* ```typescript
|
|
360
457
|
* type PostTypeInfo = {
|
|
361
458
|
* key: 'Post'
|
|
459
|
+
* fields: { title: TextField<...>, content: TextField<...> }
|
|
362
460
|
* item: Post
|
|
363
461
|
* inputs: {
|
|
364
462
|
* create: Prisma.PostCreateInput
|
|
@@ -369,15 +467,14 @@ export type FieldsWithItemType<TFields extends Record<string, FieldConfig>, TIte
|
|
|
369
467
|
*/
|
|
370
468
|
export interface TypeInfo<
|
|
371
469
|
TKey extends string = string,
|
|
372
|
-
|
|
373
|
-
TCreateInput = any, // eslint-disable-line @typescript-eslint/no-explicit-any
|
|
374
|
-
TUpdateInput = any, // eslint-disable-line @typescript-eslint/no-explicit-any
|
|
470
|
+
TFields extends Record<string, any> = Record<string, any>, // eslint-disable-line @typescript-eslint/no-explicit-any -- TypeInfo must accept any field record
|
|
375
471
|
> {
|
|
376
472
|
key: TKey
|
|
377
|
-
|
|
473
|
+
fields: TFields
|
|
474
|
+
item: any // eslint-disable-line @typescript-eslint/no-explicit-any -- Item type is provided by Prisma and varies per list
|
|
378
475
|
inputs: {
|
|
379
|
-
create:
|
|
380
|
-
update:
|
|
476
|
+
create: any // eslint-disable-line @typescript-eslint/no-explicit-any -- Prisma input types are generated and vary per list
|
|
477
|
+
update: any // eslint-disable-line @typescript-eslint/no-explicit-any -- Prisma input types are generated and vary per list
|
|
381
478
|
}
|
|
382
479
|
}
|
|
383
480
|
|
|
@@ -450,15 +547,14 @@ export type Hooks<
|
|
|
450
547
|
|
|
451
548
|
// Generic `any` default allows ListConfig to work with any list item type
|
|
452
549
|
// This is needed because the item type varies per list and is inferred from Prisma models
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
//
|
|
456
|
-
|
|
457
|
-
fields: FieldsWithItemType<Record<string, FieldConfig>, TOutput>
|
|
550
|
+
export type ListConfig<TTypeInfo extends TypeInfo> = {
|
|
551
|
+
// Field configs are automatically transformed to inject the full TypeInfo
|
|
552
|
+
// This enables proper typing in field hooks where item, create input, and update input are all typed
|
|
553
|
+
fields: FieldsWithTypeInfo<TTypeInfo>
|
|
458
554
|
access?: {
|
|
459
|
-
operation?: OperationAccess<
|
|
555
|
+
operation?: OperationAccess<TTypeInfo['item']>
|
|
460
556
|
}
|
|
461
|
-
hooks?: Hooks<
|
|
557
|
+
hooks?: Hooks<TTypeInfo['item'], TTypeInfo['inputs']['create'], TTypeInfo['inputs']['update']>
|
|
462
558
|
/**
|
|
463
559
|
* MCP server configuration for this list
|
|
464
560
|
*/
|
|
@@ -862,7 +958,8 @@ export type PluginContext = {
|
|
|
862
958
|
* Add a new list to the config
|
|
863
959
|
* Throws error if list already exists (unless merge strategy used)
|
|
864
960
|
*/
|
|
865
|
-
|
|
961
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Plugin API must accept any list config
|
|
962
|
+
addList: (name: string, listConfig: ListConfig<any>) => void
|
|
866
963
|
|
|
867
964
|
/**
|
|
868
965
|
* Extend an existing list with additional fields, hooks, or access control
|
|
@@ -885,7 +982,8 @@ export type PluginContext = {
|
|
|
885
982
|
* Register a field type globally
|
|
886
983
|
* Useful for third-party field packages
|
|
887
984
|
*/
|
|
888
|
-
|
|
985
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Plugin API must accept any field config builder
|
|
986
|
+
registerFieldType?: (type: string, builder: (options?: unknown) => BaseFieldConfig<any>) => void
|
|
889
987
|
|
|
890
988
|
/**
|
|
891
989
|
* Register a custom MCP tool
|
|
@@ -978,7 +1076,8 @@ export type Plugin = {
|
|
|
978
1076
|
*/
|
|
979
1077
|
export interface OpenSaasConfig {
|
|
980
1078
|
db: DatabaseConfig
|
|
981
|
-
|
|
1079
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Config must accept any list configuration
|
|
1080
|
+
lists: Record<string, ListConfig<any>>
|
|
982
1081
|
session?: SessionConfig
|
|
983
1082
|
ui?: UIConfig
|
|
984
1083
|
/**
|
package/src/context/index.ts
CHANGED
|
@@ -135,7 +135,8 @@ export type ServerActionProps =
|
|
|
135
135
|
/**
|
|
136
136
|
* Parse Prisma error and convert to user-friendly DatabaseError
|
|
137
137
|
*/
|
|
138
|
-
|
|
138
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- ListConfig must accept any TypeInfo
|
|
139
|
+
function parsePrismaError(error: unknown, listConfig: ListConfig<any>): Error {
|
|
139
140
|
// Check if it's a Prisma error
|
|
140
141
|
if (
|
|
141
142
|
error &&
|
|
@@ -406,7 +407,8 @@ export function getContext<
|
|
|
406
407
|
*/
|
|
407
408
|
function createFindUnique<TPrisma extends PrismaClientLike>(
|
|
408
409
|
listName: string,
|
|
409
|
-
|
|
410
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- ListConfig must accept any TypeInfo
|
|
411
|
+
listConfig: ListConfig<any>,
|
|
410
412
|
prisma: TPrisma,
|
|
411
413
|
context: AccessContext<TPrisma>,
|
|
412
414
|
config: OpenSaasConfig,
|
|
@@ -492,7 +494,8 @@ function createFindUnique<TPrisma extends PrismaClientLike>(
|
|
|
492
494
|
*/
|
|
493
495
|
function createFindMany<TPrisma extends PrismaClientLike>(
|
|
494
496
|
listName: string,
|
|
495
|
-
|
|
497
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- ListConfig must accept any TypeInfo
|
|
498
|
+
listConfig: ListConfig<any>,
|
|
496
499
|
prisma: TPrisma,
|
|
497
500
|
context: AccessContext<TPrisma>,
|
|
498
501
|
config: OpenSaasConfig,
|
|
@@ -589,7 +592,8 @@ function createFindMany<TPrisma extends PrismaClientLike>(
|
|
|
589
592
|
*/
|
|
590
593
|
function createCreate<TPrisma extends PrismaClientLike>(
|
|
591
594
|
listName: string,
|
|
592
|
-
|
|
595
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- ListConfig must accept any TypeInfo
|
|
596
|
+
listConfig: ListConfig<any>,
|
|
593
597
|
prisma: TPrisma,
|
|
594
598
|
context: AccessContext<TPrisma>,
|
|
595
599
|
config: OpenSaasConfig,
|
|
@@ -709,7 +713,8 @@ function createCreate<TPrisma extends PrismaClientLike>(
|
|
|
709
713
|
*/
|
|
710
714
|
function createUpdate<TPrisma extends PrismaClientLike>(
|
|
711
715
|
listName: string,
|
|
712
|
-
|
|
716
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- ListConfig must accept any TypeInfo
|
|
717
|
+
listConfig: ListConfig<any>,
|
|
713
718
|
prisma: TPrisma,
|
|
714
719
|
context: AccessContext<TPrisma>,
|
|
715
720
|
config: OpenSaasConfig,
|
|
@@ -863,7 +868,8 @@ function createUpdate<TPrisma extends PrismaClientLike>(
|
|
|
863
868
|
*/
|
|
864
869
|
function createDelete<TPrisma extends PrismaClientLike>(
|
|
865
870
|
listName: string,
|
|
866
|
-
|
|
871
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- ListConfig must accept any TypeInfo
|
|
872
|
+
listConfig: ListConfig<any>,
|
|
867
873
|
prisma: TPrisma,
|
|
868
874
|
context: AccessContext<TPrisma>,
|
|
869
875
|
) {
|
|
@@ -946,7 +952,8 @@ function createDelete<TPrisma extends PrismaClientLike>(
|
|
|
946
952
|
*/
|
|
947
953
|
function createCount<TPrisma extends PrismaClientLike>(
|
|
948
954
|
listName: string,
|
|
949
|
-
|
|
955
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- ListConfig must accept any TypeInfo
|
|
956
|
+
listConfig: ListConfig<any>,
|
|
950
957
|
prisma: TPrisma,
|
|
951
958
|
context: AccessContext<TPrisma>,
|
|
952
959
|
) {
|