@opensaas/stack-core 0.1.6 → 0.3.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 +208 -0
- package/CLAUDE.md +46 -1
- package/dist/access/engine.d.ts +15 -8
- package/dist/access/engine.d.ts.map +1 -1
- package/dist/access/engine.js +23 -2
- package/dist/access/engine.js.map +1 -1
- package/dist/access/engine.test.d.ts +2 -0
- package/dist/access/engine.test.d.ts.map +1 -0
- package/dist/access/engine.test.js +125 -0
- package/dist/access/engine.test.js.map +1 -0
- package/dist/access/types.d.ts +40 -9
- package/dist/access/types.d.ts.map +1 -1
- package/dist/config/index.d.ts +38 -18
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/index.js +34 -14
- package/dist/config/index.js.map +1 -1
- package/dist/config/plugin-engine.d.ts.map +1 -1
- package/dist/config/plugin-engine.js +6 -0
- package/dist/config/plugin-engine.js.map +1 -1
- package/dist/config/types.d.ts +128 -21
- package/dist/config/types.d.ts.map +1 -1
- package/dist/context/index.d.ts +14 -2
- package/dist/context/index.d.ts.map +1 -1
- package/dist/context/index.js +243 -100
- package/dist/context/index.js.map +1 -1
- package/dist/fields/index.d.ts.map +1 -1
- package/dist/fields/index.js +9 -8
- package/dist/fields/index.js.map +1 -1
- package/dist/hooks/index.d.ts +28 -12
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/index.js +16 -0
- package/dist/hooks/index.js.map +1 -1
- package/package.json +3 -4
- package/src/access/engine.test.ts +145 -0
- package/src/access/engine.ts +35 -11
- package/src/access/types.ts +39 -8
- package/src/config/index.ts +46 -19
- package/src/config/plugin-engine.ts +7 -0
- package/src/config/types.ts +149 -18
- package/src/context/index.ts +298 -110
- package/src/fields/index.ts +8 -7
- package/src/hooks/index.ts +63 -20
- package/tests/context.test.ts +38 -6
- package/tests/field-types.test.ts +728 -0
- package/tests/password-type-distribution.test.ts +0 -1
- package/tests/password-types.test.ts +0 -1
- package/tests/plugin-engine.test.ts +1102 -0
- package/tests/sudo.test.ts +405 -0
- package/tsconfig.tsbuildinfo +1 -1
package/src/config/types.ts
CHANGED
|
@@ -345,6 +345,42 @@ export type FieldsWithItemType<TFields extends Record<string, FieldConfig>, TIte
|
|
|
345
345
|
[K in keyof TFields]: WithItemType<TFields[K], TItem>
|
|
346
346
|
}
|
|
347
347
|
|
|
348
|
+
/**
|
|
349
|
+
* TypeInfo interface for list type information
|
|
350
|
+
* Provides a structured way to pass all type information for a list
|
|
351
|
+
* Inspired by Keystone's TypeInfo pattern
|
|
352
|
+
*
|
|
353
|
+
* @template TKey - The list key/name (e.g., 'Post', 'User')
|
|
354
|
+
* @template TItem - The output type (Prisma model type)
|
|
355
|
+
* @template TCreateInput - The Prisma create input type
|
|
356
|
+
* @template TUpdateInput - The Prisma update input type
|
|
357
|
+
*
|
|
358
|
+
* @example
|
|
359
|
+
* ```typescript
|
|
360
|
+
* type PostTypeInfo = {
|
|
361
|
+
* key: 'Post'
|
|
362
|
+
* item: Post
|
|
363
|
+
* inputs: {
|
|
364
|
+
* create: Prisma.PostCreateInput
|
|
365
|
+
* update: Prisma.PostUpdateInput
|
|
366
|
+
* }
|
|
367
|
+
* }
|
|
368
|
+
* ```
|
|
369
|
+
*/
|
|
370
|
+
export interface TypeInfo<
|
|
371
|
+
TKey extends string = string,
|
|
372
|
+
TItem = any, // eslint-disable-line @typescript-eslint/no-explicit-any
|
|
373
|
+
TCreateInput = any, // eslint-disable-line @typescript-eslint/no-explicit-any
|
|
374
|
+
TUpdateInput = any, // eslint-disable-line @typescript-eslint/no-explicit-any
|
|
375
|
+
> {
|
|
376
|
+
key: TKey
|
|
377
|
+
item: TItem
|
|
378
|
+
inputs: {
|
|
379
|
+
create: TCreateInput
|
|
380
|
+
update: TUpdateInput
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
348
384
|
// Generic `any` default allows OperationAccess to work with any list item type
|
|
349
385
|
// This is needed because the item type varies per list and is inferred from Prisma models
|
|
350
386
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
@@ -355,36 +391,74 @@ export type OperationAccess<T = any> = {
|
|
|
355
391
|
delete?: AccessControl<T>
|
|
356
392
|
}
|
|
357
393
|
|
|
358
|
-
|
|
394
|
+
/**
|
|
395
|
+
* Hook arguments for resolveInput hook
|
|
396
|
+
* Uses discriminated union to provide proper types based on operation
|
|
397
|
+
* - create: resolvedData is CreateInput, item is undefined
|
|
398
|
+
* - update: resolvedData is UpdateInput, item is the existing record
|
|
399
|
+
*/
|
|
400
|
+
export type ResolveInputHookArgs<
|
|
401
|
+
TOutput = Record<string, unknown>,
|
|
402
|
+
TCreateInput = Record<string, unknown>,
|
|
403
|
+
TUpdateInput = Record<string, unknown>,
|
|
404
|
+
> =
|
|
405
|
+
| {
|
|
406
|
+
operation: 'create'
|
|
407
|
+
resolvedData: TCreateInput
|
|
408
|
+
item: undefined
|
|
409
|
+
context: import('../access/types.js').AccessContext
|
|
410
|
+
}
|
|
411
|
+
| {
|
|
412
|
+
operation: 'update'
|
|
413
|
+
resolvedData: TUpdateInput
|
|
414
|
+
item: TOutput
|
|
415
|
+
context: import('../access/types.js').AccessContext
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
/**
|
|
419
|
+
* Hook arguments for other hooks (validateInput, beforeOperation, afterOperation)
|
|
420
|
+
* These hooks receive the same structure regardless of operation
|
|
421
|
+
*/
|
|
422
|
+
export type HookArgs<
|
|
423
|
+
TOutput = Record<string, unknown>,
|
|
424
|
+
TCreateInput = Record<string, unknown>,
|
|
425
|
+
TUpdateInput = Record<string, unknown>,
|
|
426
|
+
> = {
|
|
359
427
|
operation: 'create' | 'update' | 'delete'
|
|
360
|
-
resolvedData?:
|
|
361
|
-
item?:
|
|
428
|
+
resolvedData?: TCreateInput | TUpdateInput
|
|
429
|
+
item?: TOutput
|
|
362
430
|
context: import('../access/types.js').AccessContext
|
|
363
431
|
}
|
|
364
432
|
|
|
365
|
-
export type Hooks<
|
|
366
|
-
|
|
433
|
+
export type Hooks<
|
|
434
|
+
TOutput = Record<string, unknown>,
|
|
435
|
+
TCreateInput = Record<string, unknown>,
|
|
436
|
+
TUpdateInput = Record<string, unknown>,
|
|
437
|
+
> = {
|
|
438
|
+
resolveInput?: (
|
|
439
|
+
args: ResolveInputHookArgs<TOutput, TCreateInput, TUpdateInput>,
|
|
440
|
+
) => Promise<TCreateInput | TUpdateInput>
|
|
367
441
|
validateInput?: (
|
|
368
|
-
args: HookArgs<
|
|
442
|
+
args: HookArgs<TOutput, TCreateInput, TUpdateInput> & {
|
|
369
443
|
operation: 'create' | 'update'
|
|
370
444
|
addValidationError: (msg: string) => void
|
|
371
445
|
},
|
|
372
446
|
) => Promise<void>
|
|
373
|
-
beforeOperation?: (args: HookArgs<
|
|
374
|
-
afterOperation?: (args: HookArgs<
|
|
447
|
+
beforeOperation?: (args: HookArgs<TOutput, TCreateInput, TUpdateInput>) => Promise<void>
|
|
448
|
+
afterOperation?: (args: HookArgs<TOutput, TCreateInput, TUpdateInput>) => Promise<void>
|
|
375
449
|
}
|
|
376
450
|
|
|
377
451
|
// Generic `any` default allows ListConfig to work with any list item type
|
|
378
452
|
// This is needed because the item type varies per list and is inferred from Prisma models
|
|
379
453
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
380
|
-
export type ListConfig<
|
|
454
|
+
export type ListConfig<TOutput = any, TCreateInput = any, TUpdateInput = any> = {
|
|
381
455
|
// Field configs are automatically transformed to inject the item type T
|
|
382
456
|
// This enables proper typing in field hooks where item: TItem
|
|
383
|
-
fields: FieldsWithItemType<Record<string, FieldConfig>,
|
|
457
|
+
fields: FieldsWithItemType<Record<string, FieldConfig>, TOutput>
|
|
384
458
|
access?: {
|
|
385
|
-
operation?: OperationAccess<
|
|
459
|
+
operation?: OperationAccess<TOutput>
|
|
386
460
|
}
|
|
387
|
-
hooks?: Hooks<
|
|
461
|
+
hooks?: Hooks<TOutput, TCreateInput, TUpdateInput>
|
|
388
462
|
/**
|
|
389
463
|
* MCP server configuration for this list
|
|
390
464
|
*/
|
|
@@ -396,12 +470,37 @@ export type ListConfig<T = any> = {
|
|
|
396
470
|
*/
|
|
397
471
|
export type DatabaseConfig = {
|
|
398
472
|
provider: 'postgresql' | 'mysql' | 'sqlite'
|
|
399
|
-
url: string
|
|
400
473
|
/**
|
|
401
|
-
*
|
|
402
|
-
*
|
|
474
|
+
* Factory function to create a Prisma client instance with a database adapter
|
|
475
|
+
* Required in Prisma 7+ - receives the PrismaClient class and returns a configured instance
|
|
403
476
|
*
|
|
404
|
-
*
|
|
477
|
+
* The connection URL is passed directly to the adapter, not to the config.
|
|
478
|
+
*
|
|
479
|
+
* @example SQLite with better-sqlite3
|
|
480
|
+
* ```typescript
|
|
481
|
+
* import { PrismaBetterSQLite3 } from '@prisma/adapter-better-sqlite3'
|
|
482
|
+
* import Database from 'better-sqlite3'
|
|
483
|
+
*
|
|
484
|
+
* prismaClientConstructor: (PrismaClient) => {
|
|
485
|
+
* const db = new Database(process.env.DATABASE_URL || './dev.db')
|
|
486
|
+
* const adapter = new PrismaBetterSQLite3(db)
|
|
487
|
+
* return new PrismaClient({ adapter })
|
|
488
|
+
* }
|
|
489
|
+
* ```
|
|
490
|
+
*
|
|
491
|
+
* @example PostgreSQL with pg
|
|
492
|
+
* ```typescript
|
|
493
|
+
* import { PrismaPg } from '@prisma/adapter-pg'
|
|
494
|
+
* import pg from 'pg'
|
|
495
|
+
*
|
|
496
|
+
* prismaClientConstructor: (PrismaClient) => {
|
|
497
|
+
* const pool = new pg.Pool({ connectionString: process.env.DATABASE_URL })
|
|
498
|
+
* const adapter = new PrismaPg(pool)
|
|
499
|
+
* return new PrismaClient({ adapter })
|
|
500
|
+
* }
|
|
501
|
+
* ```
|
|
502
|
+
*
|
|
503
|
+
* @example Neon serverless (PostgreSQL)
|
|
405
504
|
* ```typescript
|
|
406
505
|
* import { PrismaNeon } from '@prisma/adapter-neon'
|
|
407
506
|
* import { neonConfig } from '@neondatabase/serverless'
|
|
@@ -419,7 +518,7 @@ export type DatabaseConfig = {
|
|
|
419
518
|
// Uses `any` for maximum flexibility with Prisma client constructors and adapters
|
|
420
519
|
// Different database adapters have varying type signatures that are hard to unify
|
|
421
520
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
422
|
-
prismaClientConstructor
|
|
521
|
+
prismaClientConstructor: (PrismaClientClass: any) => any
|
|
423
522
|
}
|
|
424
523
|
|
|
425
524
|
/**
|
|
@@ -846,12 +945,38 @@ export type Plugin = {
|
|
|
846
945
|
* Return value is stored in context.plugins[pluginName]
|
|
847
946
|
*/
|
|
848
947
|
runtime?: (context: import('../access/types.js').AccessContext) => unknown
|
|
948
|
+
|
|
949
|
+
/**
|
|
950
|
+
* Optional: Type metadata for runtime services
|
|
951
|
+
* Enables type-safe code generation for context.plugins
|
|
952
|
+
*
|
|
953
|
+
* @example
|
|
954
|
+
* ```typescript
|
|
955
|
+
* {
|
|
956
|
+
* import: "import type { AuthRuntimeServices } from '@opensaas/stack-auth/runtime'",
|
|
957
|
+
* typeName: "AuthRuntimeServices"
|
|
958
|
+
* }
|
|
959
|
+
* ```
|
|
960
|
+
*/
|
|
961
|
+
runtimeServiceTypes?: {
|
|
962
|
+
/**
|
|
963
|
+
* Import statement to include in generated types file
|
|
964
|
+
* Must be a complete import statement with 'import type' and quotes
|
|
965
|
+
*/
|
|
966
|
+
import: string
|
|
967
|
+
/**
|
|
968
|
+
* TypeScript type name to use in PluginServices interface
|
|
969
|
+
* Should match the exported type from the import
|
|
970
|
+
*/
|
|
971
|
+
typeName: string
|
|
972
|
+
}
|
|
849
973
|
}
|
|
850
974
|
|
|
851
975
|
/**
|
|
852
976
|
* Main configuration type
|
|
977
|
+
* Using interface instead of type to allow module augmentation
|
|
853
978
|
*/
|
|
854
|
-
export
|
|
979
|
+
export interface OpenSaasConfig {
|
|
855
980
|
db: DatabaseConfig
|
|
856
981
|
lists: Record<string, ListConfig>
|
|
857
982
|
session?: SessionConfig
|
|
@@ -881,4 +1006,10 @@ export type OpenSaasConfig = {
|
|
|
881
1006
|
* @internal
|
|
882
1007
|
*/
|
|
883
1008
|
_pluginData?: Record<string, unknown>
|
|
1009
|
+
/**
|
|
1010
|
+
* Sorted plugin instances (stored after plugin execution)
|
|
1011
|
+
* Used at runtime to call plugin.runtime() functions
|
|
1012
|
+
* @internal
|
|
1013
|
+
*/
|
|
1014
|
+
_plugins?: Plugin[]
|
|
884
1015
|
}
|