/// declare module '@ioc:Adonis/Lucid/Orm' { import { DateTime } from 'luxon'; import { Hooks } from '@poppinss/hooks'; import { ProfilerContract, ProfilerRowContract } from '@ioc:Adonis/Core/Profiler'; import { Update, Counter, OneOrMany, Aggregate, Returning, DialectContract, ChainableContract, QueryClientContract, SimplePaginatorMetaKeys, SimplePaginatorContract, TransactionClientContract, ExcutableQueryBuilderContract } from '@ioc:Adonis/Lucid/Database'; /** * ------------------------------------------------------ * Helpers * ------------------------------------------------------ */ /** * Same as [[Parameters]] but omits the first parameter */ type OmitFirst any> = T extends (x: any, ...args: infer P) => any ? P : never; /** * Same as [[Pick]] but picks by value and not the key */ type PickProperties = Pick; /** * Decorator function */ type DecoratorFn = (target: any, property: any) => void; /** * Typed decorator */ type TypedDecorator = (target: TTarget, property: TKey) => void; /** * Typed decorator that also represents an optional property */ type OptionalTypedDecorator = (target: TTarget, property: TKey) => void; /** * A complex type that filters out functions and relationships from the * model attributes and consider all other properties as database * columns. Alternatively, the user can self define a `$columns` * property. */ type ModelAttributes = Model['$columns'] extends undefined ? { [Filtered in { [P in keyof Model]: P extends keyof LucidRow | 'serializeExtras' ? never : Model[P] extends Function | ModelRelationTypes ? never : P; }[keyof Model]]: Model[Filtered]; } : Model['$columns']; /** * Extract the query scopes of a model */ type ExtractScopes = { [Scope in keyof PickProperties>]: (...args: Model[Scope] extends QueryScopeCallback ? OmitFirst : never) => ExtractScopes; }; /** * Reusable interface to define an object. */ interface ModelObject { [key: string]: any; } /** * Shape of cache node to keep getters optimized */ type CacheNode = { original: any; resolved: any; getter: (value: any) => any; }; /** * Shape for cherry picking fields */ type CherryPickFields = string[] | { pick?: string[]; omit?: string[]; }; /** * Shape for cherry picking fields on nested relationships */ type CherryPick = { fields?: CherryPickFields; relations?: { [relation: string]: CherryPick; }; }; /** * List of events for which a model will trigger hooks */ type EventsList = 'save' | 'create' | 'update' | 'delete' | 'fetch' | 'find' | 'paginate'; type HooksHandler = ((data: Data, event: Event) => Promise | void) | string; /** * ------------------------------------------------------ * Query Scope * ------------------------------------------------------ */ /** * Generic query scope callback */ type QueryScopeCallback = (query: ModelQueryBuilderContract, ...args: any[]) => void; /** * Query scope */ type QueryScope = Scope & { readonly isQueryScope: true; }; /** * A function to mark a method as query scope */ type ScopeFn = >(callback: Scope) => QueryScope; /** * ------------------------------------------------------ * Decorators and Options * ------------------------------------------------------ */ /** * Options for defining a column */ type ColumnOptions = { columnName: string; serializeAs: string | null; isPrimary: boolean; meta?: any; /** * Invoked before serializing process happens */ serialize?: (value: any, attribute: string, model: LucidRow) => any; /** * Invoked before create or update happens */ prepare?: (value: any, attribute: string, model: LucidRow) => any; /** * Invoked when row is fetched from the database */ consume?: (value: any, attribute: string, model: LucidRow) => any; }; /** * Shape of column options after they have set on the model */ type ModelColumnOptions = ColumnOptions & { hasGetter: boolean; hasSetter: boolean; }; /** * Represents a computed property on the model */ type ComputedOptions = { serializeAs: string | null; meta?: any; }; /** * Options accepted by the Model.$addRelation method */ type ModelRelationOptions = RelationOptions | ManyToManyRelationOptions | ThroughRelationOptions; /** * Signature for column decorator function */ type ColumnDecorator = (options?: Partial) => DecoratorFn; /** * Signature for computed decorator function */ type ComputedDecorator = (options?: Partial) => DecoratorFn; /** * Decorator for defining date columns */ type DateColumnDecorator = (options?: Partial) => OptionalTypedDecorator; /** * Decorator for defining date time columns. It is same as * date column as of now */ type DateTimeColumnDecorator = DateColumnDecorator; /** * Decorator for defining hooks. The generics enforces that * decorator is used on static properties only */ type HooksDecorator = () => (target: Model, property: string) => void; /** * ------------------------------------------------------ * Model Options * ------------------------------------------------------ */ /** * Model options to be used when making queries */ type ModelOptions = { connection?: string; profiler?: ProfilerContract | ProfilerRowContract; }; /** * Adapter also accepts a client directly */ type ModelAdapterOptions = ModelOptions & { client?: QueryClientContract; }; /** * Options used by the method that internally invokes * the merge method. ] */ type ModelAssignOptions = ModelAdapterOptions & { allowExtraProperties?: boolean; }; /** * Preload function on a model instance */ interface LucidRowPreload extends Preload> { (callback: (preloader: PreloaderContract) => void): Promise; } interface LucidRowAggregate extends Preload> { (callback: (preloader: PreloaderContract) => void): Promise; } /** * An extension of the simple paginator with support for serializing models */ interface ModelPaginatorContract extends Omit, 'toJSON'> { serialize(cherryPick?: CherryPick): { meta: any; data: ModelObject[]; }; toJSON(): { meta: any; data: ModelObject[]; }; } /** * Lazy load aggregates for a given model instance */ interface LazyLoadAggregatesContract extends Promise { loadAggregate: WithAggregate; loadCount: WithCount; exec(): Promise; } /** * ------------------------------------------------------ * Model Query Builder * ------------------------------------------------------ */ /** * Model query builder will have extras methods on top of the Database query builder */ interface ModelQueryBuilderContract> extends ChainableContract, ExcutableQueryBuilderContract { model: Model; returning: Returning; /** * Define a callback to transform a row */ rowTransformer(callback: (row: LucidRow) => void): this; /** * Define a custom preloader for the current query */ usePreloader(preloader: PreloaderContract): this; /** * Whether or not the query is a child query generated for `.where` * callbacks */ isChildQuery: boolean; /** * Alias for the @withScopes method */ apply>(callback: (scopes: Scopes) => void): this; /** * Apply model query scopes on the query bulder */ withScopes>(callback: (scopes: Scopes) => void): this; /** * A copy of client options. */ readonly clientOptions: ModelAdapterOptions; /** * Reference to query client used for making queries */ client: QueryClientContract; /** * Clone query builder instance */ clone(): ModelQueryBuilderContract; /** * A custom set of sideloaded properties defined on the query * builder, this will be passed to the model instance created * by the query builder */ sideload(value: ModelObject): this; /** * Execute and get first result */ first(): Promise; /** * Return the first matching row or fail */ firstOrFail(): Promise; /** * Perform delete operation */ del(returning?: OneOrMany): ModelQueryBuilderContract; delete(returning?: OneOrMany): ModelQueryBuilderContract; /** * A shorthand to define limit and offset based upon the * current page */ forPage(page: number, perPage?: number): this; /** * Execute query with pagination */ paginate(page: number, perPage?: number): Promise : SimplePaginatorContract>; /** * Mutations (update and increment can be one query aswell) */ update: Update>; increment: Counter>; decrement: Counter>; /** * Fetch relationship count */ withCount: WithCount, this>; /** * Fetch aggregate value for a given relationship */ withAggregate: WithAggregate, this>; /** * Add where constraint using the relationship */ has: Has, this>; orHas: Has, this>; andHas: Has, this>; doesntHave: Has, this>; orDoesntHave: Has, this>; andDoesntHave: Has, this>; /** * Add where constraint using the relationship with a custom callback */ whereHas: WhereHas, this>; orWhereHas: WhereHas, this>; andWhereHas: WhereHas, this>; whereDoesntHave: WhereHas, this>; orWhereDoesntHave: WhereHas, this>; andWhereDoesntHave: WhereHas, this>; /** * Define relationships to be preloaded */ preload: Preload, this>; /** * Aggregates */ count: Aggregate; countDistinct: Aggregate; min: Aggregate; max: Aggregate; sum: Aggregate; sumDistinct: Aggregate; avg: Aggregate; avgDistinct: Aggregate; /** * Executes the callback when dialect matches one of the mentioned * dialects */ ifDialect(dialect: DialectContract['name'] | DialectContract['name'][], matchCallback: (query: this) => any, noMatchCallback?: (query: this) => any): this; /** * Executes the callback when dialect matches doesn't all the mentioned * dialects */ unlessDialect(dialect: DialectContract['name'] | DialectContract['name'][], matchCallback: (query: this) => any, noMatchCallback?: (query: this) => any): this; /** * Get rows back as a plain javascript object and not an array * of model instances */ pojo(): ModelQueryBuilderContract; } /** * Shape of model keys */ interface ModelKeysContract { add(key: string, value: string): void; get(key: string, defaultValue: string): string; get(key: string, defaultValue?: string): string | undefined; resolve(key: string): string; all(): ModelObject; } /** * ------------------------------------------------------ * Shape of Model instance * ------------------------------------------------------ */ /** * Shape of the model instance. We prefix the properties with a `$` to * differentiate between special properties provided by the base * model but with exception to `save`, `delete`, `fill`, `merge` * and `toJSON`. * * @note: Since the interface name appears next to the inherited model * methods, we have to choose a succinct name */ interface LucidRow { $attributes: ModelObject; $extras: ModelObject; $original: ModelObject; $preloaded: { [relation: string]: LucidRow | LucidRow[]; }; /** * Columns is a property to get type information for model * attributes. This must be declared by the end user */ $columns: undefined; $sideloaded: ModelObject; $primaryKeyValue?: number | string; $isPersisted: boolean; $isNew: boolean; $isLocal: boolean; $dirty: ModelObject; $isDirty: boolean; $isDeleted: boolean; $options?: ModelOptions; $trx?: TransactionClientContract; $setOptionsAndTrx(options?: ModelAdapterOptions): void; useTransaction(trx: TransactionClientContract): this; useConnection(connection: string): this; /** * Gives an option to the end user to define constraints for update, insert * and delete queries. Since the query builder for these queries aren't * exposed to the end user, this method opens up the API to build * custom queries. */ $getQueryFor(action: 'insert', client: QueryClientContract): ReturnType; $getQueryFor(action: 'update' | 'delete' | 'refresh', client: QueryClientContract): ModelQueryBuilderContract; /** * Read/write attributes. Following methods are intentionally loosely typed, * so that one can bypass the public facing API and type checking for * advanced use cases */ $setAttribute(key: string, value: any): void; $getAttribute(key: string): any; $getAttributeFromCache(key: string, callback: CacheNode['getter']): any; /** * Read/write realtionships. Following methods are intentionally loosely typed, * so that one can bypass the public facing API and type checking for * advanced use cases */ $hasRelated(key: string): boolean; $setRelated(key: string, result: OneOrMany | null): void; $pushRelated(key: string, result: OneOrMany | null): void; $getRelated(key: string, defaultValue?: any): OneOrMany | undefined | null; /** * Consume the adapter result and hydrate the model */ $consumeAdapterResult(adapterResult: ModelObject, sideloadAttributes?: ModelObject): void; $hydrateOriginals(): void; fill(value: Partial>, allowExtraProperties?: boolean): this; merge(value: Partial>, allowExtraProperties?: boolean): this; /** * Enable force update even when no attributes * are dirty */ enableForceUpdate(): this; /** * Actions to perform on the instance */ save(): Promise; delete(): Promise; refresh(): Promise; /** * Load relationships onto the instance */ load: LucidRowPreload; /** * Alias for "load" * @deprecated */ preload: LucidRowPreload; /** * Load aggregates */ loadAggregate: , RelatedBuilder = Self[Name] extends ModelRelations ? Self[Name]['subQuery'] : never>(name: Name, callback: (builder: RelatedBuilder) => void) => LazyLoadAggregatesContract; /** * Load count */ loadCount: , RelatedBuilder = Self[Name] extends ModelRelations ? Self[Name]['subQuery'] : never>(name: Name, callback?: (builder: RelatedBuilder) => void) => LazyLoadAggregatesContract; /** * Serialize attributes to a plain object */ serializeAttributes(fields?: CherryPickFields, raw?: boolean): ModelObject; /** * Serialize computed properties to a plain object */ serializeComputed(fields?: CherryPickFields): ModelObject; /** * Serialize relationships to key-value pair of model instances and * their serializeAs keys */ serializeRelations(fields: undefined, raw: true): { [key: string]: LucidRow | LucidRow[]; }; /** * Serialize relationships to key-value pair of plain nested objects */ serializeRelations(cherryPick: CherryPick['relations'] | undefined, raw: false | undefined): ModelObject; serializeRelations(cherryPick?: CherryPick['relations'], raw?: boolean): ModelObject; /** * Serialize model to a plain object */ serialize(cherryPick?: CherryPick): ModelObject; /** * Converts model to an object. It just returns the properties * of the model, along with preloaded relationships */ toObject(): ModelObject; /** * Serialize everything */ toJSON(): ModelObject; /** * Returns related model for a given relationship */ related>(relation: Name): this[Name] extends ModelRelations ? this[Name]['client'] : never; } /** * ------------------------------------------------------ * Shape of Model constructor * ------------------------------------------------------ */ /** * Shape of the model static properties. The `$` prefix is to denote * special properties from the base model. * * @note: Since the interface name appears next to the inherited model * methods, we have to choose a succinct name */ interface LucidModel { /** * Whether or not model has been booted. After this model configurations * are ignored */ readonly booted: boolean; /** * A map of defined columns */ $columnsDefinitions: Map; /** * A map of defined relationships */ $relationsDefinitions: Map; /** * A map of computed properties */ $computedDefinitions: Map; /** * The primary key for finding unique referencing to a * model */ primaryKey: string; /** * Custom database connection to use */ connection?: string; /** * Naming strategy to use */ namingStrategy: NamingStrategyContract; /** * Database table to use */ table: string; /** * Self assign the primary instead of relying on the database to * return it back */ selfAssignPrimaryKey: boolean; /** * Adapter to work as a bridge between query builder and the model */ $adapter: AdapterContract; /** * Reference to hooks */ $hooks: Hooks; /** * A copy of internal keys mapping. One should be able to resolve between * all key versions */ $keys: { attributesToColumns: ModelKeysContract; attributesToSerialized: ModelKeysContract; columnsToAttributes: ModelKeysContract; columnsToSerialized: ModelKeysContract; serializedToColumns: ModelKeysContract; serializedToAttributes: ModelKeysContract; }; /** * Creating model from adapter results */ $createFromAdapterResult(this: T, result?: ModelObject, sideloadAttributes?: ModelObject, options?: ModelAdapterOptions): null | InstanceType; /** * Creating multiple model instances from an array of adapter * result */ $createMultipleFromAdapterResult(this: T, results: ModelObject[], sideloadAttributes?: ModelObject, options?: ModelAdapterOptions): InstanceType[]; /** * Managing columns */ $addColumn(name: string, options: Partial): ColumnOptions; $hasColumn(name: string): boolean; $getColumn(name: string): ModelColumnOptions | undefined; /** * Managing computed columns */ $addComputed(name: string, options: Partial): ComputedOptions; $hasComputed(name: string): boolean; $getComputed(name: string): ComputedOptions | undefined; /** * Managing relationships */ $addRelation(name: string, type: ModelRelationTypes['__opaque_type'], relatedModel: () => LucidModel, options: ModelRelationOptions): void; /** * Find if a relationship exists */ $hasRelation(name: string): boolean; /** * Get relationship declaration */ $getRelation>>(this: Model, name: Name): InstanceType[Name] extends ModelRelations ? InstanceType[Name]['client']['relation'] : RelationshipsContract; $getRelation(this: Model, name: string): RelationshipsContract; /** * Define a static property on the model using the inherit or * define strategy. * * Inherit strategy will clone the property from the parent model * and will set it on the current model */ $defineProperty(this: Model, propertyName: Prop, defaultValue: Model[Prop], strategy: 'inherit' | 'define' | ((value: Model[Prop]) => Model[Prop])): void; /** * Boot model */ boot(): void; /** * Register a before hook */ before(this: Model, event: Event, handler: HooksHandler, Event>): void; before(this: Model, event: 'paginate', handler: HooksHandler<[ ModelQueryBuilderContract, ModelQueryBuilderContract ], 'paginate'>): void; before(this: Model, event: Event, handler: HooksHandler, Event>): void; /** * Register an after hook */ after(this: Model, event: 'fetch', handler: HooksHandler[], 'fetch'>): void; after(this: Model, event: 'paginate', handler: HooksHandler>, 'paginate'>): void; after(this: Model, event: Event, handler: HooksHandler, Event>): void; /** * Create model and return its instance back */ create(this: T, values: Partial>>, options?: ModelAssignOptions): Promise>; /** * Create many of model instances */ createMany(this: T, values: Partial>>[], options?: ModelAssignOptions): Promise[]>; /** * Find one using the primary key */ find(this: T, value: any, options?: ModelAdapterOptions): Promise>; /** * Find one using the primary key or fail */ findOrFail(this: T, value: any, options?: ModelAdapterOptions): Promise>; /** * Find one using a key-value pair */ findBy(this: T, key: string, value: any, options?: ModelAdapterOptions): Promise>; /** * Find one using a key-value pair or fail */ findByOrFail(this: T, key: string, value: any, options?: ModelAdapterOptions): Promise>; /** * Same as `query().first()` */ first(this: T, options?: ModelAdapterOptions): Promise>; /** * Same as `query().firstOrFail()` */ firstOrFail(this: T, options?: ModelAdapterOptions): Promise>; /** * Find many using an array of primary keys */ findMany(this: T, value: any[], options?: ModelAdapterOptions): Promise[]>; /** * Returns the first row or create a new instance of model without * persisting it */ firstOrNew(this: T, searchPayload: Partial>>, savePayload?: Partial>>, options?: ModelAssignOptions): Promise>; /** * Returns the first row or save it to the database */ firstOrCreate(this: T, searchPayload: Partial>>, savePayload?: Partial>>, options?: ModelAssignOptions): Promise>; /** * Returns the first row or save it to the database */ updateOrCreate(this: T, searchPayload: Partial>>, updatePayload: Partial>>, options?: ModelAssignOptions): Promise>; /** * Find rows or create in-memory instances of the missing * one's. */ fetchOrNewUpMany(this: T, predicate: keyof ModelAttributes> | (keyof ModelAttributes>)[], payload: Partial>>[], options?: ModelAssignOptions): Promise[]>; /** * Find rows or create many when missing. One db call is invoked * for each create */ fetchOrCreateMany(this: T, predicate: keyof ModelAttributes> | (keyof ModelAttributes>)[], payload: Partial>>[], options?: ModelAssignOptions): Promise[]>; /** * Update existing rows or create new one's. */ updateOrCreateMany(this: T, predicate: keyof ModelAttributes> | (keyof ModelAttributes>)[], payload: Partial>>[], options?: ModelAssignOptions): Promise[]>; /** * Fetch all rows */ all(this: T, options?: ModelAdapterOptions): Promise[]>; /** * Returns the query for fetching a model instance */ query>(this: Model, options?: ModelAdapterOptions): ModelQueryBuilderContract; /** * Truncate model table */ truncate(cascade?: boolean): Promise; new (): LucidRow; } /** * ------------------------------------------------------ * Database Adapter * ------------------------------------------------------ */ /** * Every adapter must adhere to the Adapter contract */ interface AdapterContract { /** * Returns query client for a model instance by inspecting it's options */ modelClient(instance: LucidRow): QueryClientContract; /** * Returns query client for a model constructor */ modelConstructorClient(modelConstructor: LucidModel, options?: ModelAdapterOptions): QueryClientContract; /** * Delete model instance */ delete(instance: LucidRow): Promise; /** * Refresh model instance to reflect new values * from the database */ refresh(instance: LucidRow): Promise; /** * Perform insert */ insert(instance: LucidRow, attributes: ModelObject): Promise; /** * Perform update */ update(instance: LucidRow, attributes: ModelObject): Promise; /** * Must return the query builder for the model */ query(modelConstructor: LucidModel, options?: ModelAdapterOptions): ModelQueryBuilderContract; } /** * Naming strategy for model */ interface NamingStrategyContract { /** * The default table name for the given model */ tableName(model: LucidModel): string; /** * The database column name for a given model attribute */ columnName(model: LucidModel, attributeName: string): string; /** * The post serialization name for a given model attribute */ serializedName(model: LucidModel, attributeName: string): string; /** * The local key for a given model relationship */ relationLocalKey(relation: ModelRelations['__opaque_type'], model: LucidModel, relatedModel: LucidModel, relationName: string): string; /** * The foreign key for a given model relationship */ relationForeignKey(relation: ModelRelations['__opaque_type'], model: LucidModel, relatedModel: LucidModel, relationName: string): string; /** * Pivot table name for many to many relationship */ relationPivotTable(relation: 'manyToMany', model: LucidModel, relatedModel: LucidModel, relationName: string): string; /** * Pivot foreign key for many to many relationship */ relationPivotForeignKey(relation: 'manyToMany', model: LucidModel, relatedModel: LucidModel, relationName: string): string; /** * Keys for the pagination meta */ paginationMetaKeys(): SimplePaginatorMetaKeys; } }