@ditojs/server 2.93.0 → 2.94.1
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/package.json +10 -10
- package/src/app/Application.js +13 -4
- package/src/cli/db/createMigration.js +0 -0
- package/src/cli/db/seed.js +0 -0
- package/src/mixins/AssetMixin.js +4 -2
- package/src/models/Model.js +65 -22
- package/src/storage/DiskStorage.js +20 -17
- package/src/storage/Storage.js +62 -3
- package/src/storage/Storage.test.js +58 -0
- package/src/utils/fs.js +16 -0
- package/types/index.d.ts +703 -193
- package/types/tests/application.test-d.ts +0 -26
- package/types/tests/controller.test-d.ts +0 -113
- package/types/tests/errors.test-d.ts +0 -53
- package/types/tests/fixtures.ts +0 -19
- package/types/tests/model.test-d.ts +0 -193
- package/types/tests/query-builder.test-d.ts +0 -106
- package/types/tests/relation.test-d.ts +0 -83
- package/types/tests/storage.test-d.ts +0 -113
package/types/index.d.ts
CHANGED
|
@@ -50,15 +50,31 @@ export type CompiledParametersValidator = {
|
|
|
50
50
|
hasModelRefs: boolean
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
-
|
|
53
|
+
/**
|
|
54
|
+
* Application configuration passed to the Application constructor.
|
|
55
|
+
*
|
|
56
|
+
* Extend via module augmentation for project-specific config:
|
|
57
|
+
*
|
|
58
|
+
* ```ts
|
|
59
|
+
* declare module '@ditojs/server' {
|
|
60
|
+
* interface ApplicationConfig {
|
|
61
|
+
* externalUrls?: {
|
|
62
|
+
* frontend: string
|
|
63
|
+
* backend: string
|
|
64
|
+
* }
|
|
65
|
+
* }
|
|
66
|
+
* }
|
|
67
|
+
* ```
|
|
68
|
+
*/
|
|
69
|
+
export interface ApplicationConfig {
|
|
54
70
|
/** @defaultValue `production` */
|
|
55
|
-
env?: 'production' | 'development'
|
|
71
|
+
env?: LiteralUnion<'production' | 'development'>
|
|
56
72
|
/** The server configuration */
|
|
57
73
|
server?: {
|
|
58
74
|
/** The ip address or hostname used to serve requests */
|
|
59
75
|
host?: string
|
|
60
76
|
/** The port to listen on for connections */
|
|
61
|
-
port?: string
|
|
77
|
+
port?: string | number
|
|
62
78
|
}
|
|
63
79
|
/** Logging options */
|
|
64
80
|
log?: {
|
|
@@ -184,7 +200,9 @@ export type ApplicationConfig = {
|
|
|
184
200
|
keys?: Koa['keys']
|
|
185
201
|
}
|
|
186
202
|
admin?: AdminConfig
|
|
187
|
-
knex?: Knex.Config<any> & {
|
|
203
|
+
knex?: Omit<Knex.Config<any>, 'connection'> & {
|
|
204
|
+
/** Knex connection config, or a custom adapter object (e.g. PGlite). */
|
|
205
|
+
connection?: Knex.Config<any>['connection'] | Record<string, unknown>
|
|
188
206
|
/** @defaultValue `false` */
|
|
189
207
|
normalizeDbNames?: boolean | Parameters<KnexSnakeCaseMappersFactory>
|
|
190
208
|
// See https://github.com/brianc/node-pg-types/blob/master/index.d.ts#L67
|
|
@@ -268,6 +286,13 @@ export interface DiskStorageConfig extends CommonStorageConfig {
|
|
|
268
286
|
* The path to the directory where assets are stored on.
|
|
269
287
|
*/
|
|
270
288
|
path: string
|
|
289
|
+
/**
|
|
290
|
+
* Whether to store files in a two-level nested folder structure using the
|
|
291
|
+
* first two characters of the file key.
|
|
292
|
+
*
|
|
293
|
+
* @defaultValue true
|
|
294
|
+
*/
|
|
295
|
+
nestedFolders?: boolean
|
|
271
296
|
}
|
|
272
297
|
|
|
273
298
|
export type StorageConfig = S3StorageConfig | DiskStorageConfig
|
|
@@ -384,14 +409,20 @@ export class Application<$Models extends Models = Models> {
|
|
|
384
409
|
add(
|
|
385
410
|
method: string,
|
|
386
411
|
path: string,
|
|
387
|
-
handler:
|
|
412
|
+
handler: (
|
|
413
|
+
ctx: KoaContext,
|
|
414
|
+
next: () => Promise<void>
|
|
415
|
+
) => OrPromiseOf<void>
|
|
388
416
|
): this
|
|
389
417
|
find(
|
|
390
418
|
method: string,
|
|
391
419
|
path: string
|
|
392
420
|
): {
|
|
393
421
|
status: number
|
|
394
|
-
handler?:
|
|
422
|
+
handler?: (
|
|
423
|
+
ctx: KoaContext,
|
|
424
|
+
next: () => Promise<void>
|
|
425
|
+
) => OrPromiseOf<void>
|
|
395
426
|
params?: Record<string, string>
|
|
396
427
|
allowed: string[]
|
|
397
428
|
}
|
|
@@ -429,9 +460,9 @@ export class Application<$Models extends Models = Models> {
|
|
|
429
460
|
/** The schema validator instance. */
|
|
430
461
|
validator: Validator
|
|
431
462
|
/** Registered storage instances by name. */
|
|
432
|
-
storages:
|
|
463
|
+
storages: ResolvedStorages
|
|
433
464
|
/** Registered service instances by name. */
|
|
434
|
-
services:
|
|
465
|
+
services: ResolvedServices
|
|
435
466
|
/** Registered controller instances by name. */
|
|
436
467
|
controllers: Record<string, Controller>
|
|
437
468
|
models: $Models
|
|
@@ -461,7 +492,11 @@ export class Application<$Models extends Models = Models> {
|
|
|
461
492
|
addStorages(storages: StorageConfigs): void
|
|
462
493
|
setupStorages(): Promise<void>
|
|
463
494
|
/** Returns a storage by name, or `null` if not found. */
|
|
464
|
-
getStorage
|
|
495
|
+
getStorage<K extends string>(
|
|
496
|
+
name: K
|
|
497
|
+
): K extends keyof ResolvedStorages
|
|
498
|
+
? ResolvedStorages[K]
|
|
499
|
+
: Storage | null
|
|
465
500
|
|
|
466
501
|
addService(
|
|
467
502
|
service: Service | Class<Service>,
|
|
@@ -471,7 +506,12 @@ export class Application<$Models extends Models = Models> {
|
|
|
471
506
|
addServices(services: Services): void
|
|
472
507
|
setupServices(): Promise<void>
|
|
473
508
|
/** Returns a service by name. */
|
|
474
|
-
getService
|
|
509
|
+
getService<K extends string>(
|
|
510
|
+
name: K
|
|
511
|
+
): K extends keyof ResolvedServices
|
|
512
|
+
? ResolvedServices[K]
|
|
513
|
+
: Service | null
|
|
514
|
+
|
|
475
515
|
/** Finds a service matching the given predicate. */
|
|
476
516
|
findService(
|
|
477
517
|
callback: (service: Service) => boolean
|
|
@@ -620,7 +660,11 @@ export class Application<$Models extends Models = Models> {
|
|
|
620
660
|
method: HTTPMethod,
|
|
621
661
|
path: string,
|
|
622
662
|
transacted: boolean,
|
|
623
|
-
middlewares: OrArrayOf<(
|
|
663
|
+
middlewares: OrArrayOf< (
|
|
664
|
+
ctx: KoaContext,
|
|
665
|
+
next: () => Promise<void>
|
|
666
|
+
) => OrPromiseOf<void>
|
|
667
|
+
>,
|
|
624
668
|
controller?: Controller | null,
|
|
625
669
|
action?: any
|
|
626
670
|
): void
|
|
@@ -658,6 +702,7 @@ export interface Application
|
|
|
658
702
|
|
|
659
703
|
export type SchemaType = LiteralUnion<
|
|
660
704
|
| 'string'
|
|
705
|
+
| 'text'
|
|
661
706
|
| 'number'
|
|
662
707
|
| 'integer'
|
|
663
708
|
| 'boolean'
|
|
@@ -669,27 +714,105 @@ export type SchemaType = LiteralUnion<
|
|
|
669
714
|
| 'timestamp'
|
|
670
715
|
>
|
|
671
716
|
|
|
672
|
-
|
|
717
|
+
type RelatedModel<T> = T extends (infer U)[]
|
|
718
|
+
? U extends Model
|
|
719
|
+
? U
|
|
720
|
+
: never
|
|
721
|
+
: T extends Model
|
|
722
|
+
? T
|
|
723
|
+
: never
|
|
724
|
+
|
|
725
|
+
type ModelName<T extends Model, $Models, R = ResolveModels<$Models>> = {
|
|
726
|
+
[K in keyof R]: R[K] extends Class<T> ? K : never
|
|
727
|
+
}[keyof R]
|
|
728
|
+
|
|
729
|
+
type ResolveModels<$Models> = $Models extends Promise<infer M> ? M : $Models
|
|
730
|
+
|
|
731
|
+
type OnlyModels<$Models, R = ResolveModels<$Models>> = {
|
|
732
|
+
[K in keyof R as R[K] extends ModelClass ? K : never]: R[K]
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
type HasModels<$Models> = keyof OnlyModels<$Models> extends never ? false : true
|
|
736
|
+
|
|
737
|
+
type ModelRef<T extends Model, $Models> =
|
|
738
|
+
HasModels<$Models> extends true
|
|
739
|
+
? `${ModelName<T, $Models> & string}.${keyof SerializedModel<T> & string}`
|
|
740
|
+
: string
|
|
741
|
+
|
|
742
|
+
type AnyModelRef<$Models> =
|
|
743
|
+
HasModels<$Models> extends true
|
|
744
|
+
? `${string}.${string}`
|
|
745
|
+
: string
|
|
746
|
+
|
|
747
|
+
/**
|
|
748
|
+
* The supported relation types for model associations.
|
|
749
|
+
*
|
|
750
|
+
* - `'belongsTo'` — single model, foreign key on this table
|
|
751
|
+
* - `'hasOne'` — single model, foreign key on the other table
|
|
752
|
+
* - `'hasMany'` — array of models, foreign key on the other table
|
|
753
|
+
* - `'manyToMany'` — array of models, via a join table
|
|
754
|
+
* - `'hasOneThrough'` — single model, via a join table
|
|
755
|
+
*
|
|
756
|
+
* @see {@link https://github.com/ditojs/dito/blob/main/docs/model-relations.md#relation-types|Relation Types}
|
|
757
|
+
*/
|
|
758
|
+
export type RelationType =
|
|
759
|
+
| 'belongsTo'
|
|
760
|
+
| 'hasMany'
|
|
761
|
+
| 'hasOne'
|
|
762
|
+
| 'manyToMany'
|
|
763
|
+
| 'hasOneThrough'
|
|
764
|
+
|
|
765
|
+
type RelationTypeForProperty<T> = T extends Model[]
|
|
766
|
+
? 'hasMany' | 'manyToMany'
|
|
767
|
+
: T extends Model
|
|
768
|
+
? 'belongsTo' | 'hasOne' | 'hasOneThrough'
|
|
769
|
+
: RelationType
|
|
770
|
+
|
|
771
|
+
/**
|
|
772
|
+
* A model relation definition describing how two models are associated.
|
|
773
|
+
*
|
|
774
|
+
* When used with type arguments, provides type-safe `from`/`to` strings
|
|
775
|
+
* and narrows `relation` based on the property type (array vs single
|
|
776
|
+
* model).
|
|
777
|
+
*
|
|
778
|
+
* @example
|
|
779
|
+
* ```ts
|
|
780
|
+
* // Untyped — accepts any relation definition:
|
|
781
|
+
* const rel: ModelRelation = {
|
|
782
|
+
* relation: 'hasMany',
|
|
783
|
+
* from: 'Tag.id',
|
|
784
|
+
* to: 'Task.tagId'
|
|
785
|
+
* }
|
|
786
|
+
* ```
|
|
787
|
+
*
|
|
788
|
+
* @see {@link https://github.com/ditojs/dito/blob/main/docs/model-relations.md|Model Relations}
|
|
789
|
+
*/
|
|
790
|
+
export interface ModelRelation<
|
|
791
|
+
$Owner extends Model = Model,
|
|
792
|
+
$Related extends Model = Model,
|
|
793
|
+
$Models = unknown,
|
|
794
|
+
$PropertyType = unknown
|
|
795
|
+
> {
|
|
673
796
|
/**
|
|
674
797
|
* The type of relation
|
|
675
798
|
*
|
|
676
799
|
* @see {@link https://github.com/ditojs/dito/blob/main/docs/model-relations.md#relation-types|Relation Types}
|
|
677
800
|
*/
|
|
678
|
-
relation:
|
|
679
|
-
|
|
680
|
-
|
|
801
|
+
relation: unknown extends $PropertyType
|
|
802
|
+
? RelationType
|
|
803
|
+
: RelationTypeForProperty<$PropertyType>
|
|
681
804
|
/**
|
|
682
805
|
* The model and property name from which the relation is to be built, as a
|
|
683
806
|
* string with both identifiers separated by '.', e.g.:
|
|
684
807
|
* 'FromModelClass.fromPropertyName'
|
|
685
808
|
*/
|
|
686
|
-
from:
|
|
809
|
+
from: ModelRef<$Owner, $Models>
|
|
687
810
|
/**
|
|
688
811
|
* The model and property name to which the relation is to be built, as a
|
|
689
812
|
* string with both identifiers separated by '.', e.g.:
|
|
690
813
|
* 'ToModelClass.toPropertyName'
|
|
691
814
|
*/
|
|
692
|
-
to:
|
|
815
|
+
to: ModelRef<$Related, $Models>
|
|
693
816
|
/**
|
|
694
817
|
* When set to true the join model class and table is to be built
|
|
695
818
|
* automatically, or allows to specify an existing one manually.
|
|
@@ -705,14 +828,14 @@ export interface ModelRelation {
|
|
|
705
828
|
* be built, as a string with both identifiers separated by '.', e.g.:
|
|
706
829
|
* 'FromModelClass.fromPropertyName'
|
|
707
830
|
*/
|
|
708
|
-
from:
|
|
831
|
+
from: AnyModelRef<$Models>
|
|
709
832
|
/**
|
|
710
833
|
* The model and property name or table and column name of an existing
|
|
711
834
|
* join model class or join table to which the through relation is to be
|
|
712
835
|
* built, as a string with both identifiers separated by '.', e.g.:
|
|
713
836
|
* 'toModelClass.toPropertyName'
|
|
714
837
|
*/
|
|
715
|
-
to:
|
|
838
|
+
to: AnyModelRef<$Models>
|
|
716
839
|
/**
|
|
717
840
|
* List additional columns to be added to the related model.
|
|
718
841
|
*
|
|
@@ -733,11 +856,17 @@ export interface ModelRelation {
|
|
|
733
856
|
*/
|
|
734
857
|
inverse?: boolean
|
|
735
858
|
/**
|
|
736
|
-
* Optionally
|
|
737
|
-
*
|
|
738
|
-
* class
|
|
859
|
+
* Optionally overrides the related model class derived from
|
|
860
|
+
* the `to` property. Can be specified as the name the model
|
|
861
|
+
* was registered with or the model class itself.
|
|
862
|
+
*/
|
|
863
|
+
modelClass?: string | Class<$Related>
|
|
864
|
+
/**
|
|
865
|
+
* Optionally, one or more scopes can be defined to be applied when
|
|
866
|
+
* loading the relation's models. The scopes need to be defined in the
|
|
867
|
+
* related model class' scopes definitions.
|
|
739
868
|
*/
|
|
740
|
-
scope?: string
|
|
869
|
+
scope?: OrArrayOf<string>
|
|
741
870
|
/**
|
|
742
871
|
* Optionally, a filter to apply when loading the relation's models.
|
|
743
872
|
* Accepts a Dito.js filter name/object (resolved via the related model's
|
|
@@ -818,7 +947,7 @@ export type ModelProperty<T = any> = Schema<T> & {
|
|
|
818
947
|
*/
|
|
819
948
|
computed?: boolean
|
|
820
949
|
/**
|
|
821
|
-
* Marks the property
|
|
950
|
+
* Marks the property as hidden, so that it does not show up in data
|
|
822
951
|
* converted to JSON.
|
|
823
952
|
*
|
|
824
953
|
* This can be used for sensitive data.
|
|
@@ -850,6 +979,24 @@ export type ModelScopes<$Model extends Model = Model> = Record<
|
|
|
850
979
|
ModelScope<$Model>
|
|
851
980
|
>
|
|
852
981
|
|
|
982
|
+
/**
|
|
983
|
+
* A query modifier function that transforms a query builder.
|
|
984
|
+
* Modifiers are reusable query fragments applied via
|
|
985
|
+
* `.modify('name')` on queries.
|
|
986
|
+
*/
|
|
987
|
+
export type ModelModifier<$Model extends Model = Model> = (
|
|
988
|
+
query: QueryBuilder<$Model>,
|
|
989
|
+
...args: any[]
|
|
990
|
+
) => void
|
|
991
|
+
|
|
992
|
+
/**
|
|
993
|
+
* Map of modifier names to modifier functions.
|
|
994
|
+
*/
|
|
995
|
+
export type ModelModifiers<$Model extends Model = Model> = Record<
|
|
996
|
+
string,
|
|
997
|
+
ModelModifier<$Model>
|
|
998
|
+
>
|
|
999
|
+
|
|
853
1000
|
/**
|
|
854
1001
|
* A filter handler function that modifies a query builder
|
|
855
1002
|
* based on external parameters (e.g. from URL query strings).
|
|
@@ -859,6 +1006,45 @@ export type ModelFilterFunction<$Model extends Model = Model> = (
|
|
|
859
1006
|
...args: any[]
|
|
860
1007
|
) => void
|
|
861
1008
|
|
|
1009
|
+
/**
|
|
1010
|
+
* Registry of known filter type names for use with
|
|
1011
|
+
* `{ filter: '...', properties: [...] }` in model filter definitions.
|
|
1012
|
+
*
|
|
1013
|
+
* Filter types are reusable query filters registered via
|
|
1014
|
+
* `QueryFilters.register()`. Dito.js ships with two built-in types:
|
|
1015
|
+
* - `'text'` — text search with operators (contains, equals, etc.)
|
|
1016
|
+
* - `'date-range'` — date range filtering with from/to parameters
|
|
1017
|
+
*
|
|
1018
|
+
* Register custom filter types at runtime with
|
|
1019
|
+
* `QueryFilters.register()`, then declare them for type safety
|
|
1020
|
+
* via module augmentation:
|
|
1021
|
+
*
|
|
1022
|
+
* ```ts
|
|
1023
|
+
* // Register at runtime:
|
|
1024
|
+
* QueryFilters.register({
|
|
1025
|
+
* 'country': {
|
|
1026
|
+
* parameters: { code: { type: 'string' } },
|
|
1027
|
+
* handler(query, property, { code }) {
|
|
1028
|
+
* query.where(property, code)
|
|
1029
|
+
* }
|
|
1030
|
+
* }
|
|
1031
|
+
* })
|
|
1032
|
+
*
|
|
1033
|
+
* // Declare for type safety:
|
|
1034
|
+
* declare module '@ditojs/server' {
|
|
1035
|
+
* interface QueryFilterTypes {
|
|
1036
|
+
* 'country': true
|
|
1037
|
+
* }
|
|
1038
|
+
* }
|
|
1039
|
+
* ```
|
|
1040
|
+
*
|
|
1041
|
+
* @see {@link https://github.com/ditojs/dito/blob/main/docs/model-filters.md|Model Filters}
|
|
1042
|
+
*/
|
|
1043
|
+
export interface QueryFilterTypes {
|
|
1044
|
+
'text': true
|
|
1045
|
+
'date-range': true
|
|
1046
|
+
}
|
|
1047
|
+
|
|
862
1048
|
/**
|
|
863
1049
|
* A model filter definition. Can be one of:
|
|
864
1050
|
*
|
|
@@ -873,7 +1059,7 @@ export type ModelFilterFunction<$Model extends Model = Model> = (
|
|
|
873
1059
|
*/
|
|
874
1060
|
export type ModelFilter<$Model extends Model = Model> =
|
|
875
1061
|
| {
|
|
876
|
-
filter:
|
|
1062
|
+
filter: keyof QueryFilterTypes
|
|
877
1063
|
properties?: string[]
|
|
878
1064
|
}
|
|
879
1065
|
| {
|
|
@@ -949,6 +1135,11 @@ export type ModelHooks<$Model extends Model = Model> = {
|
|
|
949
1135
|
}
|
|
950
1136
|
|
|
951
1137
|
export class Model extends objection.Model {
|
|
1138
|
+
static query<M extends Model>(
|
|
1139
|
+
this: Constructor<M>,
|
|
1140
|
+
trxOrKnex?: objection.TransactionOrKnex
|
|
1141
|
+
): QueryBuilder<M, M[]>
|
|
1142
|
+
|
|
952
1143
|
constructor(json?: Record<string, any>)
|
|
953
1144
|
|
|
954
1145
|
/** @see {@link https://github.com/ditojs/dito/blob/main/docs/model-properties.md|Model Properties} */
|
|
@@ -963,6 +1154,47 @@ export class Model extends objection.Model {
|
|
|
963
1154
|
/** @see {@link https://github.com/ditojs/dito/blob/main/docs/model-filters.md|Model Filters} */
|
|
964
1155
|
static filters: ModelFilters<Model>
|
|
965
1156
|
|
|
1157
|
+
/**
|
|
1158
|
+
* Reusable named query fragments applied via
|
|
1159
|
+
* `.modify('name')` on query builders. Commonly used
|
|
1160
|
+
* to define shared select sets or filter conditions.
|
|
1161
|
+
*
|
|
1162
|
+
* @example
|
|
1163
|
+
* ```ts
|
|
1164
|
+
* // Define modifiers on a model:
|
|
1165
|
+
* static modifiers = {
|
|
1166
|
+
* sharedSelects: query =>
|
|
1167
|
+
* query.select('id', 'name', 'status'),
|
|
1168
|
+
* whereActive: query =>
|
|
1169
|
+
* query.where('active', true)
|
|
1170
|
+
* }
|
|
1171
|
+
*
|
|
1172
|
+
* // Use them on queries via QueryBuilder.modify():
|
|
1173
|
+
* query.modify('sharedSelects').modify('whereActive')
|
|
1174
|
+
* ```
|
|
1175
|
+
*/
|
|
1176
|
+
static modifiers: ModelModifiers<Model>
|
|
1177
|
+
|
|
1178
|
+
/**
|
|
1179
|
+
* Additional JSON Schema properties that are deep-merged
|
|
1180
|
+
* into the model's compiled schema (built from
|
|
1181
|
+
* {@link Model.properties}). Used for example to add
|
|
1182
|
+
* a custom `validate` function for cross-field validation.
|
|
1183
|
+
*
|
|
1184
|
+
* @example
|
|
1185
|
+
* ```ts
|
|
1186
|
+
* static schema: Schema = {
|
|
1187
|
+
* validate({ data, options }) {
|
|
1188
|
+
* if (!data.name) {
|
|
1189
|
+
* throw new Error('Name is required')
|
|
1190
|
+
* }
|
|
1191
|
+
* return true
|
|
1192
|
+
* }
|
|
1193
|
+
* }
|
|
1194
|
+
* ```
|
|
1195
|
+
*/
|
|
1196
|
+
static schema: Schema
|
|
1197
|
+
|
|
966
1198
|
static hooks: ModelHooks<Model>
|
|
967
1199
|
|
|
968
1200
|
static assets: ModelAssets
|
|
@@ -976,15 +1208,8 @@ export class Model extends objection.Model {
|
|
|
976
1208
|
hooks: ModelHooks
|
|
977
1209
|
assets: ModelAssets
|
|
978
1210
|
options: Record<string, any>
|
|
979
|
-
modifiers:
|
|
980
|
-
|
|
981
|
-
(
|
|
982
|
-
builder: objection.QueryBuilder<any, any>,
|
|
983
|
-
...args: any[]
|
|
984
|
-
) => void
|
|
985
|
-
>
|
|
986
|
-
schema: Record<string, any>
|
|
987
|
-
[key: string]: any
|
|
1211
|
+
modifiers: ModelModifiers
|
|
1212
|
+
schema: Schema
|
|
988
1213
|
}
|
|
989
1214
|
|
|
990
1215
|
/** Derived from class name (removes 'Model' suffix). */
|
|
@@ -1068,13 +1293,7 @@ export class Model extends objection.Model {
|
|
|
1068
1293
|
/** Returns the named property definition, if found. */
|
|
1069
1294
|
static getProperty(name: string): ModelProperty | null
|
|
1070
1295
|
/** Returns the model's query modifiers. */
|
|
1071
|
-
static getModifiers():
|
|
1072
|
-
string,
|
|
1073
|
-
(
|
|
1074
|
-
builder: objection.QueryBuilder<any, any>,
|
|
1075
|
-
...args: any[]
|
|
1076
|
-
) => void
|
|
1077
|
-
>
|
|
1296
|
+
static getModifiers(): ModelModifiers
|
|
1078
1297
|
|
|
1079
1298
|
/**
|
|
1080
1299
|
* Returns property names matching the given filter
|
|
@@ -1268,20 +1487,6 @@ export class Model extends objection.Model {
|
|
|
1268
1487
|
trx?: objection.Transaction
|
|
1269
1488
|
): Promise<Model | Model[]>
|
|
1270
1489
|
|
|
1271
|
-
/**
|
|
1272
|
-
* Dito.js automatically adds an `id` property if a model
|
|
1273
|
-
* property with the `primary: true` setting is not
|
|
1274
|
-
* already explicitly defined.
|
|
1275
|
-
*/
|
|
1276
|
-
readonly id: Id
|
|
1277
|
-
|
|
1278
|
-
/**
|
|
1279
|
-
* Dito.js automatically adds a `foreignKeyId` property
|
|
1280
|
-
* if foreign keys occurring in relations definitions are
|
|
1281
|
-
* not explicitly defined in the properties.
|
|
1282
|
-
*/
|
|
1283
|
-
readonly foreignKeyId: Id
|
|
1284
|
-
|
|
1285
1490
|
QueryBuilderType: QueryBuilder<this, this[]>
|
|
1286
1491
|
|
|
1287
1492
|
$app: Application<Models>
|
|
@@ -1506,20 +1711,112 @@ export interface Model extends KnexHelper {}
|
|
|
1506
1711
|
|
|
1507
1712
|
export type ModelClass = Class<Model>
|
|
1508
1713
|
|
|
1509
|
-
|
|
1714
|
+
type ModelRelationKey<T, K extends keyof T> = K extends
|
|
1715
|
+
| 'QueryBuilderType'
|
|
1716
|
+
| `$${string}`
|
|
1717
|
+
? never
|
|
1718
|
+
: T[K] extends (...args: any[]) => any
|
|
1719
|
+
? never
|
|
1720
|
+
: T[K] extends Model | Model[] | undefined
|
|
1721
|
+
? K
|
|
1722
|
+
: never
|
|
1723
|
+
|
|
1724
|
+
/**
|
|
1725
|
+
* A map of relation definitions for a model class.
|
|
1726
|
+
*
|
|
1727
|
+
* When used with a type argument, restricts keys to declared
|
|
1728
|
+
* `Model` or `Model[]` properties on the class.
|
|
1729
|
+
*
|
|
1730
|
+
* @example
|
|
1731
|
+
* ```ts
|
|
1732
|
+
* // Type-safe keys only:
|
|
1733
|
+
* class Tag extends Model {
|
|
1734
|
+
* declare tasks: Task[]
|
|
1735
|
+
*
|
|
1736
|
+
* static override relations: ModelRelations<Tag> = {
|
|
1737
|
+
* tasks: {
|
|
1738
|
+
* relation: 'manyToMany',
|
|
1739
|
+
* from: 'Tag.id',
|
|
1740
|
+
* to: 'Task.id'
|
|
1741
|
+
* }
|
|
1742
|
+
* }
|
|
1743
|
+
* }
|
|
1744
|
+
*
|
|
1745
|
+
* // With typed from/to via typeof import():
|
|
1746
|
+
* class Tag extends Model {
|
|
1747
|
+
* declare tasks: Task[]
|
|
1748
|
+
*
|
|
1749
|
+
* static override relations:
|
|
1750
|
+
* ModelRelations<Tag, typeof import('./models')> = {
|
|
1751
|
+
* tasks: {
|
|
1752
|
+
* relation: 'manyToMany',
|
|
1753
|
+
* from: 'Tag.id', // validated against Tag props
|
|
1754
|
+
* to: 'Task.id' // validated against Task props
|
|
1755
|
+
* }
|
|
1756
|
+
* }
|
|
1757
|
+
* }
|
|
1758
|
+
* ```
|
|
1759
|
+
*/
|
|
1760
|
+
export type ModelRelations<$Model extends Model = Model, $Models = unknown> = [
|
|
1761
|
+
keyof SerializedModel<$Model>
|
|
1762
|
+
] extends [never]
|
|
1763
|
+
? Record<string, ModelRelation>
|
|
1764
|
+
: {
|
|
1765
|
+
[K in keyof $Model as ModelRelationKey<$Model, K>]?: ModelRelation<
|
|
1766
|
+
$Model,
|
|
1767
|
+
RelatedModel<$Model[K]>,
|
|
1768
|
+
$Models,
|
|
1769
|
+
$Model[K]
|
|
1770
|
+
>
|
|
1771
|
+
}
|
|
1510
1772
|
|
|
1511
|
-
export type ModelProperties =
|
|
1773
|
+
export type ModelProperties<$Model extends Model = Model> = [
|
|
1774
|
+
keyof SerializedModel<$Model>
|
|
1775
|
+
] extends [never]
|
|
1776
|
+
? Record<string, ModelProperty>
|
|
1777
|
+
: {
|
|
1778
|
+
[K in keyof $Model as ModelDataKey<$Model, K>]?: ModelProperty<$Model[K]>
|
|
1779
|
+
}
|
|
1512
1780
|
|
|
1513
1781
|
/**
|
|
1514
|
-
* A controller action definition.
|
|
1515
|
-
*
|
|
1516
|
-
*
|
|
1517
|
-
*
|
|
1518
|
-
*
|
|
1782
|
+
* A controller action definition. The HTTP method and route
|
|
1783
|
+
* path are derived from the action name key (e.g.
|
|
1784
|
+
* `'post import'` → `POST /import`, `'get'` → `GET /`).
|
|
1785
|
+
*
|
|
1786
|
+
* Can be either a bare handler function or an options
|
|
1787
|
+
* object with `handler`, `authorize`, `parameters`, etc.
|
|
1788
|
+
*
|
|
1789
|
+
* @example
|
|
1790
|
+
* ```ts
|
|
1791
|
+
* // Bare handler function:
|
|
1792
|
+
* actions = {
|
|
1793
|
+
* 'post import'(ctx) {
|
|
1794
|
+
* return this.importData(ctx.request.body)
|
|
1795
|
+
* }
|
|
1796
|
+
* }
|
|
1797
|
+
*
|
|
1798
|
+
* // Options object:
|
|
1799
|
+
* actions = {
|
|
1800
|
+
* 'post import': {
|
|
1801
|
+
* handler(ctx) { ... },
|
|
1802
|
+
* authorize: 'admin',
|
|
1803
|
+
* transacted: true
|
|
1804
|
+
* }
|
|
1805
|
+
* }
|
|
1806
|
+
*
|
|
1807
|
+
* // With typed parameters:
|
|
1808
|
+
* const action: ControllerAction<this, { query: string }> = {
|
|
1809
|
+
* parameters: { query: { type: 'string' } },
|
|
1810
|
+
* handler(ctx, { query }) { ... } // query: string
|
|
1811
|
+
* }
|
|
1812
|
+
* ```
|
|
1519
1813
|
*/
|
|
1520
|
-
export type ControllerAction
|
|
1521
|
-
|
|
1522
|
-
|
|
1814
|
+
export type ControllerAction<
|
|
1815
|
+
$Controller extends Controller = Controller,
|
|
1816
|
+
$Params = Record<string, any>
|
|
1817
|
+
> =
|
|
1818
|
+
| ControllerActionOptions<$Controller, $Params>
|
|
1819
|
+
| ControllerActionHandler<$Controller, $Params>
|
|
1523
1820
|
|
|
1524
1821
|
export class Controller {
|
|
1525
1822
|
app: Application
|
|
@@ -1562,9 +1859,12 @@ export class Controller {
|
|
|
1562
1859
|
logRoutes: boolean
|
|
1563
1860
|
|
|
1564
1861
|
/**
|
|
1565
|
-
*
|
|
1566
|
-
*
|
|
1567
|
-
*
|
|
1862
|
+
* Which inherited actions to enable, e.g.
|
|
1863
|
+
* `['get', 'post', 'get stats']`. Actions defined
|
|
1864
|
+
* directly on the same object are always enabled.
|
|
1865
|
+
* When set, it replaces any parent controller's
|
|
1866
|
+
* `allow` list. When omitted, the parent's `allow`
|
|
1867
|
+
* list is inherited.
|
|
1568
1868
|
*/
|
|
1569
1869
|
allow?: OrReadOnly<ControllerActionName[]>
|
|
1570
1870
|
|
|
@@ -1620,15 +1920,25 @@ export class Controller {
|
|
|
1620
1920
|
transacted: boolean,
|
|
1621
1921
|
authorize: Authorize,
|
|
1622
1922
|
action: $ControllerAction,
|
|
1623
|
-
handlers: ((
|
|
1923
|
+
handlers: ((
|
|
1924
|
+
ctx: KoaContext,
|
|
1925
|
+
next: () => Promise<void>
|
|
1926
|
+
) => OrPromiseOf<void>)[]
|
|
1927
|
+
): void
|
|
1928
|
+
|
|
1929
|
+
setupActions(
|
|
1930
|
+
type: 'actions' | 'collection' | 'member'
|
|
1931
|
+
): Record<string, unknown> | undefined
|
|
1932
|
+
|
|
1933
|
+
setupActionRoute(
|
|
1934
|
+
type: 'actions' | 'collection' | 'member',
|
|
1935
|
+
action: ControllerAction
|
|
1624
1936
|
): void
|
|
1625
1937
|
|
|
1626
|
-
|
|
1627
|
-
setupActionRoute(type: string, action: ControllerAction): void
|
|
1628
|
-
setupAssets(): any
|
|
1938
|
+
setupAssets(): Record<string, unknown> | undefined
|
|
1629
1939
|
setupAssetRoute(
|
|
1630
1940
|
dataPath: OrArrayOf<string>,
|
|
1631
|
-
config:
|
|
1941
|
+
config: Record<string, unknown>,
|
|
1632
1942
|
authorize: Authorize
|
|
1633
1943
|
): void
|
|
1634
1944
|
|
|
@@ -1649,11 +1959,11 @@ export class Controller {
|
|
|
1649
1959
|
}
|
|
1650
1960
|
|
|
1651
1961
|
emitHook(
|
|
1652
|
-
type: string
|
|
1962
|
+
type: `${'before' | 'after'}:${string}`,
|
|
1653
1963
|
handleResult: boolean,
|
|
1654
1964
|
ctx: KoaContext,
|
|
1655
|
-
...args:
|
|
1656
|
-
): Promise<
|
|
1965
|
+
...args: unknown[]
|
|
1966
|
+
): Promise<unknown>
|
|
1657
1967
|
|
|
1658
1968
|
processAuthorize(
|
|
1659
1969
|
authorize: Authorize
|
|
@@ -1680,9 +1990,11 @@ export type ActionParameter = Schema & { name: string }
|
|
|
1680
1990
|
* the Koa context and any resolved action parameters. `this`
|
|
1681
1991
|
* is bound to the controller instance.
|
|
1682
1992
|
*/
|
|
1683
|
-
export type ModelControllerActionHandler
|
|
1684
|
-
$ModelController
|
|
1685
|
-
|
|
1993
|
+
export type ModelControllerActionHandler<$ModelController = ModelController> = (
|
|
1994
|
+
this: $ModelController,
|
|
1995
|
+
ctx: KoaContext,
|
|
1996
|
+
...args: any[]
|
|
1997
|
+
) => any
|
|
1686
1998
|
|
|
1687
1999
|
/**
|
|
1688
2000
|
* Handler function for a controller action. Receives the Koa
|
|
@@ -1690,12 +2002,14 @@ export type ModelControllerActionHandler<
|
|
|
1690
2002
|
* bound to the controller instance.
|
|
1691
2003
|
*/
|
|
1692
2004
|
export type ControllerActionHandler<
|
|
1693
|
-
$Controller extends Controller = Controller
|
|
1694
|
-
|
|
2005
|
+
$Controller extends Controller = Controller,
|
|
2006
|
+
$Params = Record<string, any>
|
|
2007
|
+
> = keyof $Params extends never
|
|
2008
|
+
? (this: $Controller, ctx: KoaContext) => any
|
|
2009
|
+
: (this: $Controller, ctx: KoaContext, params: $Params) => any
|
|
1695
2010
|
|
|
1696
2011
|
type ModelDataKey<T, K extends keyof T> = K extends
|
|
1697
2012
|
| 'QueryBuilderType'
|
|
1698
|
-
| 'foreignKeyId'
|
|
1699
2013
|
| `$${string}`
|
|
1700
2014
|
? never
|
|
1701
2015
|
: T[K] extends (...args: any[]) => any
|
|
@@ -1720,14 +2034,14 @@ type ModelDataProperties<$Model extends Model = Model> = {
|
|
|
1720
2034
|
* - Any other `string`: Checked as a role via
|
|
1721
2035
|
* `UserModel.$hasRole()`.
|
|
1722
2036
|
* - `function`: Dynamically resolves to any of the above.
|
|
1723
|
-
* - `Record<HTTPMethod, string | string[]
|
|
2037
|
+
* - `Partial<Record<HTTPMethod, string | string[]>>`: Per-method
|
|
1724
2038
|
* role authorization.
|
|
1725
2039
|
*/
|
|
1726
2040
|
export type Authorize =
|
|
1727
2041
|
| boolean
|
|
1728
2042
|
| OrArrayOf<LiteralUnion<'$self' | '$owner'>>
|
|
1729
2043
|
| ((ctx: KoaContext) => OrPromiseOf<Authorize>)
|
|
1730
|
-
| Record<HTTPMethod, string | string[]
|
|
2044
|
+
| Partial<Record<HTTPMethod, string | string[]>>
|
|
1731
2045
|
|
|
1732
2046
|
export type BaseControllerActionOptions = {
|
|
1733
2047
|
/**
|
|
@@ -1756,13 +2070,6 @@ export type BaseControllerActionOptions = {
|
|
|
1756
2070
|
* overridable `UserModel.hasRole()` method.
|
|
1757
2071
|
*/
|
|
1758
2072
|
authorize?: Authorize
|
|
1759
|
-
/**
|
|
1760
|
-
* Validates action parameters and maps them to Koa's `ctx.query` object
|
|
1761
|
-
* passed to the action handler.
|
|
1762
|
-
*
|
|
1763
|
-
* @see {@link https://github.com/ditojs/dito/blob/main/docs/model-properties.md Model Properties}
|
|
1764
|
-
*/
|
|
1765
|
-
parameters?: { [key: string]: Schema }
|
|
1766
2073
|
/**
|
|
1767
2074
|
* Provides a schema for the value returned from the action handler and
|
|
1768
2075
|
* optionally maps the value to a key inside a returned object when it
|
|
@@ -1786,17 +2093,41 @@ export type BaseControllerActionOptions = {
|
|
|
1786
2093
|
}
|
|
1787
2094
|
|
|
1788
2095
|
export type ControllerActionOptions<
|
|
1789
|
-
$Controller extends Controller = Controller
|
|
2096
|
+
$Controller extends Controller = Controller,
|
|
2097
|
+
$Params = Record<string, any>
|
|
1790
2098
|
> = BaseControllerActionOptions & {
|
|
1791
|
-
|
|
2099
|
+
/**
|
|
2100
|
+
* Defines validated parameters for the action handler.
|
|
2101
|
+
* The handler receives a single parameter object with
|
|
2102
|
+
* the same keys:
|
|
2103
|
+
* ```ts
|
|
2104
|
+
* parameters: { cart: { type: 'object' } },
|
|
2105
|
+
* handler(ctx, { cart }) { ... }
|
|
2106
|
+
* ```
|
|
2107
|
+
*
|
|
2108
|
+
* For strongly-typed parameters, pass a params map as
|
|
2109
|
+
* the second type argument to {@link ControllerActions}:
|
|
2110
|
+
* ```ts
|
|
2111
|
+
* actions: ControllerActions<this, {
|
|
2112
|
+
* 'get search': { query: string }
|
|
2113
|
+
* }>
|
|
2114
|
+
* ```
|
|
2115
|
+
*/
|
|
2116
|
+
parameters?: { [K in keyof $Params]: Schema<$Params[K]> }
|
|
2117
|
+
handler: ControllerActionHandler<$Controller, $Params>
|
|
1792
2118
|
}
|
|
1793
2119
|
|
|
1794
|
-
export type ModelControllerActionOptions
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
2120
|
+
export type ModelControllerActionOptions<$ModelController = ModelController> =
|
|
2121
|
+
BaseControllerActionOptions & {
|
|
2122
|
+
/**
|
|
2123
|
+
* Defines validated parameters for the action handler.
|
|
2124
|
+
* The handler receives a single parameter object with
|
|
2125
|
+
* the same keys.
|
|
2126
|
+
*/
|
|
2127
|
+
parameters?: { [key: string]: Schema }
|
|
2128
|
+
/** The function to be called when the action route is requested. */
|
|
2129
|
+
handler: ModelControllerActionHandler<$ModelController>
|
|
2130
|
+
}
|
|
1800
2131
|
|
|
1801
2132
|
export type MemberActionParameter<$Model extends Model = Model> =
|
|
1802
2133
|
| Schema
|
|
@@ -1825,28 +2156,22 @@ export type MemberActionParameter<$Model extends Model = Model> =
|
|
|
1825
2156
|
* A model controller action: either an options object with
|
|
1826
2157
|
* `handler` or a bare handler function.
|
|
1827
2158
|
*/
|
|
1828
|
-
export type ModelControllerAction
|
|
1829
|
-
$ModelController extends ModelController = ModelController
|
|
1830
|
-
> =
|
|
2159
|
+
export type ModelControllerAction<$ModelController = ModelController> =
|
|
1831
2160
|
| ModelControllerActionOptions<$ModelController>
|
|
1832
2161
|
| ModelControllerActionHandler<$ModelController>
|
|
1833
2162
|
|
|
1834
2163
|
/**
|
|
1835
2164
|
* Map of action names to action definitions for a model
|
|
1836
|
-
* controller's collection-level actions (e.g. `
|
|
1837
|
-
* `
|
|
2165
|
+
* controller's collection-level actions (e.g. `'get'`,
|
|
2166
|
+
* `'post'`, `'post login'`).
|
|
1838
2167
|
*/
|
|
1839
|
-
export type ModelControllerActions
|
|
1840
|
-
$ModelController extends ModelController = ModelController
|
|
1841
|
-
> = {
|
|
2168
|
+
export type ModelControllerActions<$ModelController = ModelController> = {
|
|
1842
2169
|
[name: ControllerActionName]: ModelControllerAction<$ModelController>
|
|
1843
2170
|
allow?: OrReadOnly<ControllerActionName[]>
|
|
1844
2171
|
authorize?: Authorize
|
|
1845
2172
|
}
|
|
1846
2173
|
|
|
1847
|
-
type ModelControllerMemberAction
|
|
1848
|
-
$ModelController extends ModelController = ModelController
|
|
1849
|
-
> =
|
|
2174
|
+
type ModelControllerMemberAction<$ModelController = ModelController> =
|
|
1850
2175
|
| (Omit<ModelControllerActionOptions<$ModelController>, 'parameters'> & {
|
|
1851
2176
|
parameters?: {
|
|
1852
2177
|
[key: string]: MemberActionParameter<
|
|
@@ -1862,28 +2187,66 @@ type ModelControllerMemberAction<
|
|
|
1862
2187
|
* `delete`). Member actions can use `{ from: 'member' }`
|
|
1863
2188
|
* parameters to receive the resolved member model.
|
|
1864
2189
|
*/
|
|
1865
|
-
export type ModelControllerMemberActions
|
|
1866
|
-
$ModelController extends ModelController = ModelController
|
|
1867
|
-
> = {
|
|
2190
|
+
export type ModelControllerMemberActions<$ModelController = ModelController> = {
|
|
1868
2191
|
[name: ControllerActionName]: ModelControllerMemberAction<$ModelController>
|
|
1869
2192
|
allow?: OrReadOnly<ControllerActionName[]>
|
|
1870
2193
|
authorize?: Authorize
|
|
1871
2194
|
}
|
|
1872
2195
|
|
|
1873
2196
|
/**
|
|
1874
|
-
* Action name
|
|
1875
|
-
*
|
|
1876
|
-
* `'
|
|
2197
|
+
* Action name: an HTTP method, optionally followed by a
|
|
2198
|
+
* space and a name (e.g. `'get'`, `'post login'`,
|
|
2199
|
+
* `'get session'`). The method and name are split on the
|
|
2200
|
+
* first space to determine the HTTP method and route path.
|
|
2201
|
+
* Bare names without an HTTP method prefix (e.g. `'login'`)
|
|
2202
|
+
* are not supported and will throw at runtime.
|
|
1877
2203
|
*/
|
|
1878
2204
|
export type ControllerActionName = `${HTTPMethod}${string}`
|
|
1879
2205
|
|
|
1880
2206
|
/**
|
|
1881
2207
|
* Map of action names to action definitions for a
|
|
1882
|
-
* controller.
|
|
1883
|
-
*
|
|
2208
|
+
* controller. Action names follow the pattern
|
|
2209
|
+
* `'{method} {path}'` (e.g. `'post import'`) or just
|
|
2210
|
+
* `'{method}'` for default CRUD actions (e.g. `'get'`).
|
|
2211
|
+
*
|
|
2212
|
+
* Use `allow` to whitelist specific actions and
|
|
2213
|
+
* `authorize` for group-level authorization.
|
|
2214
|
+
*
|
|
2215
|
+
* Pass a `$ParamsMap` as the second type argument to
|
|
2216
|
+
* strongly type handler parameters per action:
|
|
2217
|
+
*
|
|
2218
|
+
* @example
|
|
2219
|
+
* ```ts
|
|
2220
|
+
* type Params = {
|
|
2221
|
+
* 'get search': { query: string }
|
|
2222
|
+
* 'post create': { name: string }
|
|
2223
|
+
* }
|
|
2224
|
+
*
|
|
2225
|
+
* actions: ControllerActions<this, Params> = {
|
|
2226
|
+
* allow: ['get search', 'post create'],
|
|
2227
|
+
* 'get search': {
|
|
2228
|
+
* parameters: { query: { type: 'string' } },
|
|
2229
|
+
* handler(ctx, { query }) { ... } // query: string
|
|
2230
|
+
* },
|
|
2231
|
+
* 'post create': {
|
|
2232
|
+
* parameters: { name: { type: 'string' } },
|
|
2233
|
+
* handler(ctx, { name }) { ... } // name: string
|
|
2234
|
+
* }
|
|
2235
|
+
* }
|
|
2236
|
+
* ```
|
|
2237
|
+
*
|
|
2238
|
+
* @see {@link ControllerAction}
|
|
1884
2239
|
*/
|
|
1885
|
-
export type ControllerActions
|
|
1886
|
-
|
|
2240
|
+
export type ControllerActions<
|
|
2241
|
+
$Controller extends Controller = Controller,
|
|
2242
|
+
$ParamsMap extends Record<string, any> = {}
|
|
2243
|
+
> = {
|
|
2244
|
+
[K in keyof $ParamsMap & ControllerActionName]?: ControllerAction<
|
|
2245
|
+
$Controller,
|
|
2246
|
+
$ParamsMap[K]
|
|
2247
|
+
>
|
|
2248
|
+
} & {
|
|
2249
|
+
[name: ControllerActionName]: ControllerAction<$Controller, any>
|
|
1887
2250
|
allow?: OrReadOnly<ControllerActionName[]>
|
|
1888
2251
|
authorize?: Authorize
|
|
1889
2252
|
}
|
|
@@ -1916,66 +2279,65 @@ export class AdminController extends Controller {
|
|
|
1916
2279
|
defineViteConfig(config?: UserConfig): UserConfig
|
|
1917
2280
|
}
|
|
1918
2281
|
type ModelControllerHookType = 'collection' | 'member'
|
|
1919
|
-
type
|
|
1920
|
-
$Keys extends string,
|
|
1921
|
-
$ModelControllerHookType extends string
|
|
1922
|
-
> = `${
|
|
2282
|
+
type ModelControllerHookKey = `${
|
|
1923
2283
|
| 'before'
|
|
1924
2284
|
| 'after'
|
|
1925
2285
|
| '*'
|
|
1926
2286
|
}:${
|
|
1927
|
-
|
|
|
2287
|
+
| ModelControllerHookType
|
|
1928
2288
|
| '*'
|
|
1929
2289
|
}:${
|
|
1930
|
-
| Exclude<$Keys, 'allow'>
|
|
1931
2290
|
| ControllerActionName
|
|
1932
2291
|
| '*'
|
|
1933
2292
|
}`
|
|
1934
|
-
type ModelControllerHook<
|
|
1935
|
-
$ModelController extends ModelController = ModelController
|
|
1936
|
-
> = (
|
|
1937
|
-
ctx: KoaContext,
|
|
1938
|
-
result: objection.Page<ModelFromModelController<$ModelController>>
|
|
1939
|
-
) => any
|
|
1940
|
-
|
|
1941
|
-
type HookKeysFromController<$ModelController extends ModelController> =
|
|
1942
|
-
| ModelControllerHookKeys<
|
|
1943
|
-
Exclude<
|
|
1944
|
-
keyof Exclude<$ModelController['collection'], undefined>,
|
|
1945
|
-
symbol | number
|
|
1946
|
-
>,
|
|
1947
|
-
'collection'
|
|
1948
|
-
>
|
|
1949
|
-
| ModelControllerHookKeys<
|
|
1950
|
-
Exclude<
|
|
1951
|
-
keyof Exclude<$ModelController['member'], undefined>,
|
|
1952
|
-
symbol | number
|
|
1953
|
-
>,
|
|
1954
|
-
'member'
|
|
1955
|
-
>
|
|
1956
2293
|
|
|
1957
|
-
type
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
| '
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
type ModelControllerHooks
|
|
1973
|
-
$
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
2294
|
+
type CrudActionName = 'get' | 'post' | 'put' | 'patch' | 'delete'
|
|
2295
|
+
type NonDeleteCrudActionName = Exclude<CrudActionName, 'delete'>
|
|
2296
|
+
|
|
2297
|
+
type BeforeHookKey =
|
|
2298
|
+
`${'before' | '*'}:${ModelControllerHookType | '*'}:${ControllerActionName | '*'}`
|
|
2299
|
+
type AfterDeleteHookKey = `after:${ModelControllerHookType | '*'}:delete`
|
|
2300
|
+
type AfterItemHookKey =
|
|
2301
|
+
| `after:member:${NonDeleteCrudActionName}`
|
|
2302
|
+
| `after:collection:${'post' | 'put' | 'patch'}`
|
|
2303
|
+
type AfterCollectionGetHookKey = `after:collection:get`
|
|
2304
|
+
// Catch-all for custom actions and wildcard scopes;
|
|
2305
|
+
// CRUD hooks get strongly-typed results via the specific key types above.
|
|
2306
|
+
type AfterCustomHookKey =
|
|
2307
|
+
`after:${ModelControllerHookType | '*'}:${ControllerActionName | '*'}`
|
|
2308
|
+
|
|
2309
|
+
export type ModelControllerHooks<$ModelController = ModelController> = {
|
|
2310
|
+
[$Key in BeforeHookKey]?: (
|
|
2311
|
+
this: $ModelController,
|
|
2312
|
+
ctx: KoaContext,
|
|
2313
|
+
params?: Record<string, any>
|
|
2314
|
+
) => void
|
|
2315
|
+
} & {
|
|
2316
|
+
[$Key in AfterDeleteHookKey]?: (
|
|
2317
|
+
this: $ModelController,
|
|
2318
|
+
ctx: KoaContext,
|
|
2319
|
+
result: { count: number }
|
|
2320
|
+
) => any
|
|
2321
|
+
} & {
|
|
2322
|
+
[$Key in AfterItemHookKey]?: (
|
|
2323
|
+
this: $ModelController,
|
|
2324
|
+
ctx: KoaContext,
|
|
2325
|
+
item: ModelFromModelController<$ModelController>
|
|
2326
|
+
) => any
|
|
2327
|
+
} & {
|
|
2328
|
+
[$Key in AfterCollectionGetHookKey]?: (
|
|
2329
|
+
this: $ModelController,
|
|
2330
|
+
ctx: KoaContext,
|
|
2331
|
+
result:
|
|
2332
|
+
| ModelFromModelController<$ModelController>[]
|
|
2333
|
+
| Page<ModelFromModelController<$ModelController>>
|
|
2334
|
+
) => any
|
|
2335
|
+
} & {
|
|
2336
|
+
[$Key in AfterCustomHookKey]?: (
|
|
2337
|
+
this: $ModelController,
|
|
2338
|
+
ctx: KoaContext,
|
|
2339
|
+
result: any
|
|
2340
|
+
) => any
|
|
1979
2341
|
}
|
|
1980
2342
|
|
|
1981
2343
|
/**
|
|
@@ -2026,12 +2388,12 @@ export class CollectionController<
|
|
|
2026
2388
|
* The controller's collection actions with built-in CRUD
|
|
2027
2389
|
* defaults.
|
|
2028
2390
|
*/
|
|
2029
|
-
collection?: ModelControllerActions<
|
|
2391
|
+
collection?: ModelControllerActions<this>
|
|
2030
2392
|
/**
|
|
2031
2393
|
* The controller's member actions with built-in CRUD
|
|
2032
2394
|
* defaults.
|
|
2033
2395
|
*/
|
|
2034
|
-
member?: ModelControllerMemberActions<
|
|
2396
|
+
member?: ModelControllerMemberActions<this>
|
|
2035
2397
|
|
|
2036
2398
|
/** Creates a query builder for this controller's model. */
|
|
2037
2399
|
query(trx?: objection.Transaction): QueryBuilder<$Model>
|
|
@@ -2071,13 +2433,13 @@ export class CollectionController<
|
|
|
2071
2433
|
* Executes a controller action within a transaction
|
|
2072
2434
|
* context.
|
|
2073
2435
|
*/
|
|
2074
|
-
execute(
|
|
2436
|
+
execute<R>(
|
|
2075
2437
|
ctx: KoaContext,
|
|
2076
2438
|
execute: (
|
|
2077
2439
|
query: QueryBuilder<$Model>,
|
|
2078
2440
|
trx?: objection.Transaction
|
|
2079
|
-
) =>
|
|
2080
|
-
): Promise<
|
|
2441
|
+
) => R
|
|
2442
|
+
): Promise<Awaited<R>>
|
|
2081
2443
|
|
|
2082
2444
|
/**
|
|
2083
2445
|
* Extracts model IDs from the request body collection,
|
|
@@ -2152,12 +2514,12 @@ export class ModelController<
|
|
|
2152
2514
|
* The controller's collection actions. Wrap actions in
|
|
2153
2515
|
* this object to assign them to the collection.
|
|
2154
2516
|
*/
|
|
2155
|
-
collection?: ModelControllerActions<
|
|
2517
|
+
collection?: ModelControllerActions<this>
|
|
2156
2518
|
/**
|
|
2157
2519
|
* The controller's member actions. Wrap actions in this
|
|
2158
2520
|
* object to assign them to the member.
|
|
2159
2521
|
*/
|
|
2160
|
-
member?: ModelControllerMemberActions<
|
|
2522
|
+
member?: ModelControllerMemberActions<this>
|
|
2161
2523
|
assets?:
|
|
2162
2524
|
| boolean
|
|
2163
2525
|
| {
|
|
@@ -2166,10 +2528,55 @@ export class ModelController<
|
|
|
2166
2528
|
}
|
|
2167
2529
|
|
|
2168
2530
|
/**
|
|
2169
|
-
*
|
|
2170
|
-
*
|
|
2531
|
+
* Lifecycle hooks that run before or after controller
|
|
2532
|
+
* actions. Keys follow the pattern
|
|
2533
|
+
* `'{before|after|*}:{collection|member|*}:{actionName|*}'`.
|
|
2534
|
+
* Use `'*'` in any segment to match all values.
|
|
2535
|
+
*
|
|
2536
|
+
* @example
|
|
2537
|
+
* ```ts
|
|
2538
|
+
* override hooks: ModelControllerHooks<MyController> = {
|
|
2539
|
+
* // Guard: prevent patching locked records
|
|
2540
|
+
* 'before:member:patch'(ctx) {
|
|
2541
|
+
* if (ctx.request.body.locked) {
|
|
2542
|
+
* throw new ResponseError('Locked records cannot be modified', {
|
|
2543
|
+
* status: 403
|
|
2544
|
+
* })
|
|
2545
|
+
* }
|
|
2546
|
+
* },
|
|
2547
|
+
* // Enrich: attach computed data to the member
|
|
2548
|
+
* async 'after:member:get'(ctx, item) {
|
|
2549
|
+
* const isProcessing = await checkJobStatus(item.id)
|
|
2550
|
+
* item.$set({ isProcessing })
|
|
2551
|
+
* return item
|
|
2552
|
+
* },
|
|
2553
|
+
* // Side effect: log after a successful deletion
|
|
2554
|
+
* 'after:member:delete'(ctx, result) {
|
|
2555
|
+
* const name = this.modelClass.name
|
|
2556
|
+
* logger.info(`Deleted ${result.count} ${name} record(s)`)
|
|
2557
|
+
* },
|
|
2558
|
+
* // Custom action: runs after a custom 'get export' action
|
|
2559
|
+
* 'after:collection:get export'(ctx, items) {
|
|
2560
|
+
* analytics.track('export', { count: items.length })
|
|
2561
|
+
* },
|
|
2562
|
+
* // Custom action: receives the resolved parameters
|
|
2563
|
+
* 'before:member:post import'(ctx, params) {
|
|
2564
|
+
* logger.info(`Importing ${params?.repoFamily?.name}`)
|
|
2565
|
+
* }
|
|
2566
|
+
* }
|
|
2567
|
+
* ```
|
|
2568
|
+
*
|
|
2569
|
+
* `before:` CRUD hooks receive only `(ctx)`. Custom
|
|
2570
|
+
* action hooks also receive the resolved `params` object.
|
|
2571
|
+
*
|
|
2572
|
+
* `after:` hooks receive `(ctx, result)`. Returning a
|
|
2573
|
+
* value from an `after:` hook replaces the action result.
|
|
2574
|
+
*
|
|
2575
|
+
* @see {@link ModelControllerActions}
|
|
2576
|
+
* @see {@link ModelControllerMemberActions}
|
|
2577
|
+
* @see {@link QueryParameterOptions} for pagination parameters
|
|
2171
2578
|
*/
|
|
2172
|
-
hooks?: ModelControllerHooks<
|
|
2579
|
+
hooks?: ModelControllerHooks<this>
|
|
2173
2580
|
/** Map of relation name to RelationController instance. */
|
|
2174
2581
|
relations?: Record<string, RelationController>
|
|
2175
2582
|
}
|
|
@@ -2502,7 +2909,25 @@ export type QueryParameterOptions = {
|
|
|
2502
2909
|
}
|
|
2503
2910
|
export type QueryParameterOptionKey = keyof QueryParameterOptions
|
|
2504
2911
|
|
|
2505
|
-
|
|
2912
|
+
/**
|
|
2913
|
+
* Base class for application services.
|
|
2914
|
+
*
|
|
2915
|
+
* @typeParam $Config - The shape of the service's configuration
|
|
2916
|
+
* object, accessed via `this.config` in service methods.
|
|
2917
|
+
*
|
|
2918
|
+
* ```ts
|
|
2919
|
+
* class MailService extends Service<{
|
|
2920
|
+
* smtp: { host: string; port: number }
|
|
2921
|
+
* from: string
|
|
2922
|
+
* }> {
|
|
2923
|
+
* async send(to: string, body: string) {
|
|
2924
|
+
* const { host, port } = this.config!.smtp
|
|
2925
|
+
* // ...
|
|
2926
|
+
* }
|
|
2927
|
+
* }
|
|
2928
|
+
* ```
|
|
2929
|
+
*/
|
|
2930
|
+
export class Service<$Config extends object = Record<string, unknown>> {
|
|
2506
2931
|
constructor(app: Application<Models>, name?: string)
|
|
2507
2932
|
|
|
2508
2933
|
/** The application instance. */
|
|
@@ -2510,11 +2935,11 @@ export class Service {
|
|
|
2510
2935
|
/** The camelized service name. */
|
|
2511
2936
|
name: string
|
|
2512
2937
|
/** The service configuration. */
|
|
2513
|
-
config:
|
|
2938
|
+
config: $Config | null
|
|
2514
2939
|
/** Whether this service has been initialized. */
|
|
2515
2940
|
initialized: boolean
|
|
2516
2941
|
|
|
2517
|
-
setup(config:
|
|
2942
|
+
setup(config: $Config): void
|
|
2518
2943
|
|
|
2519
2944
|
/**
|
|
2520
2945
|
* Override in sub-classes if the service needs async
|
|
@@ -2533,6 +2958,46 @@ export class Service {
|
|
|
2533
2958
|
}
|
|
2534
2959
|
export type Services = Record<string, Class<Service> | Service>
|
|
2535
2960
|
|
|
2961
|
+
/**
|
|
2962
|
+
* Registry of application service instances by name.
|
|
2963
|
+
*
|
|
2964
|
+
* Extend via module augmentation for typed `app.services` access:
|
|
2965
|
+
*
|
|
2966
|
+
* ```ts
|
|
2967
|
+
* declare module '@ditojs/server' {
|
|
2968
|
+
* interface ApplicationServices {
|
|
2969
|
+
* mail: MailService
|
|
2970
|
+
* jobs: JobService
|
|
2971
|
+
* }
|
|
2972
|
+
* }
|
|
2973
|
+
* ```
|
|
2974
|
+
*/
|
|
2975
|
+
export interface ApplicationServices {}
|
|
2976
|
+
|
|
2977
|
+
type ResolvedServices = keyof ApplicationServices extends never
|
|
2978
|
+
? Record<string, Service>
|
|
2979
|
+
: ApplicationServices
|
|
2980
|
+
|
|
2981
|
+
/**
|
|
2982
|
+
* Registry of application storage instances by name.
|
|
2983
|
+
*
|
|
2984
|
+
* Extend via module augmentation for typed `app.storages` access:
|
|
2985
|
+
*
|
|
2986
|
+
* ```ts
|
|
2987
|
+
* declare module '@ditojs/server' {
|
|
2988
|
+
* interface ApplicationStorages {
|
|
2989
|
+
* s3: Storage
|
|
2990
|
+
* local: Storage
|
|
2991
|
+
* }
|
|
2992
|
+
* }
|
|
2993
|
+
* ```
|
|
2994
|
+
*/
|
|
2995
|
+
export interface ApplicationStorages {}
|
|
2996
|
+
|
|
2997
|
+
type ResolvedStorages = keyof ApplicationStorages extends never
|
|
2998
|
+
? Record<string, Storage>
|
|
2999
|
+
: ApplicationStorages
|
|
3000
|
+
|
|
2536
3001
|
export class QueryBuilder<
|
|
2537
3002
|
M extends Model,
|
|
2538
3003
|
R = M[]
|
|
@@ -2908,6 +3373,19 @@ export class Storage {
|
|
|
2908
3373
|
*/
|
|
2909
3374
|
convertAssetFile(file: AssetFileObject): void
|
|
2910
3375
|
|
|
3376
|
+
/**
|
|
3377
|
+
* Signs an asset file by setting its `signature` property
|
|
3378
|
+
* to an HMAC derived from its storage key.
|
|
3379
|
+
*/
|
|
3380
|
+
signAssetFile(file: AssetFile): void
|
|
3381
|
+
|
|
3382
|
+
/**
|
|
3383
|
+
* Verifies that an asset file's signature matches its
|
|
3384
|
+
* storage key. Returns false if the signature is missing
|
|
3385
|
+
* or invalid.
|
|
3386
|
+
*/
|
|
3387
|
+
verifyAssetFile(file: AssetFile): boolean
|
|
3388
|
+
|
|
2911
3389
|
/** Registers a storage subclass by type name. */
|
|
2912
3390
|
static register(storageClass: Class<Storage>): void
|
|
2913
3391
|
/** Retrieves a registered storage class by type name. */
|
|
@@ -3140,11 +3618,22 @@ export function addRelationSchemas(
|
|
|
3140
3618
|
properties: Record<string, ModelProperty>
|
|
3141
3619
|
): void
|
|
3142
3620
|
|
|
3143
|
-
export type Keyword =
|
|
3621
|
+
export type Keyword = (
|
|
3144
3622
|
| SetOptional<Ajv.MacroKeywordDefinition, 'keyword'>
|
|
3145
3623
|
| SetOptional<Ajv.CodeKeywordDefinition, 'keyword'>
|
|
3146
3624
|
| SetOptional<Ajv.FuncKeywordDefinition, 'keyword'>
|
|
3147
|
-
|
|
3625
|
+
) & {
|
|
3626
|
+
/** Custom error message shown when validation fails. */
|
|
3627
|
+
message?: string
|
|
3628
|
+
/** When true, validation errors for this keyword are suppressed. */
|
|
3629
|
+
silent?: boolean
|
|
3630
|
+
}
|
|
3631
|
+
export type Format = (Ajv.ValidateFunction | Ajv.FormatDefinition<string>) & {
|
|
3632
|
+
/** Custom error message shown when validation fails. */
|
|
3633
|
+
message?: string
|
|
3634
|
+
/** When true, validation errors for this format are suppressed. */
|
|
3635
|
+
silent?: boolean
|
|
3636
|
+
}
|
|
3148
3637
|
|
|
3149
3638
|
/** Built-in AJV keyword definitions. */
|
|
3150
3639
|
export const keywords: {
|
|
@@ -3175,14 +3664,34 @@ export const types: {
|
|
|
3175
3664
|
color: Record<string, any>
|
|
3176
3665
|
}
|
|
3177
3666
|
export type Id = string | number
|
|
3178
|
-
|
|
3667
|
+
/**
|
|
3668
|
+
* Koa context state available as `ctx.state`.
|
|
3669
|
+
*
|
|
3670
|
+
* Extend via module augmentation for typed state access:
|
|
3671
|
+
*
|
|
3672
|
+
* ```ts
|
|
3673
|
+
* declare module '@ditojs/server' {
|
|
3674
|
+
* interface KoaContextState {
|
|
3675
|
+
* foo: string
|
|
3676
|
+
* }
|
|
3677
|
+
* }
|
|
3678
|
+
* ```
|
|
3679
|
+
*/
|
|
3680
|
+
export interface KoaContextState {
|
|
3681
|
+
user: InstanceType<typeof UserModel>
|
|
3682
|
+
[key: string]: unknown
|
|
3683
|
+
}
|
|
3684
|
+
|
|
3685
|
+
export type KoaContext<$State = KoaContextState> = Koa.ParameterizedContext<
|
|
3179
3686
|
$State,
|
|
3180
3687
|
{
|
|
3181
3688
|
transaction: objection.Transaction
|
|
3182
|
-
session: koaSession.ContextSession
|
|
3689
|
+
session: koaSession.ContextSession
|
|
3183
3690
|
logger: PinoLogger
|
|
3184
3691
|
}
|
|
3185
|
-
>
|
|
3692
|
+
> & {
|
|
3693
|
+
app: Application
|
|
3694
|
+
}
|
|
3186
3695
|
|
|
3187
3696
|
type LiteralUnion<T extends U, U = string> = T | (U & Record<never, never>)
|
|
3188
3697
|
|
|
@@ -3192,8 +3701,9 @@ type OrReadOnly<T> = Readonly<T> | T
|
|
|
3192
3701
|
|
|
3193
3702
|
type OrPromiseOf<T> = Promise<T> | T
|
|
3194
3703
|
|
|
3195
|
-
type ModelFromModelController
|
|
3196
|
-
|
|
3704
|
+
type ModelFromModelController<
|
|
3705
|
+
$ModelController extends { modelClass?: Class<any> }
|
|
3706
|
+
> = InstanceType<Exclude<$ModelController['modelClass'], undefined>>
|
|
3197
3707
|
|
|
3198
3708
|
type SerializeModelPropertyValue<T> = T extends (infer U)[]
|
|
3199
3709
|
? SerializeModelPropertyValue<U>[]
|
|
@@ -3244,10 +3754,10 @@ export type SelectModelPropertyKeys<T extends Model> = keyof SerializedModel<T>
|
|
|
3244
3754
|
* }
|
|
3245
3755
|
* ```
|
|
3246
3756
|
*/
|
|
3247
|
-
export type Schema
|
|
3757
|
+
export type Schema<$Value = any> = JSONSchemaType<$Value> & {
|
|
3248
3758
|
// keywords/_validate.js
|
|
3249
3759
|
validate?: (params: {
|
|
3250
|
-
data:
|
|
3760
|
+
data: $Value
|
|
3251
3761
|
parentData: object | unknown[]
|
|
3252
3762
|
rootData: object | unknown[]
|
|
3253
3763
|
dataPath: string
|
|
@@ -3260,7 +3770,7 @@ export type Schema<T = any> = JSONSchemaType<T> & {
|
|
|
3260
3770
|
|
|
3261
3771
|
// keywords/_validate.js
|
|
3262
3772
|
validateAsync?: (params: {
|
|
3263
|
-
data:
|
|
3773
|
+
data: $Value
|
|
3264
3774
|
parentData: object | unknown[]
|
|
3265
3775
|
rootData: object | unknown[]
|
|
3266
3776
|
dataPath: string
|