import { RawQuery, OneOrMany, StrictValues, ChainableContract, RawBuilderContract } from './querybuilder.js'; import { QueryClientContract, TransactionClientContract } from './database.js'; import { LucidModel, LucidRow, ModelAssignOptions, ModelAttributes, ModelObject, ModelQueryBuilderContract, OptionalTypedDecorator } from './model.js'; /** * ------------------------------------------------------ * Helpers * ------------------------------------------------------ */ /** * Extracts relationship attributes from the model */ export type ExtractModelRelations = { [Key in keyof Model]: Model[Key] extends undefined | null ? never : NonNullable extends ModelRelations ? Key : never; }[keyof Model]; /** * Returns relationship model instance or array of instances based * upon the relationship type */ export type GetRelationModelInstance> = Relation['__opaque_type'] extends 'hasOne' | 'belongsTo' ? Relation['instance'] : Relation['instance'][]; /** * ------------------------------------------------------ * Options * ------------------------------------------------------ */ /** * Options accepted when defining a new relationship. Certain * relationships like `manyToMany` have their own options */ export type RelationOptions> = { localKey?: string; foreignKey?: string; serializeAs?: string | null; onQuery?(query: Related['builder'] | Related['subQuery']): void; meta?: any; }; /** * Options accepted by many to many relationship */ export type ManyToManyRelationOptions> = { pivotTable?: string; localKey?: string; pivotForeignKey?: string; relatedKey?: string; pivotRelatedForeignKey?: string; pivotColumns?: string[]; pivotTimestamps?: boolean | { createdAt: string | boolean; updatedAt: string | boolean; }; serializeAs?: string | null; onQuery?(query: Related['builder'] | Related['subQuery']): void; meta?: any; }; /** * Options accepted by through relationships */ export type ThroughRelationOptions> = RelationOptions & { throughLocalKey?: string; throughForeignKey?: string; throughModel: () => LucidModel; meta?: any; }; /** * ------------------------------------------------------ * Decorators * ------------------------------------------------------ */ /** * Decorator signature to define has one relationship */ export type HasOneDecorator = (model: () => RelatedModel, options?: RelationOptions>) => OptionalTypedDecorator | null>; /** * Decorator signature to define has many relationship */ export type HasManyDecorator = (model: () => RelatedModel, options?: RelationOptions>) => OptionalTypedDecorator>; /** * Decorator signature to define belongs to relationship */ export type BelongsToDecorator = (model: () => RelatedModel, options?: RelationOptions>) => OptionalTypedDecorator | null>; /** * Decorator signature to define many to many relationship */ export type ManyToManyDecorator = (model: () => RelatedModel, column?: ManyToManyRelationOptions>) => OptionalTypedDecorator>; /** * Decorator signature to define has many through relationship */ export type HasManyThroughDecorator = (model: [() => RelatedModel, () => LucidModel], column?: Omit>, 'throughModel'>) => OptionalTypedDecorator>; /** * ------------------------------------------------------ * Opaque typed relationships * ------------------------------------------------------ * * They have no runtime relevance, just a way to distinguish * between standard model properties and relationships * */ export type ModelRelationTypes = { readonly __opaque_type: 'hasOne' | 'hasMany' | 'belongsTo' | 'manyToMany' | 'hasManyThrough'; }; /** * Opaque type for has one relationship */ export type HasOne = InstanceType & { readonly __opaque_type: 'hasOne'; model: RelatedModel; instance: InstanceType; client: HasOneClientContract, RelatedModel>; builder: RelationQueryBuilderContract; subQuery: RelationSubQueryBuilderContract; }; /** * Opaque type for has many relationship */ export type HasMany = InstanceType[] & { readonly __opaque_type: 'hasMany'; model: RelatedModel; instance: InstanceType; client: HasManyClientContract, RelatedModel>; builder: HasManyQueryBuilderContract; subQuery: RelationSubQueryBuilderContract; }; /** * Opaque type for has belongs to relationship */ export type BelongsTo = InstanceType & { readonly __opaque_type: 'belongsTo'; model: RelatedModel; instance: InstanceType; client: BelongsToClientContract, RelatedModel>; builder: RelationQueryBuilderContract; subQuery: RelationSubQueryBuilderContract; }; /** * Opaque type for many to many relationship */ export type ManyToMany = InstanceType[] & { readonly __opaque_type: 'manyToMany'; model: RelatedModel; instance: InstanceType; client: ManyToManyClientContract, RelatedModel>; builder: ManyToManyQueryBuilderContract; subQuery: ManyToManySubQueryBuilderContract; }; /** * Opaque type for many to many relationship */ export type HasManyThrough = InstanceType[] & { readonly __opaque_type: 'hasManyThrough'; model: RelatedModel; instance: InstanceType; client: HasManyThroughClientContract, RelatedModel>; builder: HasManyThroughQueryBuilderContract; subQuery: RelationSubQueryBuilderContract; }; /** * These exists on the models directly as a relationship. The idea * is to distinguish relationship properties from other model * properties. */ export type ModelRelations = HasOne | HasMany | BelongsTo | ManyToMany | HasManyThrough; /** * ------------------------------------------------------ * Relationships * ------------------------------------------------------ */ /** * Interface to be implemented by all relationship types */ export interface BaseRelationContract { readonly type: ModelRelationTypes['__opaque_type']; readonly relationName: string; readonly serializeAs: string | null; readonly booted: boolean; readonly model: ParentModel; relatedModel(): RelatedModel; boot(): void; clone(parent: LucidModel): this; /** * Get client */ client(parent: InstanceType, client: QueryClientContract): unknown; /** * Get eager query for the relationship */ eagerQuery(parent: OneOrMany>, client: QueryClientContract): RelationQueryBuilderContract>; subQuery(client: QueryClientContract): RelationSubQueryBuilderContract; } /** * Has one relationship interface */ export interface HasOneRelationContract extends BaseRelationContract { readonly type: 'hasOne'; readonly localKey: string; readonly foreignKey: string; foreignKeyColumnName: string; localKeyColumnName: string; /** * Set related model as a relationship on the parent model. */ setRelated(parent: InstanceType, related: InstanceType | null): void; /** * Push related model as a relationship on the parent model */ pushRelated(parent: InstanceType, related: InstanceType | null): void; /** * Set multiple related instances on the multiple parent models. * This method is generally invoked during eager load. * * Fetch 10 users and then all profiles for all 10 users and then * call this method to set related instances */ setRelatedForMany(parent: InstanceType[], related: InstanceType[]): void; /** * Returns the query client for one or many model instances. The query * client then be used to fetch and persist relationships. */ client(parent: InstanceType, client: QueryClientContract): HasOneClientContract; /** * Hydrates related model attributes for persistance */ hydrateForPersistance(parent: LucidRow, values: ModelObject | LucidRow): void; } /** * Has many relationship interface */ export interface HasManyRelationContract extends BaseRelationContract { readonly type: 'hasMany'; readonly localKey: string; readonly foreignKey: string; foreignKeyColumnName: string; localKeyColumnName: string; /** * Set related models as a relationship on the parent model */ setRelated(parent: InstanceType, related: InstanceType[]): void; /** * Push related model(s) as a relationship on the parent model */ pushRelated(parent: InstanceType, related: OneOrMany>): void; /** * Set multiple related instances on the multiple parent models. * This method is generally invoked during eager load. * * Fetch 10 users and then all posts for all 10 users and then * call this method to set related instances */ setRelatedForMany(parent: InstanceType[], related: InstanceType[]): void; /** * Returns the query client for one or many model instances. The query * client then be used to fetch and persist relationships. */ client(parent: InstanceType, client: QueryClientContract): HasManyClientContract; /** * Hydrates related model attributes for persistance */ hydrateForPersistance(parent: LucidRow, values: ModelObject | LucidRow): void; } /** * Belongs to relationship interface */ export interface BelongsToRelationContract extends BaseRelationContract { readonly type: 'belongsTo'; readonly localKey: string; readonly foreignKey: string; foreignKeyColumnName: string; localKeyColumnName: string; /** * Set related model as a relationship on the parent model */ setRelated(parent: InstanceType, related: InstanceType | null): void; /** * Push related model as a relationship on the parent model */ pushRelated(parent: InstanceType, related: InstanceType | null): void; /** * Set multiple related instances on the multiple parent models. * This method is generally invoked during eager load. * * Fetch 10 profiles and then users for all 10 profiles and then * call this method to set related instances */ setRelatedForMany(parent: InstanceType[], related: InstanceType[]): void; /** * Returns the query client for a model instance */ client(parent: InstanceType, client: QueryClientContract): BelongsToClientContract; /** * Hydrates parent model attributes for persistance */ hydrateForPersistance(parent: LucidRow, values: ModelObject | LucidRow): void; } /** * Many to many relationship interface */ export interface ManyToManyRelationContract extends BaseRelationContract { type: 'manyToMany'; readonly localKey: string; readonly relatedKey: string; readonly pivotForeignKey: string; readonly pivotRelatedForeignKey: string; readonly pivotTable: string; pivotColumns: string[]; relatedKeyColumnName: string; localKeyColumnName: string; /** * Set related models as a relationship on the parent model */ setRelated(parent: InstanceType, related: InstanceType[]): void; /** * Push related model(s) as a relationship on the parent model */ pushRelated(parent: InstanceType, related: OneOrMany>): void; /** * Set multiple related instances on the multiple parent models. * This method is generally invoked during eager load. */ setRelatedForMany(parent: InstanceType[], related: InstanceType[]): void; /** * Returns the query client for one model instance */ client(parent: InstanceType, client: QueryClientContract): ManyToManyClientContract; /** * Get eager query for the relationship */ eagerQuery(parent: OneOrMany>, client: QueryClientContract): ManyToManyQueryBuilderContract>; /** * Get subquery for the relationships */ subQuery(client: QueryClientContract): ManyToManySubQueryBuilderContract; /** * Returns key-value pair for the pivot table in relation to the parent model */ getPivotPair(parent: LucidRow): [string, number | string]; /** * Returns key-value pair for the pivot table in relation to the related model */ getPivotRelatedPair(related: LucidRow): [string, number | string]; } /** * Has many through relationship interface */ export interface HasManyThroughRelationContract extends BaseRelationContract { type: 'hasManyThrough'; readonly localKey: string; readonly foreignKey: string; readonly throughLocalKey: string; readonly throughForeignKey: string; throughLocalKeyColumnName: string; throughForeignKeyColumnName: string; foreignKeyColumnName: string; localKeyColumnName: string; /** * Set related models as a relationship on the parent model */ setRelated(parent: InstanceType, related: InstanceType[]): void; /** * Push related model(s) as a relationship on the parent model */ pushRelated(parent: InstanceType, related: InstanceType | InstanceType[]): void; /** * Set multiple related instances on the multiple parent models. * This method is generally invoked during eager load. */ setRelatedForMany(parent: InstanceType[], related: InstanceType[]): void; /** * Returns the query client for a model instance */ client(model: InstanceType, client: QueryClientContract): RelationQueryClientContract; } /** * A union of relationships */ export type RelationshipsContract = HasOneRelationContract | HasManyRelationContract | BelongsToRelationContract | ManyToManyRelationContract | HasManyThroughRelationContract; /** * ------------------------------------------------------ * Relationships query client * ------------------------------------------------------ */ export interface RelationQueryClientContract { relation: Relation; /** * Return a query builder instance of the relationship */ query>(): RelationQueryBuilderContract; } /** * Query client for has one relationship */ export interface HasOneClientContract extends RelationQueryClientContract { /** * Save related instance. Sets up the FK automatically */ save(related: InstanceType): Promise; /** * Create related instance. Sets up the FK automatically */ create(values: Partial>>, options?: ModelAssignOptions): Promise>; /** * Return first or create related instance */ firstOrCreate(search: Partial>>, savePayload?: Partial>>, options?: ModelAssignOptions): Promise>; /** * Update or create related instance */ updateOrCreate(search: Partial>>, updatePayload: Partial>>, options?: ModelAssignOptions): Promise>; } /** * Query client for has many relationship. Extends hasOne and * adds support for saving many relations */ export interface HasManyClientContract extends HasOneClientContract { /** * Save many of related instances. Sets up FK automatically */ saveMany(related: InstanceType[]): Promise; /** * Create many of related instances. Sets up FK automatically */ createMany(values: Partial>>[], options?: ModelAssignOptions): Promise[]>; /** * Fetch or create rows. Providers a great API to sync rows */ fetchOrCreateMany(payload: Partial>>[], predicate?: keyof ModelAttributes> | (keyof ModelAttributes>)[], options?: ModelAssignOptions): Promise[]>; /** * Update or create rows. Providers a great API to sync rows */ updateOrCreateMany(payload: Partial>>[], predicate?: keyof ModelAttributes> | (keyof ModelAttributes>)[], options?: ModelAssignOptions): Promise[]>; /** * Return a query builder instance of the relationship */ query>(): HasManyQueryBuilderContract; } /** * Query client for belongs to relationship. Uses `associate` and * `dissociate` over save. */ export interface BelongsToClientContract extends RelationQueryClientContract { /** * Associate related instance */ associate(related: InstanceType): Promise; /** * Dissociate related instance */ dissociate(): Promise; } /** * Query client for many to many relationship. */ export interface ManyToManyClientContract extends RelationQueryClientContract { /** * Returns related model query builder instance */ query>(): ManyToManyQueryBuilderContract; /** * Pivot query just targets the pivot table without any joins */ pivotQuery(): ManyToManyQueryBuilderContract; /** * Save related model instance. Sets up FK automatically */ save(related: InstanceType, performSync?: boolean, // defaults to true pivotAttributes?: ModelObject): Promise; /** * Save many of related model instance. Sets up FK automatically */ saveMany(related: InstanceType[], performSync?: boolean, // defaults to true pivotAttributes?: (ModelObject | undefined)[]): Promise; /** * Create related model instance. Sets up FK automatically */ create(values: Partial>>, pivotAttributes?: ModelObject, options?: ModelAssignOptions): Promise>; /** * Create many of related model instances. Sets up FK automatically */ createMany(values: Partial>>[], pivotAttributes?: (ModelObject | undefined)[], options?: ModelAssignOptions): Promise[]>; /** * Attach new pivot rows */ attach(ids: (string | number)[] | Record, trx?: TransactionClientContract): Promise; /** * Detach existing pivot rows */ detach(ids?: (string | number)[], trx?: TransactionClientContract): Promise; /** * Sync pivot rows. */ sync(ids: (string | number)[] | Record, detach?: boolean, trx?: TransactionClientContract): Promise; } /** * HasMany through client contract. HasMany through doesn't * allow persisting relationships. Use the direct relation * for that. */ export interface HasManyThroughClientContract extends RelationQueryClientContract { /** * Return a query builder instance of the relationship */ query>(): HasManyThroughQueryBuilderContract; } /** * ------------------------------------------------------ * Relationships query builders * ------------------------------------------------------ */ /** * Interface with query builder options for the many to many pivot * table */ export interface PivotQueryBuilderContract { pivotColumns(columns: string[]): this; wherePivot: WherePivot; orWherePivot: WherePivot; andWherePivot: WherePivot; whereNotPivot: WherePivot; orWhereNotPivot: WherePivot; andWhereNotPivot: WherePivot; whereInPivot: WhereInPivot; orWhereInPivot: WhereInPivot; andWhereInPivot: WhereInPivot; whereNotInPivot: WhereInPivot; orWhereNotInPivot: WhereInPivot; andWhereNotInPivot: WhereInPivot; whereNullPivot: WhereNullPivot; orWhereNullPivot: WhereNullPivot; andWhereNullPivot: WhereNullPivot; whereNotNullPivot: WhereNullPivot; orWhereNotNullPivot: WhereNullPivot; andWhereNotNullPivot: WhereNullPivot; } /** * Base query builder for all relations */ export interface RelationQueryBuilderContract extends ModelQueryBuilderContract { /** * Is query a relationship query obtained using `related('relation').query()` */ isRelatedQuery: true; /** * Is query a relationship query obtained using `related('relation').subQuery()` */ isRelatedSubQuery: false; /** * Is query a relationship query obtained using one of the preload methods. */ isRelatedPreloadQuery: boolean; selectRelationKeys(): this; } /** * Has many query builder contract */ export interface HasManyQueryBuilderContract extends RelationQueryBuilderContract { groupLimit(limit: number): this; groupOrderBy(column: string, direction?: 'asc' | 'desc'): this; } /** * Has many query through builder contract */ export interface HasManyThroughQueryBuilderContract extends RelationQueryBuilderContract { groupLimit(limit: number): this; groupOrderBy(column: string, direction?: 'asc' | 'desc'): this; } /** * Possible signatures for adding a where clause */ interface WherePivot { (key: string, value: StrictValues | ChainableContract): Builder; (key: string, operator: string, value: StrictValues | ChainableContract): Builder; } /** * Possible signatures for adding whereNull clause. */ interface WhereNullPivot { (key: string): Builder; } /** * Possible signatures for adding where in clause. */ interface WhereInPivot { (K: string, value: StrictValues[]): Builder; (K: string[], value: StrictValues[][]): Builder; (k: string, callback: (builder: Builder) => void): Builder; (k: string, subquery: ChainableContract | RawBuilderContract | RawQuery): Builder; (k: string[], subquery: ChainableContract | RawBuilderContract | RawQuery): Builder; } /** * Shape of many to many query builder. It has few methods over the standard * model query builder */ export interface ManyToManyQueryBuilderContract extends RelationQueryBuilderContract, PivotQueryBuilderContract { isPivotOnlyQuery: boolean; groupLimit(limit: number): this; groupOrderBy(column: string, direction?: 'asc' | 'desc'): this; } /** * ------------------------------------------------------ * Sub Queries * ------------------------------------------------------ */ /** * Not in use right now. Since after omitting these types from the * model query builder losses "this" scope. Need to re-think */ export type UnSupportedSubQueryMethods = 'preload' | 'decrement' | 'increment' | 'update' | 'paginate' | 'delete' | 'del' | 'firstOrFail' | 'first' | 'exec' | 'withCount'; /** * SubQuery builder allows creating sub queries targeting a relationship. Sub queries * cannot be executed directly, but can be used as a reference in the parent query * builder. Use cases are: * * - withCount * - whereHas */ export interface RelationSubQueryBuilderContract extends ModelQueryBuilderContract { /** * Is query a relationship query obtained using `related('relation').query()` */ isRelatedQuery: false; /** * Is query a relationship query obtained using `related('relation').subQuery()` */ isRelatedSubQuery: true; /** * Is query a relationship query obtained using one of the preload methods. */ isRelatedPreloadQuery: false; selfJoinCounter: number; readonly selfJoinAlias: string; selectRelationKeys(): this; prepare(): this; } /** * SubQuery builder for many to many relationship */ export interface ManyToManySubQueryBuilderContract extends RelationSubQueryBuilderContract, PivotQueryBuilderContract { } /** * The withCount function */ export interface WithCount { , RelatedBuilder = Model[Name] extends ModelRelations ? Model[Name]['subQuery'] : never>(relation: Name, callback?: (builder: RelatedBuilder) => void): Builder; } /** * The with aggregate function */ export interface WithAggregate { , RelatedBuilder = Model[Name] extends ModelRelations ? Model[Name]['subQuery'] : never>(relation: Name, callback: (builder: RelatedBuilder) => void): Builder; } /** * The has function */ export interface Has { >(relation: Name, operator?: string, value?: StrictValues | ChainableContract): Builder; } /** * The whereHas function */ export interface WhereHas { , RelatedBuilder = Model[Name] extends ModelRelations ? Model[Name]['subQuery'] : never>(relation: Name, callback: (builder: RelatedBuilder) => void, operator?: string, value?: StrictValues | ChainableContract): Builder; } /** * ------------------------------------------------------ * Preloader * ------------------------------------------------------ */ /** * The preload function */ export interface Preload { , RelatedBuilder = Model[Name] extends ModelRelations ? Model[Name]['builder'] : never>(relation: Name, callback?: (builder: RelatedBuilder) => void): Builder; } export interface PreloadWithoutCallback { >(relation: Name): Builder; } export interface PreloadOnce extends PreloadWithoutCallback { } /** * Shape of the preloader to preload relationships */ export interface PreloaderContract { processAllForOne(parent: Model, client: QueryClientContract): Promise; processAllForMany(parent: Model[], client: QueryClientContract): Promise; load: Preload; preload: Preload; preloadOnce: PreloadOnce; debug(debug: boolean): this; sideload(values: ModelObject): this; clone(): PreloaderContract; } export {};