@ditojs/server 2.86.0 → 2.88.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/types/index.d.ts CHANGED
@@ -20,6 +20,7 @@ import { CompressOptions } from 'koa-compress'
20
20
  import koaMount from 'koa-mount'
21
21
  import koaResponseTime from 'koa-response-time'
22
22
  import koaSession from 'koa-session'
23
+ import multer from '@koa/multer'
23
24
  import multerS3 from 'multer-s3'
24
25
  import * as objection from 'objection'
25
26
  import { KnexSnakeCaseMappersFactory } from 'objection'
@@ -39,6 +40,16 @@ export type Page<$Model extends Model = Model> = {
39
40
  results: $Model[]
40
41
  }
41
42
 
43
+ /** Result of compiling action parameter definitions. */
44
+ export type CompiledParametersValidator = {
45
+ list: Array<{ name: string | null } & Schema>
46
+ schema: Schema | null
47
+ asObject: boolean
48
+ dataName: string
49
+ validate: ((data: unknown) => boolean | PromiseLike<boolean>) | null
50
+ hasModelRefs: boolean
51
+ }
52
+
42
53
  export type ApplicationConfig = {
43
54
  /** @defaultValue `production` */
44
55
  env?: 'production' | 'development'
@@ -101,7 +112,7 @@ export type ApplicationConfig = {
101
112
  * Whether to normalize paths from camel case to kebab case.
102
113
  *
103
114
  * @defaultValue `false`
104
- * @see {@link https://github.com/ditojs/dito/blob/master/docs/controllers.md#path-normalization|Path Normalization}
115
+ * @see {@link https://github.com/ditojs/dito/blob/main/docs/controllers.md#path-normalization|Path Normalization}
105
116
  */
106
117
  normalizePaths?: boolean
107
118
  /**
@@ -126,7 +137,7 @@ export type ApplicationConfig = {
126
137
  */
127
138
  helmet?: boolean | Parameters<typeof helmet>[0]
128
139
  logger?: {
129
- prettyPrint: PrettyOptions
140
+ prettyPrint?: PrettyOptions
130
141
  } & PinoLoggerOptions
131
142
  /**
132
143
  * Configure body parser.
@@ -179,21 +190,36 @@ export type ApplicationConfig = {
179
190
  // See https://github.com/brianc/node-pg-types/blob/master/index.d.ts#L67
180
191
  typeParsers?: Record<number, <I extends string | Buffer>(value: I) => any>
181
192
  }
182
- /** Service configurations. Pass `false` as a value to disable a service. */
183
- services?: Services
193
+ /**
194
+ * Service configurations keyed by service name. Pass
195
+ * `false` as a value to disable a service.
196
+ */
197
+ services?: Record<string, Record<string, unknown> | false>
184
198
  storages?: StorageConfigs
199
+ /** Logger configuration at the application level. */
200
+ logger?: {
201
+ prettyPrint?: PrettyOptions
202
+ } & PinoLoggerOptions
185
203
  assets?: {
186
204
  /**
187
- * Threshold after which unused assets that haven't seen changes for given
188
- * timeframe are removed.
205
+ * Threshold after which unused assets that haven't
206
+ * seen changes for given timeframe are removed.
189
207
  *
190
- * @example
191
- * '1 hr 20 mins'
192
- *
193
- * @default `0`
208
+ * @example '1 hr 20 mins'
209
+ * @defaultValue `'24h'`
194
210
  * @see https://www.npmjs.com/package/parse-duration
195
211
  */
196
212
  cleanupTimeThreshold?: string | number
213
+ /**
214
+ * Threshold after which dangling assets (uploaded
215
+ * but never persisted) are removed. Cannot be set to
216
+ * 0 as the file would be deleted immediately after
217
+ * upload.
218
+ *
219
+ * @defaultValue `'24h'`
220
+ * @see https://www.npmjs.com/package/parse-duration
221
+ */
222
+ danglingTimeThreshold?: string | number
197
223
  }
198
224
  }
199
225
 
@@ -205,14 +231,14 @@ export type MulterS3File = {
205
231
  contentDisposition: null
206
232
  storageClass: string
207
233
  serverSideEncryption: null
208
- metadata: any
234
+ metadata: Record<string, string>
209
235
  location: string
210
236
  etag: string
211
237
  }
212
238
 
213
239
  export type StorageConfigs = { [key: string]: StorageConfig }
214
240
 
215
- type CommonStorageConfig = {
241
+ interface CommonStorageConfig {
216
242
  /**
217
243
  * The concurrency at which assets are added to storage.
218
244
  *
@@ -236,7 +262,7 @@ export type S3StorageConfig = CommonStorageConfig & {
236
262
  's3' | 'key' | 'contentType' | 'metadata'
237
263
  >
238
264
 
239
- export type DiskStorageConfig = CommonStorageConfig & {
265
+ export interface DiskStorageConfig extends CommonStorageConfig {
240
266
  type: 'disk'
241
267
  /**
242
268
  * The path to the directory where assets are stored on.
@@ -300,7 +326,7 @@ export interface ApiConfig {
300
326
  * prefer to use another path normalization algorithm, they can be defined the
301
327
  * api settings passed to the DitoAdmin constructor.
302
328
  *
303
- * @default Defaults to Application.config.app.normalizePaths and then
329
+ * @defaultValue Application.config.app.normalizePaths
304
330
  */
305
331
  normalizePaths?: boolean
306
332
  /** Auth resources */
@@ -329,9 +355,8 @@ export interface ApiConfig {
329
355
  resources?: Record<string, (resource: ApiResource | string) => string>
330
356
 
331
357
  /**
332
- * Optionally override / extend headers
333
- *
334
- * @defaultValue `
358
+ * Optionally override / extend headers sent with API
359
+ * requests.
335
360
  */
336
361
  headers?: Record<string, string>
337
362
  }
@@ -355,41 +380,261 @@ export class Application<$Models extends Models = Models> {
355
380
  basePath?: string
356
381
  config?: ApplicationConfig
357
382
  validator?: Validator
358
- // TODO: router types
359
- router?: any
383
+ router?: {
384
+ add(
385
+ method: string,
386
+ path: string,
387
+ handler: Function
388
+ ): this
389
+ find(
390
+ method: string,
391
+ path: string
392
+ ): {
393
+ status: number
394
+ handler?: Function
395
+ params?: Record<string, string>
396
+ allowed: string[]
397
+ }
398
+ getAllowedMethods(
399
+ path?: string | null,
400
+ exclude?: string | null
401
+ ): string[]
402
+ normalizePath(path: string): string
403
+ }
360
404
  /**
361
405
  * Subscribe to application events. Event names: `'before:start'`,
362
406
  * `'after:start'`, `'before:stop'`, `'after:stop'`, `'error'`
363
407
  */
364
- events?: Record<string, (this: Application<$Models>, ...args: []) => void>
365
- models: $Models
408
+ events?: Record<
409
+ string,
410
+ (this: Application<$Models>, ...args: any[]) => void
411
+ >
412
+ models?: $Models
366
413
  controllers?: ApplicationControllers
367
- // TODO: services docs
414
+ /** Service classes or instances to register. */
368
415
  services?: Services
369
416
  middleware?: Koa.Middleware
370
417
  })
371
418
 
419
+ /** The base path for resolving relative paths. */
420
+ basePath: string
421
+ /** The merged application configuration. */
422
+ config: ApplicationConfig
423
+ /** The Knex instance for database access. */
424
+ knex: Knex
425
+ /** The HTTP server instance, or `null` if not started. */
426
+ server: import('http').Server | null
427
+ /** Whether the application is currently running. */
428
+ isRunning: boolean
429
+ /** The schema validator instance. */
430
+ validator: Validator
431
+ /** Registered storage instances by name. */
432
+ storages: Record<string, Storage>
433
+ /** Registered service instances by name. */
434
+ services: Record<string, Service>
435
+ /** Registered controller instances by name. */
436
+ controllers: Record<string, Controller>
372
437
  models: $Models
438
+
373
439
  setup(): Promise<void>
440
+ /** Calls `start()` and exits the process on failure. */
374
441
  execute(): Promise<void>
375
442
  start(): Promise<void>
376
443
  stop(timeout?: number): Promise<void>
377
- addStorage(storage: StorageConfig): void
444
+
445
+ /** Configures the pino logger instance. */
446
+ setupLogger(): void
447
+ /** Configures the Knex database connection. */
448
+ setupKnex(): void
449
+ /**
450
+ * Configures all Koa middleware (logger, error
451
+ * handling, CORS, compression, session, passport,
452
+ * etc.).
453
+ */
454
+ setupMiddleware(middleware?: Koa.Middleware): void
455
+
456
+ addStorage(
457
+ config: StorageConfig | Storage,
458
+ name?: string
459
+ ): Storage
460
+
378
461
  addStorages(storages: StorageConfigs): void
379
462
  setupStorages(): Promise<void>
380
- addService(service: Service): void
463
+ /** Returns a storage by name, or `null` if not found. */
464
+ getStorage(name: string): Storage | null
465
+
466
+ addService(
467
+ service: Service | Class<Service>,
468
+ name?: string
469
+ ): void
470
+
381
471
  addServices(services: Services): void
382
472
  setupServices(): Promise<void>
473
+ /** Returns a service by name. */
474
+ getService(name: string): Service | null
475
+ /** Finds a service matching the given predicate. */
476
+ findService(
477
+ callback: (service: Service) => boolean
478
+ ): Service | null
479
+
383
480
  addModel(model: Class<Model>): void
384
481
  addModels(models: Models): void
385
482
  setupModels(): Promise<void>
386
- addController(controllers: Controller, namespace?: string): void
387
- addControllers(controllers: ApplicationControllers, namespace?: string): void
483
+ /**
484
+ * Returns a model class by name. Also looks up
485
+ * `${name}Model` if the name doesn't already end in
486
+ * 'Model'.
487
+ */
488
+ getModel(name: string): Class<Model> | null
489
+ /** Finds a model class matching the given predicate. */
490
+ findModel(
491
+ callback: (model: Class<Model>) => boolean
492
+ ): Class<Model> | null
493
+
494
+ addController(
495
+ controller: Controller | Class<Controller>,
496
+ namespace?: string
497
+ ): void
498
+
499
+ addControllers(
500
+ controllers: ApplicationControllers,
501
+ namespace?: string
502
+ ): void
503
+
388
504
  setupControllers(): Promise<void>
389
- defineAdminViteConfig(config?: UserConfig): UserConfig
505
+ /** Returns a controller by its URL. */
506
+ getController(url: string): Controller | null
507
+ /**
508
+ * Finds a controller matching the given predicate.
509
+ */
510
+ findController(
511
+ callback: (controller: Controller) => boolean
512
+ ): Controller | null
513
+
514
+ /** Returns the admin controller, if registered. */
515
+ getAdminController(): AdminController | null
516
+
517
+ /** Compiles a JSON schema into a validation function. */
518
+ compileValidator(
519
+ jsonSchema: Schema,
520
+ options?: Record<string, any>
521
+ ): Ajv.ValidateFunction | null
522
+
523
+ /**
524
+ * Compiles action parameter definitions into a
525
+ * validation function and metadata.
526
+ */
527
+ compileParametersValidator(
528
+ parameters:
529
+ | Record<string, Schema>
530
+ | Array<{ name?: string } & Schema>,
531
+ options?: Record<string, any>
532
+ ): CompiledParametersValidator
533
+
534
+ /** Creates a ValidationError from raw error data. */
535
+ createValidationError(error: {
536
+ type: string
537
+ message?: string
538
+ errors: Ajv.ErrorObject[]
539
+ options?: Record<string, any>
540
+ json?: any
541
+ }): ValidationError
542
+
543
+ /** Creates a DatabaseError from a native DB error. */
544
+ createDatabaseError(error: dbErrors.DBError): DatabaseError
545
+
546
+ /**
547
+ * Wraps a database identifier through Knex's
548
+ * `wrapIdentifier` (e.g. camelCase to snake_case).
549
+ */
550
+ normalizeIdentifier(identifier: string): string
551
+ /**
552
+ * Reverses a database identifier through Knex's
553
+ * `postProcessResponse` (e.g. snake_case to camelCase).
554
+ */
555
+ denormalizeIdentifier(identifier: string): string
556
+ /** Normalizes a URL path. */
557
+ normalizePath(path: string): string
558
+
559
+ /** Formats an error for logging/response. */
560
+ formatError(error: Error | ResponseError): unknown
561
+
562
+ /** Logs an error to the application logger. */
563
+ logError(error: Error | ResponseError, ctx?: KoaContext): void
564
+ /** Releases unused assets past the cleanup threshold. */
565
+ releaseUnusedAssets(options?: {
566
+ timeThreshold?: string | number | null
567
+ transaction?: objection.Transaction | null
568
+ concurrency?: number
569
+ }): Promise<Model[] | undefined>
570
+
571
+ /**
572
+ * Creates Asset model records for the given files.
573
+ * Returns inserted assets, or `null` if no AssetModel
574
+ * is registered.
575
+ */
576
+ createAssets(
577
+ storage: Storage,
578
+ files: AssetFile[],
579
+ count?: number,
580
+ transaction?: objection.Transaction | null
581
+ ): Promise<Model[] | null>
582
+
583
+ /**
584
+ * Handles added, removed, and changed asset files.
585
+ * Imports foreign assets, updates counts, and schedules
586
+ * cleanup. Returns imported files when an AssetModel is
587
+ * registered.
588
+ */
589
+ handleAddedAndRemovedAssets(
590
+ storage: Storage,
591
+ addedFiles: AssetFile[],
592
+ removedFiles: AssetFile[],
593
+ changedFiles: AssetFile[],
594
+ transaction?: objection.Transaction | null
595
+ ): Promise<AssetFile[] | undefined>
596
+
597
+ /**
598
+ * Finds and imports missing assets from external
599
+ * sources (data URIs, file:// URLs, or HTTP URLs).
600
+ * Returns the list of imported files.
601
+ */
602
+ addForeignAssets(
603
+ storage: Storage,
604
+ files: AssetFile[],
605
+ transaction?: objection.Transaction | null
606
+ ): Promise<AssetFile[]>
607
+
608
+ /**
609
+ * Handles modifications to existing asset files by
610
+ * updating their stored data. Returns the list of
611
+ * modified files.
612
+ */
613
+ handleModifiedAssets(
614
+ storage: Storage,
615
+ files: AssetFile[],
616
+ transaction?: objection.Transaction | null
617
+ ): Promise<AssetFile[]>
618
+
619
+ addRoute(
620
+ method: HTTPMethod,
621
+ path: string,
622
+ transacted: boolean,
623
+ middlewares: OrArrayOf<(ctx: KoaContext, next: Function) => void>,
624
+ controller?: Controller | null,
625
+ action?: any
626
+ ): void
627
+
628
+ loadAdminViteConfig(): Promise<UserConfig | null>
629
+ getAssetConfig(options?: {
630
+ models?: string[]
631
+ normalizeDbNames?: boolean
632
+ }): Record<string, Record<string, Record<string, any>>>
633
+
634
+ defineAdminViteConfig(config?: UserConfig): UserConfig | null
390
635
  logger: PinoLogger
391
636
  requestStorage: AsyncLocalStorage<AsyncRequestLocals>
392
- requestLocals: AsyncRequestLocals
637
+ requestLocals: Partial<AsyncRequestLocals>
393
638
  }
394
639
 
395
640
  export interface Application
@@ -428,7 +673,7 @@ export interface ModelRelation {
428
673
  /**
429
674
  * The type of relation
430
675
  *
431
- * @see {@link https://github.com/ditojs/dito/blob/master/docs/model-relations.md#relation-types|Relation Types}
676
+ * @see {@link https://github.com/ditojs/dito/blob/main/docs/model-relations.md#relation-types|Relation Types}
432
677
  */
433
678
  relation: LiteralUnion<
434
679
  'belongsTo' | 'hasMany' | 'hasOne' | 'manyToMany' | 'hasOneThrough'
@@ -449,7 +694,7 @@ export interface ModelRelation {
449
694
  * When set to true the join model class and table is to be built
450
695
  * automatically, or allows to specify an existing one manually.
451
696
  *
452
- * @see {@link https://github.com/ditojs/dito/blob/master/docs/model-relations.md#join-models-and-tables|Join Models and Tables}
697
+ * @see {@link https://github.com/ditojs/dito/blob/main/docs/model-relations.md#join-models-and-tables|Join Models and Tables}
453
698
  */
454
699
  through?:
455
700
  | boolean
@@ -494,12 +739,15 @@ export interface ModelRelation {
494
739
  */
495
740
  scope?: string
496
741
  /**
497
- * Optionally, a filter can be defined to be applied when loading the
498
- * relation's models. The filter needs to be defined in the related model
499
- * class' filters definitions. Can be a single filter name or an object with
500
- * filter names as keys and argument arrays as values.
742
+ * Optionally, a filter to apply when loading the relation's models.
743
+ * Accepts a Dito.js filter name/object (resolved via the related model's
744
+ * filters), or a callback/find-filter object as an alias for `modify`.
501
745
  */
502
- filter?: string | { [name: string]: any[] }
746
+ filter?:
747
+ | string
748
+ | { [name: string]: unknown[] }
749
+ | ((query: QueryBuilder) => void)
750
+ | Record<string, unknown>
503
751
  /**
504
752
  * Controls whether the auto-inserted foreign key property should be marked as
505
753
  * nullable. This only makes sense on a 'belongsTo' relation, where the model
@@ -513,6 +761,16 @@ export interface ModelRelation {
513
761
  * their owner.
514
762
  */
515
763
  owner?: boolean
764
+ /**
765
+ * An optional modify callback or find-filter object to scope the relation
766
+ * query. This is the Objection.js-native way to modify relation queries.
767
+ *
768
+ * As a function: `modify: query => query.where('active', true)`
769
+ * As an object: `modify: { active: true }` (converted to a find-filter)
770
+ */
771
+ modify?:
772
+ | ((query: QueryBuilder) => void)
773
+ | Record<string, unknown>
516
774
  }
517
775
 
518
776
  export type ModelProperty<T = any> = Schema<T> & {
@@ -544,8 +802,9 @@ export type ModelProperty<T = any> = Schema<T> & {
544
802
  */
545
803
  unique?: boolean | string
546
804
  /**
547
- * Marks the column for a property of type 'integer' to be unsigned in the
548
- * migrations, by calling the .index() method.calling the .unsigned() method.
805
+ * Marks the column for a property of type 'integer' to be
806
+ * unsigned in the migrations, by calling the .unsigned()
807
+ * method.
549
808
  */
550
809
  unsigned?: boolean
551
810
  /**
@@ -567,45 +826,90 @@ export type ModelProperty<T = any> = Schema<T> & {
567
826
  hidden?: boolean
568
827
  }
569
828
 
829
+ /**
830
+ * A scope function that modifies a query builder in-place
831
+ * to apply filtering or ordering logic. The return value
832
+ * is ignored at runtime.
833
+ *
834
+ * @see {@link https://github.com/ditojs/dito/blob/main/docs/model-scopes.md|Model Scopes}
835
+ */
570
836
  export type ModelScope<$Model extends Model = Model> = (
571
- this: $Model,
572
837
  query: QueryBuilder<$Model>,
573
838
  applyParentScope: (query: QueryBuilder<$Model>) => QueryBuilder<$Model>
574
839
  ) => QueryBuilder<$Model, any> | void
575
840
 
841
+ /**
842
+ * Map of scope names to scope functions. Scopes can be
843
+ * applied via `withScope()` on queries or set as defaults
844
+ * on controllers.
845
+ *
846
+ * @see {@link https://github.com/ditojs/dito/blob/main/docs/model-scopes.md|Model Scopes}
847
+ */
576
848
  export type ModelScopes<$Model extends Model = Model> = Record<
577
849
  string,
578
850
  ModelScope<$Model>
579
851
  >
580
852
 
853
+ /**
854
+ * A filter handler function that modifies a query builder
855
+ * based on external parameters (e.g. from URL query strings).
856
+ */
581
857
  export type ModelFilterFunction<$Model extends Model = Model> = (
582
858
  queryBuilder: QueryBuilder<$Model>,
583
859
  ...args: any[]
584
860
  ) => void
585
861
 
862
+ /**
863
+ * A model filter definition. Can be one of:
864
+ *
865
+ * - A **built-in filter** reference (`{ filter: 'text' }` or
866
+ * `{ filter: 'date-range' }`) with optional `properties`.
867
+ * - A **custom filter** with a `handler` function, optional
868
+ * `parameters` schema for validation, and optional
869
+ * `response` schema.
870
+ * - A bare **handler function** as shorthand.
871
+ *
872
+ * @see {@link https://github.com/ditojs/dito/blob/main/docs/model-filters.md|Model Filters}
873
+ */
586
874
  export type ModelFilter<$Model extends Model = Model> =
587
875
  | {
588
- filter: 'text' | 'date-range'
876
+ filter: LiteralUnion<'text' | 'date-range'>
589
877
  properties?: string[]
590
878
  }
591
879
  | {
592
880
  handler: ModelFilterFunction<$Model>
593
881
  parameters?: { [key: string]: Schema }
882
+ response?: Schema
594
883
  // TODO: validate type
595
884
  validate?: any
596
885
  }
597
886
  | ModelFilterFunction<$Model>
598
887
 
888
+ /**
889
+ * Map of filter names to filter definitions.
890
+ *
891
+ * @see {@link https://github.com/ditojs/dito/blob/main/docs/model-filters.md|Model Filters}
892
+ */
599
893
  export type ModelFilters<$Model extends Model = Model> = Record<
600
894
  string,
601
895
  ModelFilter<$Model>
602
896
  >
603
897
 
898
+ /**
899
+ * Configuration for a model asset property, linking it to a
900
+ * named storage backend.
901
+ */
604
902
  export interface ModelAsset {
903
+ /** The name of the storage backend to use. */
605
904
  storage: string
905
+ /**
906
+ * Whether to read image dimensions (width/height) on
907
+ * upload.
908
+ */
606
909
  readDimensions?: boolean
607
910
  }
608
911
 
912
+ /** Map of property names to their asset configurations. */
609
913
  export type ModelAssets = Record<string, ModelAsset>
610
914
 
611
915
  export interface ModelOptions extends objection.ModelOptions {
@@ -618,6 +922,23 @@ type ModelHookFunction<$Model extends Model> = (
618
922
  args: objection.StaticHookArguments<$Model>
619
923
  ) => void
620
924
 
925
+ /**
926
+ * Map of lifecycle hook names to handler functions. Hook
927
+ * names follow the pattern `'before:operation'` or
928
+ * `'after:operation'` where operation is `find`, `insert`,
929
+ * `update`, or `delete`.
930
+ *
931
+ * @example
932
+ * ```ts
933
+ * static hooks: ModelHooks<MyModel> = {
934
+ * 'before:insert': ({ items }) => {
935
+ * for (const item of items) {
936
+ * item.createdAt = new Date()
937
+ * }
938
+ * }
939
+ * }
940
+ * ```
941
+ */
621
942
  export type ModelHooks<$Model extends Model = Model> = {
622
943
  [key in `${'before' | 'after'}:${
623
944
  | 'find'
@@ -628,64 +949,448 @@ export type ModelHooks<$Model extends Model = Model> = {
628
949
  }
629
950
 
630
951
  export class Model extends objection.Model {
631
- /** @see {@link https://github.com/ditojs/dito/blob/master/docs/model-properties.md|Model Properties} */
952
+ constructor(json?: Record<string, any>)
953
+
954
+ /** @see {@link https://github.com/ditojs/dito/blob/main/docs/model-properties.md|Model Properties} */
632
955
  static properties: ModelProperties
633
956
 
634
- /** @see {@link https://github.com/ditojs/dito/blob/master/docs/model-relations.md|Model Relations} */
957
+ /** @see {@link https://github.com/ditojs/dito/blob/main/docs/model-relations.md|Model Relations} */
635
958
  static relations: ModelRelations
636
959
 
637
- /** @see {@link https://github.com/ditojs/dito/blob/master/docs/model-scopes.md|Model Scopes} */
960
+ /** @see {@link https://github.com/ditojs/dito/blob/main/docs/model-scopes.md|Model Scopes} */
638
961
  static scopes: ModelScopes<Model>
639
962
 
640
- /** @see {@link https://github.com/ditojs/dito/blob/master/docs/model-filters.md|Model Filters} */
963
+ /** @see {@link https://github.com/ditojs/dito/blob/main/docs/model-filters.md|Model Filters} */
641
964
  static filters: ModelFilters<Model>
642
965
 
643
966
  static hooks: ModelHooks<Model>
644
967
 
645
968
  static assets: ModelAssets
646
969
 
647
- static getPropertyOrRelationAtDataPath: (dataPath: OrArrayOf<string>) => any
970
+ /** The merged definition object, assembled from the class hierarchy. */
971
+ static get definition(): {
972
+ properties: ModelProperties
973
+ relations: ModelRelations
974
+ scopes: ModelScopes
975
+ filters: ModelFilters
976
+ hooks: ModelHooks
977
+ assets: ModelAssets
978
+ options: Record<string, any>
979
+ modifiers: Record<
980
+ string,
981
+ (
982
+ builder: objection.QueryBuilder<any, any>,
983
+ ...args: any[]
984
+ ) => void
985
+ >
986
+ schema: Record<string, any>
987
+ [key: string]: any
988
+ }
989
+
990
+ /** Derived from class name (removes 'Model' suffix). */
991
+ static get tableName(): string
992
+ /** Returns the primary key column(s). */
993
+ static get idColumn(): string | string[]
994
+ /**
995
+ * Returns the cached converted relation mappings for
996
+ * the model.
997
+ */
998
+ static get relationMappings(): Record<
999
+ string,
1000
+ objection.RelationMapping<Model>
1001
+ >
1002
+
1003
+ /** Returns the cached converted JSON schema. */
1004
+ static get jsonSchema(): Record<string, any>
1005
+ /** Aliases `computedAttributes`. */
1006
+ static get virtualAttributes(): string[]
1007
+
1008
+ static get jsonAttributes(): string[]
1009
+ static get booleanAttributes(): string[]
1010
+ static get dateAttributes(): string[]
1011
+ static get computedAttributes(): string[]
1012
+ static get hiddenAttributes(): string[]
1013
+
1014
+ /** The application instance this model is registered with. */
1015
+ static app: Application<Models>
1016
+ /** Whether the model has been initialized by the application. */
1017
+ static initialized: boolean
1018
+ /** The QueryBuilder class used by this model. */
1019
+ static QueryBuilder: typeof QueryBuilder
1020
+ /** Whether to deep-clone object attributes on read. */
1021
+ static cloneObjectAttributes: boolean
1022
+ /**
1023
+ * Only pick properties defined in jsonSchema
1024
+ * for database JSON.
1025
+ */
1026
+ static pickJsonSchemaProperties: boolean
1027
+ /** Whether to use LIMIT 1 in first() queries. */
1028
+ static useLimitInFirst: boolean
1029
+
1030
+ /** Called by the application during model registration. */
1031
+ static configure(app: Application<Models>): void
1032
+ /** Sets up model schema, relations, and scopes. */
1033
+ static setup(): void
1034
+ /**
1035
+ * Async initialization hook. Override in subclasses for
1036
+ * setup that requires async operations.
1037
+ * @overridable
1038
+ */
1039
+ static initialize(): void | Promise<void>
1040
+
1041
+ /**
1042
+ * Creates a model instance from JSON data. Dito's
1043
+ * override adds async validation support: returns a
1044
+ * Promise when `options.async` is true.
1045
+ * @override
1046
+ */
1047
+ static fromJson<M extends Model>(
1048
+ this: Constructor<M>,
1049
+ json: Record<string, any>,
1050
+ options?: ModelOptions
1051
+ ): M | Promise<M>
1052
+
1053
+ /** Returns the named scope function, if defined. */
1054
+ static getScope(name: string): ModelScope | undefined
1055
+ /** Returns whether the model has the named scope. */
1056
+ static hasScope(name: string): boolean
1057
+ /**
1058
+ * Creates a reference instance containing only the
1059
+ * identifier properties.
1060
+ */
1061
+ static getReference(
1062
+ modelOrId: Model | Id,
1063
+ includeProperties?: string[]
1064
+ ): Model
1065
+
1066
+ /** Returns whether the given value is a model reference. */
1067
+ static isReference(obj: unknown): boolean
1068
+ /** Returns the named property definition, if found. */
1069
+ static getProperty(name: string): ModelProperty | null
1070
+ /** Returns the model's query modifiers. */
1071
+ static getModifiers(): Record<
1072
+ string,
1073
+ (
1074
+ builder: objection.QueryBuilder<any, any>,
1075
+ ...args: any[]
1076
+ ) => void
1077
+ >
1078
+
1079
+ /**
1080
+ * Returns property names matching the given filter
1081
+ * function.
1082
+ */
1083
+ static getAttributes(
1084
+ filter: (property: ModelProperty) => boolean
1085
+ ): string[]
1086
+
1087
+ /** Returns relations where this model is the related side. */
1088
+ static getRelatedRelations(): objection.Relation[]
1089
+
1090
+ /**
1091
+ * Maps property names to column names (identity
1092
+ * function — naming is handled at Knex level).
1093
+ * @override
1094
+ */
1095
+ static propertyNameToColumnName(
1096
+ propertyName: string
1097
+ ): string
1098
+
1099
+ /**
1100
+ * Maps column names to property names (identity
1101
+ * function — naming is handled at Knex level).
1102
+ * @override
1103
+ */
1104
+ static columnNameToPropertyName(
1105
+ columnName: string
1106
+ ): string
1107
+
1108
+ /**
1109
+ * Handles modifiers not found directly on the model by
1110
+ * checking scopes and special prefixes.
1111
+ * @override
1112
+ */
1113
+ static modifierNotFound(
1114
+ query: QueryBuilder<Model>,
1115
+ modifier: string | Function
1116
+ ): void
1117
+
1118
+ /**
1119
+ * Creates a NotFoundError with model context.
1120
+ * @override
1121
+ */
1122
+ static createNotFoundError(
1123
+ ctx: Record<string, any>,
1124
+ error?: Error
1125
+ ): NotFoundError
1126
+
1127
+ /**
1128
+ * Returns the shared application validator.
1129
+ * @override
1130
+ */
1131
+ static createValidator(): Validator
1132
+
1133
+ /**
1134
+ * Creates a typed validation or relation error from
1135
+ * raw error data.
1136
+ * @override
1137
+ */
1138
+ static createValidationError(error: {
1139
+ type: string
1140
+ message?: string
1141
+ errors: any[]
1142
+ options?: Record<string, any>
1143
+ json?: any
1144
+ }): ResponseError
1145
+
1146
+ /** Starts a new transaction. */
1147
+ static transaction(): Promise<objection.Transaction>
1148
+ /** Runs a callback within a transaction. */
1149
+ static transaction(
1150
+ handler: (trx: objection.Transaction) => Promise<any>
1151
+ ): Promise<any>
1152
+
1153
+ static transaction(
1154
+ trx: objection.Transaction,
1155
+ handler: (trx: objection.Transaction) => Promise<any>
1156
+ ): Promise<any>
1157
+
1158
+ static getPropertyOrRelationAtDataPath(
1159
+ dataPath: OrArrayOf<string>
1160
+ ): {
1161
+ property?: ModelProperty | null
1162
+ relation?: objection.Relation | null
1163
+ wildcard?: string | null
1164
+ dataPath?: string | null
1165
+ nestedDataPath?: string | null
1166
+ name?: string
1167
+ expression?: string | null
1168
+ }
1169
+
1170
+ /**
1171
+ * Creates a query builder for a relation. Unlike
1172
+ * Objection.js, Dito automatically aliases the query
1173
+ * to the relation name.
1174
+ * @override
1175
+ */
1176
+ static relatedQuery<M extends Model>(
1177
+ this: Constructor<M>,
1178
+ relationName: string,
1179
+ trx?: objection.Transaction
1180
+ ): QueryBuilder<M>
1181
+
1182
+ /**
1183
+ * Hook called before find queries.
1184
+ * @overridable
1185
+ */
1186
+ static beforeFind(
1187
+ args: objection.StaticHookArguments<Model>
1188
+ ): Promise<void>
1189
+
1190
+ /**
1191
+ * Hook called after find queries.
1192
+ * @overridable
1193
+ */
1194
+ static afterFind(
1195
+ args: objection.StaticHookArguments<Model>
1196
+ ): Promise<void>
1197
+
1198
+ /**
1199
+ * Hook called before insert queries.
1200
+ * @overridable
1201
+ */
1202
+ static beforeInsert(
1203
+ args: objection.StaticHookArguments<Model>
1204
+ ): Promise<void>
1205
+
1206
+ /**
1207
+ * Hook called after insert queries.
1208
+ * @overridable
1209
+ */
1210
+ static afterInsert(
1211
+ args: objection.StaticHookArguments<Model>
1212
+ ): Promise<void>
1213
+
1214
+ /**
1215
+ * Hook called before update queries.
1216
+ * @overridable
1217
+ */
1218
+ static beforeUpdate(
1219
+ args: objection.StaticHookArguments<Model>
1220
+ ): Promise<void>
1221
+
1222
+ /**
1223
+ * Hook called after update queries.
1224
+ * @overridable
1225
+ */
1226
+ static afterUpdate(
1227
+ args: objection.StaticHookArguments<Model>
1228
+ ): Promise<void>
1229
+
1230
+ /**
1231
+ * Hook called before delete queries.
1232
+ * @overridable
1233
+ */
1234
+ static beforeDelete(
1235
+ args: objection.StaticHookArguments<Model>
1236
+ ): Promise<void>
1237
+
1238
+ /**
1239
+ * Hook called after delete queries.
1240
+ * @overridable
1241
+ */
1242
+ static afterDelete(
1243
+ args: objection.StaticHookArguments<Model>
1244
+ ): Promise<void>
648
1245
 
649
1246
  static count: {
650
- (column?: objection.ColumnRef, options?: { as: string }): number
651
- (aliasToColumnDict: Record<string, string | string[]>): number
652
- (...columns: objection.ColumnRef[]): number
1247
+ (column?: objection.ColumnRef, options?: { as: string }): Promise<number>
1248
+ (aliasToColumnDict: Record<string, string | string[]>): Promise<number>
1249
+ (...columns: objection.ColumnRef[]): Promise<number>
653
1250
  }
654
1251
 
655
1252
  /**
656
- * Dito.js automatically adds an `id` property if a model property with the
657
- * `primary: true` setting is not already explicitly defined.
1253
+ * Filters a model graph using a relation expression,
1254
+ * removing any data not matching the expression.
1255
+ */
1256
+ static filterGraph(
1257
+ modelGraph: Model | Model[],
1258
+ expr: string | objection.RelationExpression<Model>
1259
+ ): Model | Model[]
1260
+
1261
+ /**
1262
+ * Populates a model graph by loading relations defined
1263
+ * in the given expression.
1264
+ */
1265
+ static populateGraph(
1266
+ modelGraph: Model | Model[],
1267
+ expr: string | objection.RelationExpression<Model>,
1268
+ trx?: objection.Transaction
1269
+ ): Promise<Model | Model[]>
1270
+
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.
658
1275
  */
659
1276
  readonly id: Id
660
1277
 
661
1278
  /**
662
- * Dito.js automatically adds a `foreignKeyId` property if foreign keys
663
- * occurring in relations definitions are not explicitly defined in the
664
- * properties.
1279
+ * Dito.js automatically adds a `foreignKeyId` property
1280
+ * if foreign keys occurring in relations definitions are
1281
+ * not explicitly defined in the properties.
665
1282
  */
666
1283
  readonly foreignKeyId: Id
667
1284
 
668
1285
  QueryBuilderType: QueryBuilder<this, this[]>
669
1286
 
670
- // Todo: include application settings
671
1287
  $app: Application<Models>
672
- $is(model: Model): boolean
1288
+ /**
1289
+ * Called after model construction. Override in subclasses
1290
+ * for custom instance initialization.
1291
+ * @overridable
1292
+ */
1293
+ $initialize(): void
1294
+ $is(model: Model | null | undefined): boolean
1295
+ /** Returns `true` if all named properties are defined. */
1296
+ $has(...properties: string[]): boolean
1297
+ /** Runs a callback within a transaction. */
1298
+ $transaction(
1299
+ handler: (trx: objection.Transaction) => Promise<any>
1300
+ ): Promise<any>
1301
+
1302
+ $transaction(
1303
+ trx: objection.Transaction,
1304
+ handler: (trx: objection.Transaction) => Promise<any>
1305
+ ): Promise<any>
1306
+
1307
+ /** Emits an event on this model instance. */
1308
+ $emit(event: string, ...args: any[]): Promise<any[]>
1309
+ /**
1310
+ * Filters a model graph using a relation expression,
1311
+ * removing data not matching the expression.
1312
+ */
1313
+ $filterGraph(
1314
+ modelGraph: Model | Model[],
1315
+ expr: string | objection.RelationExpression<Model>
1316
+ ): Model | Model[]
1317
+
1318
+ /**
1319
+ * Populates a model graph by loading relations defined
1320
+ * in the given expression.
1321
+ */
1322
+ $populateGraph(
1323
+ modelGraph: Model | Model[],
1324
+ expr: string | objection.RelationExpression<Model>,
1325
+ trx?: objection.Transaction
1326
+ ): Promise<Model | Model[]>
1327
+
673
1328
  $update(
674
- attributes: Partial<ExtractModelProperties<this>>,
1329
+ attributes: Partial<ModelDataProperties<this>>,
675
1330
  trx?: objection.Transaction
676
1331
  ): objection.SingleQueryBuilder<objection.QueryBuilderType<this>>
677
1332
 
678
1333
  $patch(
679
- attributes: Partial<ExtractModelProperties<this>>,
1334
+ attributes: Partial<ModelDataProperties<this>>,
680
1335
  trx?: objection.Transaction
681
1336
  ): objection.SingleQueryBuilder<objection.QueryBuilderType<this>>
682
1337
 
683
1338
  $validate<$JSON extends null | {}>(
684
1339
  json?: $JSON,
685
1340
  options?: ModelOptions & Record<string, any>
686
- ): Promise<$JSON | this>
1341
+ ): ($JSON | this) | Promise<$JSON | this>
1342
+
1343
+ $validateGraph(
1344
+ options: ModelOptions & Record<string, any>
1345
+ ): Promise<this>
1346
+
1347
+ /**
1348
+ * Sets JSON data on the model instance. Triggers
1349
+ * `$initialize()` when appropriate.
1350
+ * @override
1351
+ */
1352
+ $setJson(
1353
+ json: Record<string, any>,
1354
+ options?: ModelOptions
1355
+ ): this
1356
+
1357
+ /**
1358
+ * Formats data for database storage. Converts dates to
1359
+ * ISO strings, handles boolean conversion for SQLite,
1360
+ * and removes computed properties.
1361
+ * @override @overridable
1362
+ */
1363
+ $formatDatabaseJson(
1364
+ json: Record<string, any>
1365
+ ): Record<string, any>
1366
+
1367
+ /**
1368
+ * Parses data from the database. Converts SQLite
1369
+ * booleans back and delegates to `$parseJson()` for
1370
+ * date and AssetFile handling.
1371
+ * @override @overridable
1372
+ */
1373
+ $parseDatabaseJson(
1374
+ json: Record<string, any>
1375
+ ): Record<string, any>
687
1376
 
688
- $validateGraph(options: ModelOptions & Record<string, any>): Promise<this>
1377
+ /**
1378
+ * Parses general JSON data. Converts date strings to
1379
+ * Date objects and handles asset file conversion.
1380
+ * @override @overridable
1381
+ */
1382
+ $parseJson(
1383
+ json: Record<string, any>
1384
+ ): Record<string, any>
1385
+
1386
+ /**
1387
+ * Formats data for API output. Removes hidden
1388
+ * attributes from the JSON representation.
1389
+ * @override @overridable
1390
+ */
1391
+ $formatJson(
1392
+ json: Record<string, any>
1393
+ ): Record<string, any>
689
1394
 
690
1395
  /* -------------------- Start QueryBuilder.mixin(Model) ------------------- */
691
1396
  static first: StaticQueryBuilderMethod<'first'>
@@ -708,13 +1413,15 @@ export class Model extends objection.Model {
708
1413
  static insert: StaticQueryBuilderMethod<'insert'>
709
1414
  static upsert: StaticQueryBuilderMethod<'upsert'>
710
1415
  static update: StaticQueryBuilderMethod<'update'>
711
- static relate: StaticQueryBuilderMethod<'relate'>
712
1416
  static patch: StaticQueryBuilderMethod<'patch'>
713
-
714
- static truncate: StaticQueryBuilderMethod<'truncate'>
715
1417
  static delete: StaticQueryBuilderMethod<'delete'>
1418
+
1419
+ static updateById: StaticQueryBuilderMethod<'updateById'>
1420
+ static patchById: StaticQueryBuilderMethod<'patchById'>
716
1421
  static deleteById: StaticQueryBuilderMethod<'deleteById'>
717
1422
 
1423
+ static truncate: StaticQueryBuilderMethod<'truncate'>
1424
+
718
1425
  static insertAndFetch: StaticQueryBuilderMethod<'insertAndFetch'>
719
1426
  static upsertAndFetch: StaticQueryBuilderMethod<'upsertAndFetch'>
720
1427
  static updateAndFetch: StaticQueryBuilderMethod<'updateAndFetch'>
@@ -756,7 +1463,7 @@ export class Model extends objection.Model {
756
1463
  static whereNotColumn: StaticQueryBuilderMethod<'whereNotColumn'>
757
1464
  static whereComposite: StaticQueryBuilderMethod<'whereComposite'>
758
1465
  static whereInComposite: StaticQueryBuilderMethod<'whereInComposite'>
759
- static whereNotInComposite: StaticQueryBuilderMethod<'whereInComposite'> // TODO: `whereNotInComposite`
1466
+ static whereNotInComposite: StaticQueryBuilderMethod<'whereNotInComposite'>
760
1467
  static whereJsonHasAny: StaticQueryBuilderMethod<'whereJsonHasAny'>
761
1468
  static whereJsonHasAll: StaticQueryBuilderMethod<'whereJsonHasAll'>
762
1469
  static whereJsonIsArray: StaticQueryBuilderMethod<'whereJsonIsArray'>
@@ -803,6 +1510,13 @@ export type ModelRelations = Record<string, ModelRelation>
803
1510
 
804
1511
  export type ModelProperties = Record<string, ModelProperty>
805
1512
 
1513
+ /**
1514
+ * A controller action definition. Either an options object
1515
+ * with `handler`, `method`, `path`, `authorize`, etc., or a
1516
+ * bare handler function. The HTTP method and path are
1517
+ * derived from the action name key (e.g. `'postImport'`
1518
+ * maps to `POST /import`).
1519
+ */
806
1520
  export type ControllerAction<$Controller extends Controller = Controller> =
807
1521
  | ControllerActionOptions<$Controller>
808
1522
  | ControllerActionHandler<$Controller>
@@ -810,29 +1524,47 @@ export type ControllerAction<$Controller extends Controller = Controller> =
810
1524
  export class Controller {
811
1525
  app: Application
812
1526
  /**
813
- * Optionally provide the controller path. A default is deducted from the
814
- * normalized class name otherwise.
1527
+ * Optionally provide the controller path. A default is
1528
+ * deducted from the normalized class name otherwise.
815
1529
  */
816
1530
  path?: string
817
1531
  /**
818
- * The controller's name. If not provided, it is automatically deducted from
819
- * the controller class name. If this name ends in 'Controller', that is
820
- * stripped off the name, so 'GreetingsController' turns into 'Greetings'.
1532
+ * The controller's name. If not provided, it is
1533
+ * automatically deducted from the controller class name.
1534
+ * If this name ends in 'Controller', that is stripped
1535
+ * off the name, so 'GreetingsController' turns into
1536
+ * 'Greetings'.
821
1537
  */
822
1538
  name?: string
823
1539
  /**
824
- * The controller's namespace, which is prepended to path to generate the
825
- * absolute controller route. Note that it is rare to provide this manually.
826
- * Usually Dito.js determines the namespace automatically from the controller
827
- * object passed to the Dito.js application's constructor and its
828
- * sub-objects.
1540
+ * The controller's namespace, which is prepended to path
1541
+ * to generate the absolute controller route. Note that
1542
+ * it is rare to provide this manually. Usually Dito.js
1543
+ * determines the namespace automatically from the
1544
+ * controller object passed to the Dito.js application's
1545
+ * constructor and its sub-objects.
829
1546
  *
830
- * @see {@link https://github.com/ditojs/dito/blob/master/docs/controllers.md#namespaces Namespaces}
1547
+ * @see {@link https://github.com/ditojs/dito/blob/main/docs/controllers.md#namespaces Namespaces}
831
1548
  */
832
1549
  namespace?: string
1550
+ /** The fully resolved URL for this controller. */
1551
+ url?: string
1552
+ /**
1553
+ * Whether actions should be transacted by default.
1554
+ * Can be overridden per-action.
1555
+ */
1556
+ transacted?: boolean
1557
+ /** Whether this controller has been initialized. */
1558
+ initialized: boolean
1559
+ /** The nesting level of this controller. */
1560
+ level: number
1561
+ /** Whether to log routes during setup. */
1562
+ logRoutes: boolean
1563
+
833
1564
  /**
834
- * A list of allowed actions. If provided, only the action names listed here
835
- * as strings will be mapped to routes, everything else will be omitted.
1565
+ * A list of allowed actions. If provided, only the
1566
+ * action names listed here as strings will be mapped to
1567
+ * routes, everything else will be omitted.
836
1568
  */
837
1569
  allow?: OrReadOnly<ControllerActionName[]>
838
1570
 
@@ -840,9 +1572,28 @@ export class Controller {
840
1572
  authorize?: Authorize
841
1573
  actions?: ControllerActions<this>
842
1574
 
1575
+ /** Returns the logger, optionally scoped to a context. */
1576
+ getLogger(ctx?: KoaContext): PinoLogger
1577
+ /** The controller's logger instance. */
1578
+ get logger(): PinoLogger
1579
+ /**
1580
+ * Returns a member model for the request context.
1581
+ * No-op in the base class; override in subclasses.
1582
+ */
1583
+ getMember(
1584
+ ctx?: KoaContext,
1585
+ base?: any,
1586
+ options?: {
1587
+ query?: Record<string, any>
1588
+ modify?: (query: QueryBuilder<Model>) => QueryBuilder<Model>
1589
+ forUpdate?: boolean
1590
+ }
1591
+ ): Promise<Model | null>
1592
+
1593
+ setProperty(key: string, value: unknown): void
1594
+
843
1595
  /**
844
- * `configure()` is called right after the constructor, but before `setup()`
845
- * which sets up the actions and routes, and the custom `async initialize()`.
1596
+ * Called right after the constructor, before `setup()` and `initialize()`.
846
1597
  * @overridable
847
1598
  */
848
1599
  configure(): void
@@ -853,8 +1604,6 @@ export class Controller {
853
1604
  * @overridable
854
1605
  */
855
1606
  initialize(): Promise<void>
856
- // TODO: type reflectActionsObject
857
- reflectActionsObject(): any
858
1607
  /**
859
1608
  * @overridable
860
1609
  */
@@ -874,8 +1623,8 @@ export class Controller {
874
1623
  handlers: ((ctx: KoaContext, next: Function) => void)[]
875
1624
  ): void
876
1625
 
877
- setupActions(type: string): string[]
878
- setupActionRoute(type: any, action: any): void
1626
+ setupActions(type: string): any
1627
+ setupActionRoute(type: string, action: ControllerAction): void
879
1628
  setupAssets(): any
880
1629
  setupAssetRoute(
881
1630
  dataPath: OrArrayOf<string>,
@@ -892,55 +1641,88 @@ export class Controller {
892
1641
  /** To be overridden by sub-classes. */
893
1642
  getPath(type: string, path: string): string
894
1643
  getUrl(type: string, path: string): string
895
- inheritValues(type: string): any
896
- processValues(values: any): {
897
- // Create a filtered `values` object that only contains the allowed fields
898
- values: any
1644
+ inheritValues(type: string): Record<string, unknown> | undefined
1645
+ processValues(values: Record<string, unknown>): {
1646
+ values: Record<string, unknown>
899
1647
  allow: string[]
900
- authorize: Authorize
1648
+ authorize: Record<string, Authorize>
901
1649
  }
902
1650
 
903
1651
  emitHook(
904
1652
  type: string,
905
- handleResult: any,
906
- ctx: any,
1653
+ handleResult: boolean,
1654
+ ctx: KoaContext,
907
1655
  ...args: any[]
908
1656
  ): Promise<any>
909
1657
 
910
- processAuthorize(authorize: any): any
911
- describeAuthorize(authorize: any): string
912
- handleAuthorization(): Promise<void>
1658
+ processAuthorize(
1659
+ authorize: Authorize
1660
+ ): (ctx: KoaContext, member?: Model) => OrPromiseOf<boolean>
1661
+
1662
+ describeAuthorize(authorize: Authorize): string
1663
+ handleAuthorization(
1664
+ authorization: (
1665
+ ctx: KoaContext,
1666
+ member?: Model
1667
+ ) => OrPromiseOf<boolean>,
1668
+ ctx: KoaContext,
1669
+ member?: Model
1670
+ ): Promise<void>
913
1671
  }
914
1672
 
1673
+ export interface Controller extends EventEmitter {}
1674
+
1675
+ /** A named action parameter with a JSON Schema definition. */
915
1676
  export type ActionParameter = Schema & { name: string }
916
1677
 
1678
+ /**
1679
+ * Handler function for a model controller action. Receives
1680
+ * the Koa context and any resolved action parameters. `this`
1681
+ * is bound to the controller instance.
1682
+ */
917
1683
  export type ModelControllerActionHandler<
918
1684
  $ModelController extends ModelController = ModelController
919
1685
  > = (this: $ModelController, ctx: KoaContext, ...args: any[]) => any
920
1686
 
1687
+ /**
1688
+ * Handler function for a controller action. Receives the Koa
1689
+ * context and any resolved action parameters. `this` is
1690
+ * bound to the controller instance.
1691
+ */
921
1692
  export type ControllerActionHandler<
922
1693
  $Controller extends Controller = Controller
923
1694
  > = (this: $Controller, ctx: KoaContext, ...args: any[]) => any
924
1695
 
925
- export type ExtractModelProperties<$Model extends Model = Model> = {
926
- [K in SelectModelPropertyKeys<$Model>]: $Model[K] extends Model
927
- ? ExtractModelProperties<$Model[K]>
1696
+ type ModelDataKey<T, K extends keyof T> = K extends
1697
+ | 'QueryBuilderType'
1698
+ | 'foreignKeyId'
1699
+ | `$${string}`
1700
+ ? never
1701
+ : T[K] extends (...args: any[]) => any
1702
+ ? never
1703
+ : K
1704
+
1705
+ type ModelDataProperties<$Model extends Model = Model> = {
1706
+ [K in keyof $Model as ModelDataKey<$Model, K>]: $Model[K] extends Model
1707
+ ? ModelDataProperties<$Model[K]>
928
1708
  : $Model[K]
929
1709
  }
930
1710
 
931
- export type Extends<$A, $B> = $A extends $B ? 1 : 0
932
-
933
- export type SelectModelPropertyKeys<$Model extends Model> = {
934
- [K in keyof $Model]-?: K extends
935
- | 'QueryBuilderType'
936
- | 'foreignKeyId'
937
- | `$${string}`
938
- ? never
939
- : $Model[K] extends Function
940
- ? never
941
- : K
942
- }[keyof $Model]
943
-
1711
+ /**
1712
+ * Authorization configuration for controllers and actions.
1713
+ *
1714
+ * - `boolean`: `true` requires authentication, `false` is
1715
+ * public.
1716
+ * - `'$self'`: Authorized only if the member matches the
1717
+ * authenticated user.
1718
+ * - `'$owner'`: Authorized if the member is owned by the
1719
+ * authenticated user (via `Model.$hasOwner()`).
1720
+ * - Any other `string`: Checked as a role via
1721
+ * `UserModel.$hasRole()`.
1722
+ * - `function`: Dynamically resolves to any of the above.
1723
+ * - `Record<HTTPMethod, string | string[]>`: Per-method
1724
+ * role authorization.
1725
+ */
944
1726
  export type Authorize =
945
1727
  | boolean
946
1728
  | OrArrayOf<LiteralUnion<'$self' | '$owner'>>
@@ -978,7 +1760,7 @@ export type BaseControllerActionOptions = {
978
1760
  * Validates action parameters and maps them to Koa's `ctx.query` object
979
1761
  * passed to the action handler.
980
1762
  *
981
- * @see {@link https://github.com/ditojs/dito/blob/master/docs/model-properties.md Model Properties}
1763
+ * @see {@link https://github.com/ditojs/dito/blob/main/docs/model-properties.md Model Properties}
982
1764
  */
983
1765
  parameters?: { [key: string]: Schema }
984
1766
  /**
@@ -986,13 +1768,13 @@ export type BaseControllerActionOptions = {
986
1768
  * optionally maps the value to a key inside a returned object when it
987
1769
  * contains a `name` property.
988
1770
  *
989
- * @see {@link https://github.com/ditojs/dito/blob/master/docs/model-properties.md Model Properties}
1771
+ * @see {@link https://github.com/ditojs/dito/blob/main/docs/model-properties.md Model Properties}
990
1772
  */
991
1773
  response?: Schema & { name?: string }
992
1774
  /**
993
1775
  * The scope(s) to be applied to every query executed through the action.
994
1776
  *
995
- * @see {@link https://github.com/ditojs/dito/blob/master/docs/model-scopes.md Model Scopes}
1777
+ * @see {@link https://github.com/ditojs/dito/blob/main/docs/model-scopes.md Model Scopes}
996
1778
  */
997
1779
  scope?: string[]
998
1780
  /**
@@ -1039,12 +1821,21 @@ export type MemberActionParameter<$Model extends Model = Model> =
1039
1821
  modify?: (query: QueryBuilder<$Model>) => QueryBuilder<$Model>
1040
1822
  }
1041
1823
 
1824
+ /**
1825
+ * A model controller action: either an options object with
1826
+ * `handler` or a bare handler function.
1827
+ */
1042
1828
  export type ModelControllerAction<
1043
1829
  $ModelController extends ModelController = ModelController
1044
1830
  > =
1045
1831
  | ModelControllerActionOptions<$ModelController>
1046
1832
  | ModelControllerActionHandler<$ModelController>
1047
1833
 
1834
+ /**
1835
+ * Map of action names to action definitions for a model
1836
+ * controller's collection-level actions (e.g. `getList`,
1837
+ * `postCreate`).
1838
+ */
1048
1839
  export type ModelControllerActions<
1049
1840
  $ModelController extends ModelController = ModelController
1050
1841
  > = {
@@ -1065,6 +1856,12 @@ type ModelControllerMemberAction<
1065
1856
  })
1066
1857
  | ModelControllerActionHandler<$ModelController>
1067
1858
 
1859
+ /**
1860
+ * Map of action names to action definitions for a model
1861
+ * controller's member-level actions (e.g. `get`, `patch`,
1862
+ * `delete`). Member actions can use `{ from: 'member' }`
1863
+ * parameters to receive the resolved member model.
1864
+ */
1068
1865
  export type ModelControllerMemberActions<
1069
1866
  $ModelController extends ModelController = ModelController
1070
1867
  > = {
@@ -1073,15 +1870,33 @@ export type ModelControllerMemberActions<
1073
1870
  authorize?: Authorize
1074
1871
  }
1075
1872
 
1873
+ /**
1874
+ * Action name pattern: an HTTP method followed by an
1875
+ * optional suffix, e.g. `'getStats'`, `'postImport'`,
1876
+ * `'deleteAll'`.
1877
+ */
1076
1878
  export type ControllerActionName = `${HTTPMethod}${string}`
1077
1879
 
1880
+ /**
1881
+ * Map of action names to action definitions for a
1882
+ * controller. Supports `allow` to whitelist specific
1883
+ * actions and `authorize` for group-level authorization.
1884
+ */
1078
1885
  export type ControllerActions<$Controller extends Controller = Controller> = {
1079
1886
  [name: ControllerActionName]: ControllerAction<$Controller>
1080
1887
  allow?: OrReadOnly<ControllerActionName[]>
1081
1888
  authorize?: Authorize
1082
1889
  }
1083
1890
 
1084
- export class UsersController<M extends Model> extends ModelController<M> {}
1891
+ export class UsersController<
1892
+ M extends Model = Model
1893
+ > extends ModelController<M> {
1894
+ /**
1895
+ * Returns whether the current request is authenticated
1896
+ * and the user is an instance of the controller's model.
1897
+ */
1898
+ isAuthenticated(ctx: KoaContext): boolean
1899
+ }
1085
1900
 
1086
1901
  export class AdminController extends Controller {
1087
1902
  config: AdminConfig
@@ -1097,8 +1912,8 @@ export class AdminController extends Controller {
1097
1912
 
1098
1913
  sendDitoObject(ctx: Koa.Context): void
1099
1914
  middleware(): Koa.Middleware
1100
- setupViteServer(): void
1101
- defineViteConfig(config: UserConfig): UserConfig
1915
+ setupViteServer(): Promise<void>
1916
+ defineViteConfig(config?: UserConfig): UserConfig
1102
1917
  }
1103
1918
  type ModelControllerHookType = 'collection' | 'member'
1104
1919
  type ModelControllerHookKeys<
@@ -1123,8 +1938,6 @@ type ModelControllerHook<
1123
1938
  result: objection.Page<ModelFromModelController<$ModelController>>
1124
1939
  ) => any
1125
1940
 
1126
- type HookHandler = () => void
1127
-
1128
1941
  type HookKeysFromController<$ModelController extends ModelController> =
1129
1942
  | ModelControllerHookKeys<
1130
1943
  Exclude<
@@ -1161,93 +1974,235 @@ type ModelControllerHooks<
1161
1974
  > = {
1162
1975
  [$Key in HookKeysFromController<$ModelController>]?: HandlerFromHookKey<
1163
1976
  $ModelController,
1164
- HookKeysFromController<$ModelController>
1977
+ $Key
1165
1978
  >
1166
1979
  }
1167
1980
 
1981
+ /**
1982
+ * Scope(s) to apply to all queries in a model controller.
1983
+ * A single scope name or an array of scope names.
1984
+ */
1168
1985
  export type ModelControllerScope = OrArrayOf<string>
1169
1986
 
1170
- export class ModelController<$Model extends Model = Model> extends Controller {
1987
+ /**
1988
+ * Abstract base class for controllers that operate on
1989
+ * model collections. Provides CRUD action infrastructure,
1990
+ * query building, and member resolution.
1991
+ */
1992
+ export class CollectionController<
1993
+ $Model extends Model = Model
1994
+ > extends Controller {
1171
1995
  /**
1172
- * The model class that this controller represents. If none is provided, the
1173
- * singularized controller name is used to look up the model class in models
1174
- * registered with the application. As a convention, model controller names
1175
- * should always be provided in pluralized form.
1996
+ * The model class this controller operates on. Set by
1997
+ * subclasses during configuration.
1176
1998
  */
1177
1999
  modelClass?: Class<$Model>
1178
2000
  /**
1179
- * The controller's collection actions. Instead of being provided on the
1180
- * instance level as in the controller base class, they are to be wrapped in a
1181
- * designated object in order to be assigned to the collection.
2001
+ * Whether to use graph methods for insert/update/patch.
1182
2002
  *
1183
- * To limit which collection actions will be mapped to routes, supply an array
1184
- * of action names under the `allow` key. Only the action names listed there
1185
- * will be mapped to routes, everything else will be omitted.
2003
+ * @defaultValue `false`
1186
2004
  */
1187
- collection?: ModelControllerActions<ModelController<$Model>>
2005
+ graph?: boolean
2006
+ /** Whether the controller handles relate operations. */
2007
+ relate?: boolean
1188
2008
  /**
1189
- * The controller's member actions. Instead of being provided on the instance
1190
- * level as in the controller base class, they are to be wrapped in a
1191
- * designated object in order to be assigned to the member.
1192
- *
1193
- * To limit which member actions will be mapped to routes, supply an array of
1194
- * action names under the `allow` key. Only the action names listed there will
1195
- * be mapped to routes, everything else will be omitted.
2009
+ * Whether the controller handles unrelate operations.
1196
2010
  */
1197
- member?: ModelControllerMemberActions<ModelController<$Model>>
1198
- assets?:
1199
- | boolean
1200
- | {
1201
- allow?: OrArrayOf<string>
1202
- authorize: Record<string, OrArrayOf<string>>
1203
- }
2011
+ unrelate?: boolean
2012
+ /** Whether this is a one-to-one relation controller. */
2013
+ isOneToOne: boolean
2014
+ /** The route parameter name for the member id. */
2015
+ idParam: string
2016
+ /**
2017
+ * The scope(s) to apply to every query executed through
2018
+ * this controller.
2019
+ */
2020
+ scope?: ModelControllerScope
2021
+ allowScope?: boolean | OrArrayOf<string>
2022
+ allowFilter?: boolean | OrArrayOf<string>
2023
+ allowParam?: OrArrayOf<LiteralUnion<keyof QueryParameterOptions>>
1204
2024
 
1205
2025
  /**
1206
- * When nothing is returned from a hook, the standard action result is used.
2026
+ * The controller's collection actions with built-in CRUD
2027
+ * defaults.
1207
2028
  */
1208
- hooks?: ModelControllerHooks<ModelController<$Model>>
2029
+ collection?: ModelControllerActions<CollectionController<$Model>>
1209
2030
  /**
1210
- * Controls whether normal database methods should be used, or their …Graph…
1211
- * counterparts.
1212
- *
1213
- * @see {@link https://github.com/ditojs/dito/blob/master/docs/model-queries.md#graph-methods Model Queries – Graph Methods}
2031
+ * The controller's member actions with built-in CRUD
2032
+ * defaults.
1214
2033
  */
1215
- graph?: boolean
2034
+ member?: ModelControllerMemberActions<CollectionController<$Model>>
2035
+
2036
+ /** Creates a query builder for this controller's model. */
2037
+ query(trx?: objection.Transaction): QueryBuilder<$Model>
1216
2038
  /**
1217
- * The query parameter(s) allowed to be passed to the default model actions,
1218
- * both on `collection` and `member` level. If none is provided, every
1219
- * supported parameter is allowed.
1220
- *
1221
- * @see {@link https://github.com/ditojs/dito/blob/master/docs/model-queries.md#find-methods) Model Queries – Find Methods}
2039
+ * Applies controller-level scopes and configuration to
2040
+ * a query builder.
1222
2041
  */
1223
- allowParam?: OrArrayOf<LiteralUnion<keyof QueryParameterOptions>>
2042
+ setupQuery(
2043
+ query: QueryBuilder<$Model>,
2044
+ base?: any
2045
+ ): QueryBuilder<$Model>
2046
+
1224
2047
  /**
1225
- * The scope(s) allowed to be requested when passing the 'scope' query
1226
- * parameter to the default model actions. If none is provided, every
1227
- * supported scope is allowed.
1228
- *
1229
- * @see {@link https://github.com/ditojs/dito/blob/master/docs/model-scopes.md Model Scopes}
2048
+ * Extracts the member id from the request context's
2049
+ * route parameters.
1230
2050
  */
1231
- allowScope?: boolean | OrArrayOf<string>
2051
+ getMemberId(ctx: KoaContext): Id | Id[]
1232
2052
  /**
1233
- * The filter(s) allowed to be requested when passing the 'filter' query
1234
- * parameter to the default model actions. If none is provided, every
1235
- * supported filter is allowed.
1236
- *
1237
- * @see {@link https://github.com/ditojs/dito/blob/master/docs/model-filters.md Model Filters}
2053
+ * Retrieves the model id from a model instance.
1238
2054
  */
1239
- allowFilter?: boolean | OrArrayOf<string>
2055
+ getModelId(model: $Model): Id | Id[]
1240
2056
  /**
1241
- * The scope(s) to be applied to every query executed through this controller.
1242
- *
1243
- * @see {@link https://github.com/ditojs/dito/blob/master/docs/model-scopes.md Model Scopes}
2057
+ * Retrieves a member model from the database for the
2058
+ * current request context.
1244
2059
  */
1245
- scope?: ModelControllerScope
1246
- query(): QueryBuilder<$Model>
2060
+ getMember(
2061
+ ctx: KoaContext,
2062
+ base?: any,
2063
+ options?: {
2064
+ query?: Record<string, any>
2065
+ modify?: (query: QueryBuilder<$Model>) => QueryBuilder<$Model>
2066
+ forUpdate?: boolean
2067
+ }
2068
+ ): Promise<$Model | null>
2069
+
2070
+ /**
2071
+ * Executes a controller action within a transaction
2072
+ * context.
2073
+ */
2074
+ execute(
2075
+ ctx: KoaContext,
2076
+ execute: (
2077
+ query: QueryBuilder<$Model>,
2078
+ trx?: objection.Transaction
2079
+ ) => any
2080
+ ): Promise<any>
2081
+
2082
+ /**
2083
+ * Extracts model IDs from the request body collection,
2084
+ * validating each ID.
2085
+ */
2086
+ getCollectionIds(ctx: KoaContext): Array<Id | Id[]>
2087
+ /**
2088
+ * Returns relevant IDs for the current request: from
2089
+ * route params for member requests, from body for
2090
+ * collection requests.
2091
+ */
2092
+ getIds(ctx: KoaContext): Array<Id | Id[]>
2093
+ /**
2094
+ * Returns the request context extended with a memberId
2095
+ * property.
2096
+ */
2097
+ getContextWithMemberId(
2098
+ ctx: KoaContext,
2099
+ memberId?: Id | Id[]
2100
+ ): KoaContext
2101
+
2102
+ /**
2103
+ * Validates and coerces a model ID using the model
2104
+ * class's reference mechanism.
2105
+ */
2106
+ validateId(id: any): Id | Id[]
2107
+
2108
+ /**
2109
+ * Executes an insert/update/patch action and fetches
2110
+ * the result. Supports both normal and DitoGraph modes.
2111
+ */
2112
+ executeAndFetch(
2113
+ action: string,
2114
+ ctx: KoaContext,
2115
+ modify?: (
2116
+ query: QueryBuilder<$Model>,
2117
+ trx?: objection.Transaction
2118
+ ) => void,
2119
+ body?: Record<string, any>
2120
+ ): Promise<$Model>
2121
+
2122
+ /**
2123
+ * Executes a by-id mutation action and fetches the
2124
+ * result. Throws NotFoundError if not found.
2125
+ */
2126
+ executeAndFetchById(
2127
+ action: string,
2128
+ ctx: KoaContext,
2129
+ modify?: (
2130
+ query: QueryBuilder<$Model>,
2131
+ trx?: objection.Transaction
2132
+ ) => void,
2133
+ body?: Record<string, any>
2134
+ ): Promise<$Model>
2135
+ }
2136
+
2137
+ /**
2138
+ * Controller for a top-level model resource. Extends
2139
+ * CollectionController with relation and asset setup.
2140
+ */
2141
+ export class ModelController<
2142
+ $Model extends Model = Model
2143
+ > extends CollectionController<$Model> {
2144
+ /**
2145
+ * The model class this controller represents. If not
2146
+ * provided, the singularized controller name is used
2147
+ * to look up the model class in models registered with
2148
+ * the application.
2149
+ */
2150
+ modelClass?: Class<$Model>
2151
+ /**
2152
+ * The controller's collection actions. Wrap actions in
2153
+ * this object to assign them to the collection.
2154
+ */
2155
+ collection?: ModelControllerActions<ModelController<$Model>>
2156
+ /**
2157
+ * The controller's member actions. Wrap actions in this
2158
+ * object to assign them to the member.
2159
+ */
2160
+ member?: ModelControllerMemberActions<ModelController<$Model>>
2161
+ assets?:
2162
+ | boolean
2163
+ | {
2164
+ allow?: OrArrayOf<string>
2165
+ authorize: Record<string, OrArrayOf<string>>
2166
+ }
2167
+
2168
+ /**
2169
+ * When nothing is returned from a hook, the standard
2170
+ * action result is used.
2171
+ */
2172
+ hooks?: ModelControllerHooks<ModelController<$Model>>
2173
+ /** Map of relation name to RelationController instance. */
2174
+ relations?: Record<string, RelationController>
2175
+ }
2176
+
2177
+ /**
2178
+ * Controller for nested relation resources. Created
2179
+ * automatically by ModelController during relation setup.
2180
+ */
2181
+ export class RelationController<
2182
+ $Model extends Model = Model
2183
+ > extends CollectionController<$Model> {
2184
+ /** The parent controller that owns this relation. */
2185
+ parent: CollectionController
2186
+
2187
+ /** The raw relation definition object. */
2188
+ object: Record<string, unknown>
2189
+
2190
+ /** The Objection.js relation instance. */
2191
+ relationInstance: objection.Relation
2192
+
2193
+ /** The raw relation definition from the parent. */
2194
+ relationDefinition: Record<string, unknown>
2195
+
2196
+ /** Whether this is a one-to-one relation. */
2197
+ isOneToOne: boolean
2198
+ /** Whether relate operations are supported. */
2199
+ relate: boolean
2200
+ /** Whether unrelate operations are supported. */
2201
+ unrelate: boolean
1247
2202
  }
1248
2203
 
1249
2204
  export class Validator extends objection.Validator {
1250
- constructor(schema?: {
2205
+ constructor(options?: {
1251
2206
  options?: {
1252
2207
  /** @defaultValue `false` */
1253
2208
  async?: boolean
@@ -1274,15 +2229,66 @@ export class Validator extends objection.Validator {
1274
2229
  }
1275
2230
  keywords?: Record<string, Keyword>
1276
2231
  formats?: Record<string, Format>
2232
+ types?: Record<string, any>
1277
2233
  })
2234
+
2235
+ /**
2236
+ * Compiles a JSON schema into a validation function.
2237
+ * Supports sync, async, and throwing modes via options.
2238
+ */
2239
+ compile(
2240
+ jsonSchema: Schema,
2241
+ options?: Record<string, any>
2242
+ ): Ajv.ValidateFunction
2243
+
2244
+ /** Adds a schema to all cached Ajv instances. */
2245
+ addSchema(jsonSchema: Schema): void
2246
+
2247
+ /**
2248
+ * Returns a cached Ajv instance for the given options,
2249
+ * creating one if needed.
2250
+ */
2251
+ getAjv(options?: Record<string, any>): Ajv.default
2252
+
2253
+ /** Creates a new Ajv instance with the given options. */
2254
+ createAjv(options?: Record<string, any>): Ajv.default
2255
+
2256
+ /**
2257
+ * Converts Ajv validation errors into an Objection.js
2258
+ * style error hash.
2259
+ */
2260
+ parseErrors(
2261
+ errors: Ajv.ErrorObject[],
2262
+ options?: Record<string, any>
2263
+ ): Record<string, any[]>
2264
+
2265
+ /**
2266
+ * Processes a JSON schema for patch or async validation
2267
+ * modes.
2268
+ */
2269
+ processSchema(
2270
+ jsonSchema: Schema,
2271
+ options?: Record<string, any>
2272
+ ): Schema
2273
+
2274
+ /** Returns a registered keyword definition by name. */
2275
+ getKeyword(name: string): Keyword | undefined
2276
+
2277
+ /** Returns a registered format definition by name. */
2278
+ getFormat(name: string): Format | undefined
2279
+
2280
+ /** Prefixes error instance paths with a given prefix. */
2281
+ prefixInstancePaths(
2282
+ errors: Ajv.ErrorObject[],
2283
+ prefix: string
2284
+ ): Ajv.ErrorObject[]
1278
2285
  }
1279
2286
 
1280
2287
  // NOTE: Because EventEmitter overrides a number of EventEmitter2 methods with
1281
2288
  // changed signatures, we are unable to extend it.
1282
2289
  export class EventEmitter {
1283
- static mixin: (target: any) => {}
2290
+ static mixin: (target: any) => void
1284
2291
  constructor(options?: EventEmitter2.ConstructorOptions)
1285
- responds(event: EventEmitter2.event): boolean
1286
2292
  emit(
1287
2293
  event: EventEmitter2.event | EventEmitter2.eventNS,
1288
2294
  ...values: any[]
@@ -1293,20 +2299,42 @@ export class EventEmitter {
1293
2299
  listener: EventEmitter2.ListenerFn
1294
2300
  ): this
1295
2301
 
2302
+ on(
2303
+ event: string[],
2304
+ listener: EventEmitter2.ListenerFn
2305
+ ): this
2306
+
2307
+ on(
2308
+ event: Record<string, EventEmitter2.ListenerFn>
2309
+ ): this
2310
+
1296
2311
  off(
1297
2312
  event: EventEmitter2.event | EventEmitter2.eventNS,
1298
2313
  listener: EventEmitter2.ListenerFn
1299
2314
  ): this
1300
2315
 
2316
+ off(
2317
+ event: string[],
2318
+ listener: EventEmitter2.ListenerFn
2319
+ ): this
2320
+
2321
+ off(
2322
+ event: Record<string, EventEmitter2.ListenerFn>
2323
+ ): this
2324
+
1301
2325
  once(
1302
2326
  event: EventEmitter2.event | EventEmitter2.eventNS,
1303
2327
  listener: EventEmitter2.ListenerFn
1304
2328
  ): this
1305
2329
 
1306
- setupEmitter(
1307
- events: Record<string, EventEmitter2.ListenerFn>,
1308
- options: EventEmitter2.ConstructorOptions
1309
- ): void
2330
+ once(
2331
+ event: string[],
2332
+ listener: EventEmitter2.ListenerFn
2333
+ ): this
2334
+
2335
+ once(
2336
+ event: Record<string, EventEmitter2.ListenerFn>
2337
+ ): this
1310
2338
 
1311
2339
  // From EventEmitter2:
1312
2340
  emitAsync(
@@ -1402,9 +2430,9 @@ export class EventEmitter {
1402
2430
  stopListeningTo(
1403
2431
  target?: EventEmitter2.GeneralEventEmitter,
1404
2432
  event?: EventEmitter2.event | EventEmitter2.eventNS
1405
- ): Boolean
2433
+ ): boolean
1406
2434
 
1407
- hasListeners(event?: String): Boolean
2435
+ hasListeners(event?: string): boolean
1408
2436
  static once(
1409
2437
  emitter: EventEmitter2.EventEmitter2,
1410
2438
  event: EventEmitter2.event | EventEmitter2.eventNS,
@@ -1414,13 +2442,49 @@ export class EventEmitter {
1414
2442
  static defaultMaxListeners: number
1415
2443
  }
1416
2444
 
2445
+ /**
2446
+ * Options for Dito.js graph operations (`insertDitoGraph`,
2447
+ * `upsertDitoGraph`, `updateDitoGraph`, `patchDitoGraph`).
2448
+ * Controls how nested relation graphs are persisted.
2449
+ */
1417
2450
  export interface DitoGraphOptions {
2451
+ /**
2452
+ * Strategy for fetching existing data before upserting.
2453
+ *
2454
+ * - `'OnlyNeeded'`: Fetch only data needed to determine
2455
+ * changes.
2456
+ * - `'OnlyIdentifiers'`: Fetch only IDs for comparison.
2457
+ * - `'Everything'`: Fetch all existing graph data.
2458
+ */
1418
2459
  fetchStrategy?: 'OnlyNeeded' | 'OnlyIdentifiers' | 'Everything'
2460
+ /**
2461
+ * Whether to relate existing models found in the graph
2462
+ * instead of inserting new ones.
2463
+ */
1419
2464
  relate?: boolean
2465
+ /** Whether to allow `#ref` references in the graph. */
1420
2466
  allowRefs?: boolean
2467
+ /**
2468
+ * Whether to insert models that don't exist in the
2469
+ * database yet during an upsert.
2470
+ */
1421
2471
  insertMissing?: boolean
2472
+ /**
2473
+ * Whether to unrelate models removed from the graph
2474
+ * (sets the foreign key to `null` instead of deleting).
2475
+ */
1422
2476
  unrelate?: boolean
2477
+ /**
2478
+ * Whether to update existing models found in the graph
2479
+ * (as opposed to only inserting new ones).
2480
+ */
1423
2481
  update?: boolean
2482
+ /**
2483
+ * Enables special handling for cyclic graph upserts,
2484
+ * where self-referential relations are broken into two
2485
+ * phases.
2486
+ */
2487
+ cyclic?: boolean
1424
2488
  }
1425
2489
 
1426
2490
  export type QueryParameterOptions = {
@@ -1434,22 +2498,37 @@ export type QueryParameterOptions = {
1434
2498
  range?: [number, number] | string
1435
2499
  limit?: number
1436
2500
  offset?: number
1437
- order?: 'asc' | 'desc'
2501
+ order?: OrArrayOf<string>
1438
2502
  }
1439
2503
  export type QueryParameterOptionKey = keyof QueryParameterOptions
1440
2504
 
1441
2505
  export class Service {
1442
2506
  constructor(app: Application<Models>, name?: string)
1443
- setup(config: any): void
2507
+
2508
+ /** The application instance. */
2509
+ app: Application<Models>
2510
+ /** The camelized service name. */
2511
+ name: string
2512
+ /** The service configuration. */
2513
+ config: Record<string, unknown> | null
2514
+ /** Whether this service has been initialized. */
2515
+ initialized: boolean
2516
+
2517
+ setup(config: Record<string, unknown>): void
2518
+
1444
2519
  /**
1445
- * To be overridden in sub-classes, if the service needs to initialize.
2520
+ * Override in sub-classes if the service needs async
2521
+ * initialization.
1446
2522
  * @overridable
1447
2523
  */
1448
2524
  initialize(): Promise<void>
2525
+
1449
2526
  /** @overridable */
1450
2527
  start(): Promise<void>
2528
+
1451
2529
  /** @overridable */
1452
2530
  stop(): Promise<void>
2531
+
1453
2532
  get logger(): PinoLogger
1454
2533
  }
1455
2534
  export type Services = Record<string, Class<Service> | Service>
@@ -1458,124 +2537,147 @@ export class QueryBuilder<
1458
2537
  M extends Model,
1459
2538
  R = M[]
1460
2539
  > extends objection.QueryBuilder<M, R> {
2540
+ /** Clones the query with scope/filter state. */
2541
+ clone(): QueryBuilder<M, R>
2542
+ /**
2543
+ * Inherits scopes from a parent query.
2544
+ * @override
2545
+ */
2546
+ childQueryOf(
2547
+ query: QueryBuilder<Model>,
2548
+ options?: Record<string, any>
2549
+ ): this
2550
+
2551
+ /**
2552
+ * Creates a find-only copy of this query (clears
2553
+ * `runAfter` callbacks).
2554
+ * @override
2555
+ */
2556
+ toFindQuery(): QueryBuilder<M, M[]>
2557
+
1461
2558
  /**
1462
2559
  * Returns true if the query defines normal selects: select(), column(),
1463
2560
  * columns()
1464
2561
  */
1465
- hasNormalSelects: () => boolean
2562
+ hasNormalSelects(): boolean
1466
2563
  /**
1467
- * Returns true if the query defines special selects: distinct(), count(),
1468
- * countDistinct(), min(), max(), sum(), sumDistinct(), avg(), avgDistinct()
2564
+ * Returns true if the query defines special selects:
2565
+ * distinct(), count(), countDistinct(), min(), max(),
2566
+ * sum(), sumDistinct(), avg(), avgDistinct()
1469
2567
  */
1470
- hasSpecialSelects: () => boolean
1471
- withScope: (...scopes: string[]) => this
2568
+ hasSpecialSelects(): boolean
2569
+ withScope(...scopes: string[]): this
1472
2570
  /**
1473
- * Clear all scopes defined with `withScope()` statements, preserving the
1474
- * default scope.
2571
+ * Clear all scopes defined with `withScope()` statements,
2572
+ * preserving the default scope.
1475
2573
  */
1476
- clearWithScope: () => this
1477
- ignoreScope: (...scopes: string[]) => this
1478
- applyScope: (...scopes: string[]) => this
1479
- allowScope: (...scopes: string[]) => void
1480
- clearAllowScope: () => void
1481
- applyFilter: {
1482
- (name: string, ...args: any[]): this
1483
- (filters: { [name: string]: any[] }): this
1484
- }
1485
-
1486
- allowFilter: (...filters: string[]) => void
1487
- withGraph: (
2574
+ clearWithScope(): this
2575
+ ignoreScope(...scopes: string[]): this
2576
+ applyScope(...scopes: string[]): this
2577
+ allowScope(...scopes: string[]): void
2578
+ clearAllowScope(): void
2579
+ applyFilter(name: string, ...args: unknown[]): this
2580
+ applyFilter(filters: { [name: string]: unknown[] }): this
2581
+ allowFilter(...filters: string[]): void
2582
+ /** Omits properties from the query result. */
2583
+ omit(...properties: string[]): void
2584
+ withGraph(
1488
2585
  expr: objection.RelationExpression<M>,
1489
- options?: objection.GraphOptions & { algorithm: 'fetch' | 'join' }
1490
- ) => this
2586
+ options?: objection.GraphOptions & {
2587
+ algorithm?: 'fetch' | 'join'
2588
+ }
2589
+ ): this
1491
2590
 
1492
- toSQL: () => string
2591
+ toSQL(): { sql: string; bindings: unknown[] }
1493
2592
  raw: Knex.RawBuilder
1494
2593
  selectRaw: SetReturnType<Knex.RawBuilder, this>
1495
- // TODO: add type for Dito's pluck method, which has a different method
1496
- // signature than the objection one:
1497
- // pluck: <K extends objection.ModelProps<M>>(
1498
- // key: K
1499
- // ) => QueryBuilder<M, ReflectArrayType<R, M[K]>>
1500
- loadDataPath: (
2594
+ pluck(key: string): this
2595
+ loadDataPath(
1501
2596
  dataPath: string[] | string,
1502
- options: objection.GraphOptions & { algorithm: 'fetch' | 'join' }
1503
- ) => this
2597
+ options?: objection.GraphOptions & {
2598
+ algorithm?: 'fetch' | 'join'
2599
+ }
2600
+ ): this
1504
2601
 
1505
- upsert: (
2602
+ upsert(
1506
2603
  data: PartialModelObject<M>,
1507
2604
  options?: {
1508
- update: boolean
1509
- fetch: boolean
2605
+ update?: boolean
2606
+ fetch?: boolean
1510
2607
  }
1511
- ) => this
2608
+ ): this
1512
2609
 
1513
- find: (
2610
+ find(
1514
2611
  query: QueryParameterOptions,
1515
2612
  allowParam?:
1516
2613
  | QueryParameterOptionKey[]
1517
2614
  | {
1518
- [key in keyof QueryParameterOptionKey]: boolean
2615
+ [key in QueryParameterOptionKey]?: boolean
1519
2616
  }
1520
- ) => this
2617
+ ): this
1521
2618
 
1522
- patchById: (id: Id, data: PartialModelObject<M>) => this
1523
- updateById: (id: Id, data: PartialModelObject<M>) => this
1524
- upsertAndFetch: (data: PartialModelObject<M>) => this
1525
- insertDitoGraph: (
2619
+ patchById(id: Id, data: PartialModelObject<M>): this
2620
+ updateById(id: Id, data: PartialModelObject<M>): this
2621
+ upsertAndFetch(data: PartialModelObject<M>): this
2622
+ insertDitoGraph(
1526
2623
  data: PartialDitoModelGraph<M>,
1527
2624
  options?: DitoGraphOptions
1528
- ) => this
2625
+ ): this
1529
2626
 
1530
- insertDitoGraphAndFetch: (
2627
+ insertDitoGraphAndFetch(
1531
2628
  data: PartialDitoModelGraph<M>,
1532
2629
  options?: DitoGraphOptions
1533
- ) => this
2630
+ ): this
1534
2631
 
1535
- upsertDitoGraph: (
2632
+ upsertDitoGraph(
1536
2633
  data: PartialDitoModelGraph<M>,
1537
2634
  options?: DitoGraphOptions
1538
- ) => this
2635
+ ): this
1539
2636
 
1540
- upsertDitoGraphAndFetch: (data: any, options?: DitoGraphOptions) => this
1541
- upsertDitoGraphAndFetchById: (
2637
+ upsertDitoGraphAndFetch(
2638
+ data: PartialDitoModelGraph<M>,
2639
+ options?: DitoGraphOptions
2640
+ ): this
2641
+
2642
+ upsertDitoGraphAndFetchById(
1542
2643
  id: Id,
1543
- data: any,
2644
+ data: PartialDitoModelGraph<M>,
1544
2645
  options?: DitoGraphOptions
1545
- ) => QueryBuilder<M, M>
2646
+ ): this
1546
2647
 
1547
- updateDitoGraph: (
2648
+ updateDitoGraph(
1548
2649
  data: PartialDitoModelGraph<M>,
1549
2650
  options?: DitoGraphOptions
1550
- ) => Promise<any>
2651
+ ): this
1551
2652
 
1552
- updateDitoGraphAndFetch: (
2653
+ updateDitoGraphAndFetch(
1553
2654
  data: PartialDitoModelGraph<M>,
1554
2655
  options?: DitoGraphOptions
1555
- ) => this
2656
+ ): this
1556
2657
 
1557
- updateDitoGraphAndFetchById: (
2658
+ updateDitoGraphAndFetchById(
1558
2659
  id: Id,
1559
- data: any,
2660
+ data: PartialDitoModelGraph<M>,
1560
2661
  options?: DitoGraphOptions
1561
- ) => QueryBuilder<M, M>
2662
+ ): this
1562
2663
 
1563
- patchDitoGraph: (
2664
+ patchDitoGraph(
1564
2665
  data: PartialDitoModelGraph<M>,
1565
2666
  options?: DitoGraphOptions
1566
- ) => this
2667
+ ): this
1567
2668
 
1568
- patchDitoGraphAndFetch: (
2669
+ patchDitoGraphAndFetch(
1569
2670
  data: PartialDitoModelGraph<M>,
1570
2671
  options?: DitoGraphOptions
1571
- ) => this
2672
+ ): this
1572
2673
 
1573
- patchDitoGraphAndFetchById: (
2674
+ patchDitoGraphAndFetchById(
1574
2675
  id: Id,
1575
2676
  data: PartialDitoModelGraph<M>,
1576
2677
  options?: DitoGraphOptions
1577
- ) => QueryBuilder<M, M>
1578
- // TODO: static mixin(target)
2678
+ ): this
2679
+
2680
+ truncate(options?: { restart?: boolean; cascade?: boolean }): this
1579
2681
 
1580
2682
  ArrayQueryBuilderType: QueryBuilder<M, M[]>
1581
2683
  SingleQueryBuilderType: QueryBuilder<M, M>
@@ -1585,6 +2687,51 @@ export class QueryBuilder<
1585
2687
  }
1586
2688
  export interface QueryBuilder<M extends Model, R = M[]> extends KnexHelper {}
1587
2689
 
2690
+ /**
2691
+ * Registry of built-in query parameter handlers (scope,
2692
+ * filter, range, limit, offset, order). Used by
2693
+ * `QueryBuilder.find()` to apply URL query parameters.
2694
+ */
2695
+ type QueryParameterHandler = (
2696
+ query: QueryBuilder<Model>,
2697
+ key: string,
2698
+ value: unknown
2699
+ ) => void
2700
+
2701
+ export const QueryParameters: {
2702
+ register(name: string, handler: QueryParameterHandler): void
2703
+ register(
2704
+ handlers: Record<string, QueryParameterHandler>
2705
+ ): void
2706
+ get(name: string): QueryParameterHandler | undefined
2707
+ has(name: string): boolean
2708
+ getAllowed(): Record<string, boolean>
2709
+ }
2710
+
2711
+ export type QueryFilterDefinition = {
2712
+ parameters?: Record<string, Schema>
2713
+ handler: (
2714
+ query: QueryBuilder<Model>,
2715
+ property: string,
2716
+ ...args: unknown[]
2717
+ ) => void
2718
+ }
2719
+
2720
+ /**
2721
+ * Registry of built-in query filters (text, date-range).
2722
+ * Provides reusable filter implementations that can be
2723
+ * referenced by name in model filter definitions.
2724
+ */
2725
+ export const QueryFilters: {
2726
+ register(name: string, definition: QueryFilterDefinition): void
2727
+ register(
2728
+ definitions: Record<string, QueryFilterDefinition>
2729
+ ): void
2730
+ get(name: string): QueryFilterDefinition | undefined
2731
+ has(name: string): boolean
2732
+ getAllowed(): Record<string, boolean>
2733
+ }
2734
+
1588
2735
  export type PartialModelObject<T extends Model> = {
1589
2736
  [K in objection.NonFunctionPropertyNames<T>]?: objection.Defined<
1590
2737
  T[K]
@@ -1620,17 +2767,22 @@ export class ResponseError extends Error {
1620
2767
  /** The error message. */
1621
2768
  message?: string
1622
2769
  /**
1623
- * An optional code to be used to distinguish different error
1624
- * instances.
2770
+ * An optional code to be used to distinguish
2771
+ * different error instances.
1625
2772
  */
1626
2773
  code?: string | number
1627
2774
  }
2775
+ | Error
1628
2776
  | string,
1629
- defaults?: { message?: string; status?: number }
2777
+ defaults?: { message?: string; status?: number },
2778
+ overrides?: Record<string, unknown>
1630
2779
  )
1631
2780
 
1632
2781
  status: number
1633
2782
  code?: string | number
2783
+ /** Additional error data. */
2784
+ data?: Record<string, unknown>
2785
+ toJSON(): Record<string, unknown>
1634
2786
  }
1635
2787
  export class AssetError extends ResponseError {}
1636
2788
  export class AuthenticationError extends ResponseError {}
@@ -1642,12 +2794,13 @@ export class DatabaseError extends ResponseError {
1642
2794
  | dbErrors.NotNullViolationError
1643
2795
  | dbErrors.ConstraintViolationError
1644
2796
  | dbErrors.DataError
1645
- | dbErrors.DBError
2797
+ | dbErrors.DBError,
2798
+ overrides?: Record<string, unknown>
1646
2799
  )
1647
2800
  }
1648
2801
  export class GraphError extends ResponseError {}
1649
2802
  export class ModelError extends ResponseError {
1650
- constructor(model: Class<Model> | Model)
2803
+ constructor(model: Class<Model> | Model, error?: unknown)
1651
2804
  }
1652
2805
  export class NotFoundError extends ResponseError {}
1653
2806
  export class NotImplementedError extends ResponseError {}
@@ -1655,18 +2808,180 @@ export class QueryBuilderError extends ResponseError {}
1655
2808
  export class RelationError extends ResponseError {}
1656
2809
  export class ValidationError extends ResponseError {}
1657
2810
  export class ControllerError extends ResponseError {
1658
- constructor(controller: { name: string } | { constructor: { name: string } })
2811
+ constructor(
2812
+ controller:
2813
+ | Function
2814
+ | { constructor: { name: string } },
2815
+ error?: unknown
2816
+ )
1659
2817
  }
1660
2818
  /* ------------------------------- End Errors ------------------------------ */
1661
2819
 
1662
- /* ------------------------------ Start Mixins ----------------------------- */
1663
- export type Mixin = (
1664
- target: Object,
1665
- propertyName: string,
1666
- propertyDescriptor: PropertyDescriptor
1667
- ) => void
2820
+ /* ----------------------------- Start Storage ----------------------------- */
2821
+ /**
2822
+ * Base class for file storage backends. Subclasses handle
2823
+ * disk and S3 storage.
2824
+ */
2825
+ export class Storage {
2826
+ constructor(app: Application<Models>, config: StorageConfig)
2827
+ /** The application instance. */
2828
+ app: Application<Models>
2829
+ /** The storage configuration. */
2830
+ config: StorageConfig
2831
+ /** The storage name. */
2832
+ name: string
2833
+ /** The base URL for accessing stored files. */
2834
+ url?: string
2835
+ /** The file system path for disk storage. */
2836
+ path?: string
2837
+ /**
2838
+ * Upload concurrency limit.
2839
+ *
2840
+ * @defaultValue `8`
2841
+ */
2842
+ concurrency: number
2843
+ /** Whether this storage has been initialized. */
2844
+ initialized: boolean
2845
+
2846
+ /** Sets up the storage backend. */
2847
+ setup(): Promise<void>
2848
+ /**
2849
+ * Override in sub-classes for async initialization.
2850
+ * @overridable
2851
+ */
2852
+ initialize(): Promise<void>
2853
+ /**
2854
+ * Returns a multer-compatible storage object for handling
2855
+ * uploads, or null if no underlying storage is configured.
2856
+ */
2857
+ getUploadStorage(
2858
+ config: multer.Options
2859
+ ): multer.StorageEngine | null
2860
+
2861
+ /** Returns a multer upload handler for this storage. */
2862
+ getUploadHandler(
2863
+ config: multer.Options
2864
+ ): Koa.Middleware | null
2865
+
2866
+ /**
2867
+ * Generates a unique storage key from a filename,
2868
+ * combining a UUID with the file extension.
2869
+ */
2870
+ getUniqueKey(name: string): string
2871
+ /**
2872
+ * Checks whether the given URL is allowed as an import
2873
+ * source based on `config.allowedImports`.
2874
+ */
2875
+ isImportSourceAllowed(url: string): boolean
2876
+ /** Adds a file to storage. */
2877
+ addFile(file: AssetFile, data: Buffer): Promise<AssetFile>
2878
+ /** Removes a file from storage. */
2879
+ removeFile(file: AssetFile): Promise<void>
2880
+ /** Reads a file's contents from storage. */
2881
+ readFile(file: AssetFile): Promise<Buffer>
2882
+ /** Lists all keys in the storage. */
2883
+ listKeys(): Promise<string[]>
2884
+ /** Returns the file system path for a file, if any. */
2885
+ getFilePath(file: AssetFile): string | undefined
2886
+ /** Returns the public URL for a file, if any. */
2887
+ getFileUrl(file: AssetFile): string | undefined
2888
+
2889
+ /**
2890
+ * Converts a multer upload object to the internal file
2891
+ * format.
2892
+ */
2893
+ convertStorageFile(
2894
+ storageFile: StorageFile
2895
+ ): AssetFileObject
2896
+
2897
+ /**
2898
+ * Converts an array of multer upload objects to the
2899
+ * internal file format.
2900
+ */
2901
+ convertStorageFiles(
2902
+ storageFiles: StorageFile[]
2903
+ ): AssetFileObject[]
2904
+
2905
+ /**
2906
+ * Converts a plain file object into an AssetFile
2907
+ * instance in-place on this storage.
2908
+ */
2909
+ convertAssetFile(file: AssetFileObject): void
1668
2910
 
1669
- type AssetFileObject = {
2911
+ /** Registers a storage subclass by type name. */
2912
+ static register(storageClass: Class<Storage>): void
2913
+ /** Retrieves a registered storage class by type name. */
2914
+ static get(type: string): Class<Storage> | null
2915
+ }
2916
+
2917
+ /**
2918
+ * Represents a file asset with metadata. Created from
2919
+ * uploaded files or imported URLs.
2920
+ */
2921
+ export class AssetFile {
2922
+ constructor(options: {
2923
+ name: string
2924
+ data: string | Buffer
2925
+ type?: string
2926
+ width?: number
2927
+ height?: number
2928
+ })
2929
+
2930
+ /** Unique storage key (UUID + extension). */
2931
+ key: string
2932
+ /** The original filename. */
2933
+ name: string
2934
+ /** The file's MIME type, set from options or detected from data. */
2935
+ type: string | undefined
2936
+ /** File size in bytes. */
2937
+ size: number
2938
+ /** Image width, if dimensions were read. */
2939
+ width?: number
2940
+ /** Image height, if dimensions were read. */
2941
+ height?: number
2942
+ /** The public URL for this file, set after storage upload. */
2943
+ url?: string
2944
+ /** The file data buffer. */
2945
+ get data(): Buffer | null
2946
+ /** The storage instance this file belongs to. */
2947
+ get storage(): Storage | null
2948
+ /** The file system path, if stored on disk. */
2949
+ get path(): string | undefined
2950
+ /** Reads the file's contents from storage. */
2951
+ read(): Promise<Buffer | null>
2952
+
2953
+ /**
2954
+ * Converts a plain object into an AssetFile instance
2955
+ * in-place on the given storage.
2956
+ */
2957
+ static convert(
2958
+ object: Record<string, any>,
2959
+ storage: Storage
2960
+ ): void
2961
+
2962
+ /** Creates a new AssetFile from the given options. */
2963
+ static create(options: {
2964
+ name: string
2965
+ data: string | Buffer
2966
+ type?: string
2967
+ width?: number
2968
+ height?: number
2969
+ }): AssetFile
2970
+
2971
+ /**
2972
+ * Generates a unique storage key for a filename,
2973
+ * combining a UUID with the file extension.
2974
+ */
2975
+ static getUniqueKey(name: string): string
2976
+ }
2977
+
2978
+ export interface StorageFile extends multer.File {
2979
+ key: string
2980
+ width?: number
2981
+ height?: number
2982
+ }
2983
+
2984
+ export type AssetFileObject = {
1670
2985
  // The unique key within the storage (uuid/v4 + file extension)
1671
2986
  key: string
1672
2987
  // The original filename
@@ -1678,10 +2993,16 @@ type AssetFileObject = {
1678
2993
  // The public url of the file
1679
2994
  url: string
1680
2995
  // The width of the image if the storage defines `config.readDimensions`
1681
- width: number
2996
+ width?: number
1682
2997
  // The height of the image if the storage defines `config.readDimensions`
1683
- height: number
2998
+ height?: number
2999
+ // HMAC signature of the key, present in upload responses, stripped before
3000
+ // persistence.
3001
+ signature?: string
1684
3002
  }
3003
+ /* ------------------------------ End Storage ------------------------------ */
3004
+
3005
+ /* ------------------------------ Start Mixins ----------------------------- */
1685
3006
 
1686
3007
  export const AssetMixin: <T extends Constructor<{}>>(
1687
3008
  target: T
@@ -1691,6 +3012,12 @@ export const AssetMixin: <T extends Constructor<{}>>(
1691
3012
  file: AssetFileObject
1692
3013
  storage: string
1693
3014
  count: number
3015
+ createdAt: Date
3016
+ updatedAt: Date
3017
+ $parseJson(
3018
+ json: object,
3019
+ opt?: ModelOptions
3020
+ ): object
1694
3021
  }>
1695
3022
 
1696
3023
  export const AssetModel: ReturnType<typeof AssetMixin<typeof Model>>
@@ -1710,7 +3037,7 @@ export const SessionMixin: <T extends Constructor<{}>>(
1710
3037
  ) => T &
1711
3038
  Constructor<{
1712
3039
  id: string
1713
- value: { [key: string]: any }
3040
+ value: Record<string, unknown>
1714
3041
  }>
1715
3042
 
1716
3043
  export const SessionModel: ReturnType<typeof SessionMixin<typeof Model>>
@@ -1742,8 +3069,14 @@ export const UserMixin: <T extends Constructor<{}>>(
1742
3069
  sessionScope?: OrArrayOf<string>
1743
3070
  }
1744
3071
 
1745
- // TODO: type options
1746
- login(ctx: KoaContext, options: any): Promise<void>
3072
+ /** Registers the passport strategy for this user class. */
3073
+ setup(): void
3074
+
3075
+ /** Authenticates a user via Passport. */
3076
+ login(
3077
+ ctx: KoaContext,
3078
+ options?: Record<string, unknown>
3079
+ ): Promise<InstanceType<typeof UserModel>>
1747
3080
 
1748
3081
  sessionQuery(
1749
3082
  trx: Knex.Transaction
@@ -1752,72 +3085,11 @@ export const UserMixin: <T extends Constructor<{}>>(
1752
3085
 
1753
3086
  export const UserModel: ReturnType<typeof UserMixin<typeof Model>>
1754
3087
 
1755
- /**
1756
- * Apply the action mixin to a controller action, in order to determine which
1757
- * HTTP method (`'get'`, `'post'`, `'put'`, `'delete'` or `'patch'`) the action
1758
- * should listen to and optionally the path to which it is mapped, defined in
1759
- * relation to the route path of its controller. By default, the normalized
1760
- * method name is used as the action's path, and the `'get'` method is assigned
1761
- * if none is provided.
1762
- */
1763
- export const action: (method: string, path: string) => Mixin
1764
-
1765
- /**
1766
- * Apply the authorize mixin to a controller action, in order to determines
1767
- * whether or how the request is authorized. This value can either be one of the
1768
- * values as described below, an array of them or a function which returns one
1769
- * or more of them.
1770
- *
1771
- * - Boolean: `true` if the action should be authorized, `false` otherwise.
1772
- * - '$self': The requested member is checked against `ctx.state.user` and the
1773
- * action is only authorized if it matches the member.
1774
- * - '$owner': The member is asked if it is owned by `ctx.state.user` through
1775
- * the optional `Model.$hasOwner()` method.
1776
- * - Any string: `ctx.state.user` is checked for this role through the
1777
- * overridable `UserModel.hasRole()` method.
1778
- */
1779
- export const authorize: (
1780
- authorize: (ctx: KoaContext) => void | boolean | OrArrayOf<string>
1781
- ) => Mixin
1782
-
1783
- /**
1784
- * Apply the parameters mixin to a controller action, in order to apply
1785
- * automatic mapping of Koa.js' `ctx.query` object to method parameters along
1786
- * with their automatic validation.
1787
- *
1788
- * @see {@link https://github.com/ditojs/dito/blob/master/docs/model-properties.md Model Properties}
1789
- */
1790
- export const parameters: (params: { [key: string]: Schema }) => Mixin
1791
-
1792
- /**
1793
- * Apply the response mixin to a controller action, in order to provide a schema
1794
- * for the value returned from the action handler and optionally map the value
1795
- * to a key inside a returned object when it contains a `name` property.
1796
- */
1797
- export const response: (
1798
- response: Schema & { name?: string },
1799
- options: any
1800
- ) => Mixin
1801
-
1802
- /**
1803
- * Apply the scope mixin to a controller action, in order to determine the
1804
- * scope(s) to be applied when loading the relation's models. The scope needs to
1805
- * be defined in the related model class' scopes definitions.
1806
- */
1807
- export const scope: (...scopes: string[]) => Mixin
1808
-
1809
- /**
1810
- * Apply the transacted mixin to a controller action in order to determine
1811
- * whether queries in the action should be executed within a transaction. Any
1812
- * failure will mean the database will rollback any queries executed to the
1813
- * pre-transaction state.
1814
- */
1815
- export const transacted: () => Mixin
1816
-
1817
3088
  /* ------------------------------ End Mixins ----------------------------- */
1818
3089
 
1819
3090
  export type HTTPMethod =
1820
3091
  | 'get'
3092
+ | 'head'
1821
3093
  | 'post'
1822
3094
  | 'put'
1823
3095
  | 'delete'
@@ -1827,7 +3099,7 @@ export type HTTPMethod =
1827
3099
  | 'connect'
1828
3100
 
1829
3101
  export interface KnexHelper {
1830
- getDialect(): string
3102
+ getDialect(): string | null
1831
3103
 
1832
3104
  isPostgreSQL(): boolean
1833
3105
 
@@ -1838,11 +3110,70 @@ export interface KnexHelper {
1838
3110
  isMsSQL(): boolean
1839
3111
  }
1840
3112
 
3113
+ export function convertSchema(
3114
+ schema: Schema,
3115
+ options?: Record<string, any>,
3116
+ parentEntry?: Record<string, any> | null
3117
+ ): Record<string, any>
3118
+
3119
+ export function convertRelations(
3120
+ ownerModelClass: Class<Model>,
3121
+ relations: ModelRelations,
3122
+ models: Models
3123
+ ): Record<string, any>
3124
+
3125
+ export function convertRelation(
3126
+ schema: ModelRelation,
3127
+ models: Models
3128
+ ): Record<string, any>
3129
+
3130
+ export function getRelationClass(
3131
+ relation: string | typeof objection.Relation
3132
+ ): typeof objection.Relation | null
3133
+
3134
+ export function isThroughRelationClass(
3135
+ relationClass: typeof objection.Relation
3136
+ ): boolean
3137
+
3138
+ export function addRelationSchemas(
3139
+ modelClass: Class<Model>,
3140
+ properties: Record<string, ModelProperty>
3141
+ ): void
3142
+
1841
3143
  export type Keyword =
1842
3144
  | SetOptional<Ajv.MacroKeywordDefinition, 'keyword'>
1843
3145
  | SetOptional<Ajv.CodeKeywordDefinition, 'keyword'>
1844
3146
  | SetOptional<Ajv.FuncKeywordDefinition, 'keyword'>
1845
3147
  export type Format = Ajv.ValidateFunction | Ajv.FormatDefinition<string>
3148
+
3149
+ /** Built-in AJV keyword definitions. */
3150
+ export const keywords: {
3151
+ specificType: Keyword
3152
+ primary: Keyword
3153
+ foreign: Keyword
3154
+ unique: Keyword
3155
+ index: Keyword
3156
+ computed: Keyword
3157
+ hidden: Keyword
3158
+ unsigned: Keyword
3159
+ _instanceof: Keyword
3160
+ validate: Keyword
3161
+ validateAsync: Keyword
3162
+ relate: Keyword
3163
+ range: Keyword
3164
+ }
3165
+
3166
+ /** Built-in AJV format definitions. */
3167
+ export const formats: {
3168
+ empty: Format
3169
+ required: Format
3170
+ }
3171
+
3172
+ /** Built-in schema type definitions. */
3173
+ export const types: {
3174
+ asset: Record<string, any>
3175
+ color: Record<string, any>
3176
+ }
1846
3177
  export type Id = string | number
1847
3178
  export type KoaContext<$State = any> = Koa.ParameterizedContext<
1848
3179
  $State,
@@ -1855,8 +3186,6 @@ export type KoaContext<$State = any> = Koa.ParameterizedContext<
1855
3186
 
1856
3187
  type LiteralUnion<T extends U, U = string> = T | (U & Record<never, never>)
1857
3188
 
1858
- type ReflectArrayType<Source, Target> = Source extends any[] ? Target[] : Target
1859
-
1860
3189
  type OrArrayOf<T> = T[] | T
1861
3190
 
1862
3191
  type OrReadOnly<T> = Readonly<T> | T
@@ -1866,46 +3195,80 @@ type OrPromiseOf<T> = Promise<T> | T
1866
3195
  type ModelFromModelController<$ModelController extends ModelController> =
1867
3196
  InstanceType<Exclude<$ModelController['modelClass'], undefined>>
1868
3197
 
1869
- export type SelectModelProperties<T> = {
1870
- [K in SelectModelKeys<T>]: T[K] extends Model
1871
- ? SelectModelProperties<T[K]>
1872
- : T[K]
3198
+ type SerializeModelPropertyValue<T> = T extends (infer U)[]
3199
+ ? SerializeModelPropertyValue<U>[]
3200
+ : T extends Model
3201
+ ? SerializedModel<T>
3202
+ : T extends Date
3203
+ ? string
3204
+ : T
3205
+
3206
+ /**
3207
+ * Extracts the JSON-serialized data properties from a Dito
3208
+ * Model, stripping methods, `$`-prefixed, and internal keys.
3209
+ * Converts `Date` to `string` to reflect JSON serialization.
3210
+ */
3211
+ export type SerializedModel<T extends Model> = {
3212
+ -readonly [K in keyof T as ModelDataKey<T, K>]: SerializeModelPropertyValue<
3213
+ T[K]
3214
+ >
1873
3215
  }
1874
3216
 
1875
- export type SelectModelKeys<T> = Exclude<
1876
- objection.NonFunctionPropertyNames<T>,
1877
- | 'QueryBuilderType' //
1878
- | 'foreignKeyId'
1879
- | `$${string}`
1880
- >
3217
+ /** @deprecated Use `SerializedModel` instead. */
3218
+ export type SelectModelProperties<T extends Model> = SerializedModel<T>
3219
+ /** @deprecated Use `keyof SerializedModel<T>` instead. */
3220
+ export type SelectModelKeys<T extends Model> = keyof SerializedModel<T>
3221
+ /** @deprecated Use `SerializedModel` instead. */
3222
+ export type ExtractModelProperties<T extends Model> = SerializedModel<T>
3223
+ /** @deprecated Use `keyof SerializedModel<T>` instead. */
3224
+ export type SelectModelPropertyKeys<T extends Model> = keyof SerializedModel<T>
1881
3225
 
1882
3226
  /* ---------------------- Extended from Ajv JSON Schema --------------------- */
1883
3227
 
3228
+ /**
3229
+ * Dito.js JSON Schema type, extending the AJV JSON Schema type with
3230
+ * Dito.js-specific validation keywords (`validate`, `validateAsync`,
3231
+ * `instanceof`).
3232
+ *
3233
+ * Used throughout the framework for model property definitions, action
3234
+ * parameters, and response schemas.
3235
+ *
3236
+ * @template T - The TypeScript type that this schema validates against.
3237
+ *
3238
+ * @example
3239
+ * ```ts
3240
+ * const schema: Schema<string> = {
3241
+ * type: 'string',
3242
+ * minLength: 1,
3243
+ * validate: ({ data, app }) => typeof data === 'string'
3244
+ * }
3245
+ * ```
3246
+ */
1884
3247
  export type Schema<T = any> = JSONSchemaType<T> & {
1885
3248
  // keywords/_validate.js
1886
3249
  validate?: (params: {
1887
- data: any
1888
- parentData: object | any[]
1889
- rootData: object | any[]
3250
+ data: unknown
3251
+ parentData: object | unknown[]
3252
+ rootData: object | unknown[]
1890
3253
  dataPath: string
1891
3254
  parentIndex?: number
1892
3255
  parentKey?: string
1893
3256
  app: Application<Models>
1894
3257
  validator: Validator
1895
- options: any
3258
+ options: unknown
1896
3259
  }) => boolean | void
1897
3260
 
1898
3261
  // keywords/_validate.js
1899
3262
  validateAsync?: (params: {
1900
- data: any
1901
- parentData: object | any[]
1902
- rootData: object | any[]
3263
+ data: unknown
3264
+ parentData: object | unknown[]
3265
+ rootData: object | unknown[]
1903
3266
  dataPath: string
1904
3267
  parentIndex?: number
1905
3268
  parentKey?: string
1906
3269
  app: Application<Models>
1907
3270
  validator: Validator
1908
- options: any
3271
+ options: unknown
1909
3272
  }) => Promise<boolean | void>
1910
3273
 
1911
3274
  // keywords/_instanceof.js