import { CompiledQuery, Selectable } from 'kysely'; import DreamClassTransactionBuilder from './dream/DreamClassTransactionBuilder.js'; import DreamInstanceTransactionBuilder from './dream/DreamInstanceTransactionBuilder.js'; import DreamTransaction from './dream/DreamTransaction.js'; import type { InstanceDestroyOptions, InstanceUpdateOptions } from './dream/internal/instanceMutationOptions.js'; import LeftJoinLoadBuilder from './dream/LeftJoinLoadBuilder.js'; import LoadBuilder from './dream/LoadBuilder.js'; import Query from './dream/Query.js'; import { OnStatementForSpecificColumns, PassthroughOnClause, WhereStatement } from './types/associations/shared.js'; import { AssociationTableNames, DbConnectionType } from './types/db.js'; import { AllDefaultScopeNames, AssociationNameToDream, AttributeKeys, CreateOrFindByExtraOpts, DefaultOrNamedScopeName, DreamAssociationNames, DreamAssociationNamesWithoutRequiredOnClauses, DreamAssociationNamesWithPassthroughOnClauses, DreamAssociationNamesWithRequiredOnClauses, DreamAttributes, DreamBelongsToAssociationNames, DreamColumnNames, DreamHasManyAssociationNames, DreamHasOneAssociationNames, DreamModelAssociationNames, DreamPrimaryKeyType, DreamSerializerKey, FindablePropertiesForClass, GlobalModelNames, GlobalSerializerName, JoinAndStatements, OrderDir, PassthroughColumnNames, PassthroughOnClauseKeys, PluckEachArgs, PrimaryKeyForFind, RequiredOnClauseKeys, TableColumnNames, UpdateableAssociationProperties, UpdateableProperties, UpdateablePropertiesForClass, UpdateOrCreateByExtraOpts, ValidatedPluckEachColumnNames } from './types/dream.js'; import { BaseModelColumnTypes, CursorPaginatedDreamQueryOptions, CursorPaginatedDreamQueryResult, DefaultQueryTypeOptions, FindEachOpts, LoadForModifierFn, PaginatedDreamQueryOptions, PaginatedDreamQueryResult, QueryWithJoinedAssociationsType, QueryWithJoinedAssociationsTypeAndNoPreload } from './types/query.js'; import { DreamModelSerializerType, SimpleObjectSerializerType } from './types/serializer.js'; import { ValidationType } from './types/validation.js'; import { JoinedAssociation, JoinedAssociationsTypeFromAssociations, VariadicJoinsArgs, VariadicLeftJoinLoadArgs, VariadicLoadArgs } from './types/variadic.js'; export default class Dream { DB: any; /** * @internal * * This getter will throw an error when developers use .toEqual instead of * useToMatchDreamModels or useToMatchDreamModel in a vite spec. This * must be the first getter in the class in order for this to work, so don't move it. * */ private get _useToMatchDreamModels(); /** * @internal * * Modern Javascript sets all properties that do not have an explicit * assignment within the constructor to undefined in an implicit constructor. * Since the Dream constructor sets the value of properties of instances of * classes that extend Dream (e.g. when passing attributes to #new or #create * or when loading a model via one of the #find methods or #all), we need to * prevent those properties from being set back to undefined. Since all * properties corresponding to a database column get a setter, we achieve this * protection by including a guard in the setters that returns if this * property is set. * */ private columnSetterGuardActivated; /** * @internal * * Certain features (e.g. passing a Dream instance to `create` so that it automatically destructures polymorphic type and primary key) * need static access to things set up by decorators (e.g. associations). Stage 3 Decorators change the context that is available * at decoration time such that the class of a property being decorated is only avilable during instance instantiation. In order * to only apply static values once, on boot, `globallyInitializingDecorators` is set to true on Dream, and all Dream models are instantiated. * */ private static globallyInitializingDecorators; get schema(): any; get connectionName(): any; get connectionTypeConfig(): any; get globalTypeConfig(): any; /** * Determines if the provided Dream class is the same as or a subclass of this Dream class. * This method is particularly useful for runtime type checking and works with STI (Single Table Inheritance) classes. * * For regular Dream classes, this checks if the provided class is exactly the same class. * For STI classes, this checks inheritance relationships - a child STI class will return true * when compared against its parent class. * * ```ts * // Regular class comparison * User.typeof(User) // true * User.typeof(Pet) // false * * // STI inheritance checking * class Balloon extends ApplicationModel { * // base STI class * } * * class Mylar extends Balloon { * // STI child class * } * * Balloon.typeof(Balloon) // true * Balloon.typeof(Mylar) // false * Mylar.typeof(Balloon) // true (child recognizes parent) * Mylar.typeof(Mylar) // true * * // Runtime type checking with variables * const dreamClass: typeof Dream = getRandomDreamClass() * if (dreamClass.typeof(Pet)) { * // dreamClass is Pet or a subclass of Pet * } * ``` * * @param dreamClass - The Dream class to compare against this class * @returns `true` if the provided class is the same as this class or if this class is a subclass of the provided class (STI inheritance), `false` otherwise */ static typeof(dreamClass: typeof Dream): boolean; /** * Shadows #primaryKey, a getter which can be overwritten to customize the id field * for a given model. * * @returns string */ static get primaryKey(): any; /** * Shadows #table, a getter which can be overwritten to customize the table field * for a given model. * * @returns string */ static get table(): string; /** * @internal * * The `createdAt` field may be customized on a Dream model: * * ```ts * class User extends ApplicationModel { * public get createdAtField() { * return 'createdAtTimestamp' as const * } * } * * const user = await User.first() * user.createdAtTimestamp // returns the DateTime that this user was created * * @returns string */ private get _createdAtField(); /** * @internal * * The `updatedAt` field may be customized on a Dream model: * * ```ts * class User extends ApplicationModel { * public get updatedAtField() { * return 'updatedAtTimestamp' as const * } * } * * const user = await User.first() * user.updatedAtTimestamp // returns the DateTime that this user was updated * ``` * * @returns string */ private get _updatedAtField(); /** * @internal * * The `deletedAt` field may be customized on a Dream model: * * ```ts * class User extends ApplicationModel { * public get deletedAtField() { * return 'deletedAtTimestamp' as const * } * } * * const user = await User.first() * await user.destroy() * user.deletedAtTimestamp // returns the DateTime that this user was soft-deleted * ``` * * @returns string */ private get _deletedAtField(); /** * @internal * * Model storage for association metadata, set when using the association decorators like: * @deco.HasOne * @deco.HasMany * @deco.BelongsTo */ private static associationMetadataByType; /** * @internal * * Model storage for scope metadata, set when using the Scope decorator * (this default assignment simply ensures that it is * always an array rather than undefined, * freezing ensures that we never modify the static array on the inherited Dream class) */ private static scopes; /** * @internal * * Model storage for virtual attribute metadata, set on the inheriting class when * using the Virtual decorator (this default assignment simply ensures that it is * always an array rather than undefined, * freezing ensures that we never modify the static array on the inherited Dream class) */ private static virtualAttributes; /** * @internal * * Model storage for additional columns that may not be set via the new/create/update * methods. Set on the inheriting class when using the Virtual decorator (this default * assignment simply ensures that it is always an array rather than undefined) */ private static explicitUnsafeParamColumns; /** * @internal * * Model storage for encrypted attribute metadata, set on the inheriting class when * using the Encrypted decorator (this default assignment simply ensures that it is * always an array rather than undefined, * freezing ensures that we never modify the static array on the inherited Dream class) */ private static encryptedAttributes; /** * @internal * * Model storage for sortable metadata, set when using the Sortable decorator * (this default assignment simply ensures that it is always an array rather than undefined, * freezing ensures that we never modify the static array on the inherited Dream class) * */ private static sortableFields; /** * @internal * * Model storage for STI metadata, set when using the STI decorator */ private static extendedBy; /** * @internal * * Model storage for STI metadata, set when using the STI decorator * (this default assignment simply ensures that it is always a valid object rather than undefined, * freezing ensures that we never modify the static array on the inherited Dream class) */ private static sti; /** * @internal * * Model storage for model hook metadata, set when using the following decorators: * BeforeCreate * BeforeUpdate * BeforeSave * BeforeDestroy * AfterCreate * AfterCreateCommit * AfterUpdate * AfterUpdateCommit * AfterSave * AfterSaveCommit * AfterDestroy * AfterDestroyCommit */ private static hooks; /** * @internal * * Model storage for validation metadata, set when using the Validates decorator * (this default assignment simply ensures that it is always an array rather than undefined, * freezing ensures that we never modify the static array on the inherited Dream class) */ private static validations; /** * @internal * * Model storage for custom validation metadata, set when using the Validate decorator * (this default assignment simply ensures that it is always an array rather than undefined, * freezing ensures that we never modify the static array on the inherited Dream class) * */ private static customValidations; /** * @internal * * Model storage for replica-safe metadata, set when using the ReplicaSafe decorator */ private static replicaSafe; /** * @internal * * Model storage for soft-delete metadata, set when using the SoftDelete decorator */ private static softDelete; /** * @internal * * Provided to distinguish between Dream and other classes * * @returns true */ static get isDream(): boolean; /** * @internal * * Returns true if this model class is the base class of other STI models * * @returns boolean */ private static get isSTIBase(); /** * @internal * * This class together with every STI class below it, however deeply nested. These * are the classes whose rows a query rooted at this class can reach, since an STI * child's rows are a subset of its base's. * * The classes *above* this one are deliberately excluded, as are its siblings: a * column that only a sibling declares is meaningless for the rows this class * matches, so treating it as this class's business protects nothing and costs a * wider walk on every call. * * @returns An array of Dream classes */ private static selfAndStiDescendants; private static stiSiblings; /** * @internal * * Returns true if this model class a child class of a base STI model * * @returns boolean */ private static get isSTIChild(); /** * @internal * * Returns either the base STI class, or else this class * * @returns A dream class */ private static get stiBaseClassOrOwnClass(); /** * Returns the string value representing this model type that will be stored in the * database for STI children or polymorphic associations. * * This value is used to identify the specific model class when querying polymorphic * associations or STI records. For STI models, this returns the base class' sanitized name. * For regular models, it returns the model's sanitized class name. * * When building queries manually, use this method for the type or polymorphic type part of * the query. For example: * * ```ts * // Using in polymorphic queries * const localizedTexts = await LocalizedText.passthrough({ locale }) * .whereAny( * modelsWithTextAssociation.map( * model => ({ * localizableId: model.primaryKeyValue(), * localizableType: model.referenceTypeString, * }) * ) * ) * .all() * * // Example values for different model types * const user = User.new() * user.referenceTypeString // 'User' * * // For STI child classes, returns the base class name * const mylarBalloon = MylarBalloon.new() // extends Balloon (STI base) * mylarBalloon.referenceTypeString // 'Balloon' * ``` * * @returns The string identifier for this model type used in database storage */ static get referenceTypeString(): string; /** * @internal * * Returns the class name, replacing prefixed underscores, since esbuild * will translate some class names to have underscore prefixes, which can * cause unexpected behavior. * * see https://github.com/evanw/esbuild/issues/1260 for more information * * @returns string */ static get sanitizedName(): string; /** * @internal * * Shadows .stiBaseClassOrOwnClass. Returns either the base STI class, or else this class * * @returns A dream class */ private get stiBaseClassOrOwnClass(); /** * Returns the string value representing this model type that will be stored in the * database for STI children or polymorphic associations. * * This value is used to identify the specific model class when querying polymorphic * associations or STI records. For STI models, this returns the base class' sanitized name. * For regular models, it returns the model's sanitized class name. * * When building queries manually, use this method for the type or polymorphic type part of * the query. For example: * * ```ts * // Using in polymorphic queries * const localizedTexts = await LocalizedText.passthrough({ locale }) * .whereAny( * modelsWithTextAssociation.map( * model => ({ * localizableId: model.primaryKeyValue(), * localizableType: model.referenceTypeString, * }) * ) * ) * .all() * * // Example values for different model types * const user = User.new() * user.referenceTypeString // 'User' * * // For STI child classes, returns the base class name * const mylarBalloon = MylarBalloon.new() // extends Balloon (STI base) * mylarBalloon.referenceTypeString // 'Balloon' * ``` * * @returns The string identifier for this model type used in database storage */ get referenceTypeString(): string; /** * this.constructor.name may be prefixed with an underscore during conversion to Javascript. * This method returns the constructor name without a leading underscore. * * @returns A string */ get sanitizedConstructorName(): string; /** * @internal * * Used by model hook decorators to apply a hook to a specific model. * * @param hookType - the type of hook you want to attach the provided statement to * @param statement - the statement to couple to the provided hookType * @returns void */ private static addHook; /** * @internal * * Returns a unique global name for the given model. * * @returns A string representing a unique key for this model */ static get globalName(): string; private static _globalName; /** * @internal * * Used by DreamApp during the load process * for models and serializers to assign * unique global names to each model based on the file * name of that model. */ private static setGlobalName; private static serializationMap; private static displaySerialization; /** * @internal * * Maps the association tree reachable from `serializers` — every serializer that can render this * class at this point in the walk, which is more than one when this class is an STI base. The * serializers are merged into one edge set (see mergeSerializerAssociationEdges) rather than * walked one at a time, so an association several of them render is visited once and the nested * trees of a shared target merge instead of clobbering each other. */ private static recursiveSerializationMap; /** * Returns the column names for the given model * * @returns The column names for the given model */ static columns, DB extends I['DB'], TableName extends keyof DB = InstanceType['table'] & keyof DB, Table extends DB[keyof DB] = DB[TableName], RetType = Set>(this: T): RetType; private static _columns; /** * Returns the list of column names that are safe for * casting automatically using `.paramsFor` within a * psychic controller. It will return a subset of the * `.columns` getter, but which filters out the following: * * createdAt * * updatedAt * * deletedAt * * type fields for STI models * * foreign key fields for belongs to associations (these should usually be verified before being set) * * type fields corresponding to polymorphic associations * * @internal * * Not part of the public API. Psychic reaches it through the bracketed * back-door (`dreamClass['paramSafeColumnsOrFallback']()`), the same escape * hatch used for other Dream internals such as `virtualAttributes`. * * @returns A subset of columns for the given dream class */ private static paramSafeColumnsOrFallback; private static defaultParamSafeColumns; /** * @internal * * Returns true if the column is a column in the database * * @param columnName - the name of the property you are checking for * @returns boolean */ static isColumn(this: T, columnName: string): boolean; /** * @internal * * Returns true if the column is virtual (set using the Virtual decorator) * * @param columnName - the name of the property you are checking for * @returns boolean */ static isVirtualColumn(this: T, columnName: string): boolean; /** * @internal * * Locates an association's metadata by key * * ```ts * Post.getAssociationMetadata('user') * // { * // modelCB: [Function (anonymous)], * // type: 'BelongsTo', * // as: 'user', * // optional: false, * // polymorphic: false, * // primaryKeyOverride: null, * // primaryKey: [Function: primaryKey], * // primaryKeyValue: [Function: primaryKeyValue], * // foreignKey: [Function: foreignKey], * // foreignKeyTypeField: [Function: foreignKeyTypeField] * // } * ``` * * @param associationName - the name of the association you wish to retrieve metadata for * @returns Association metadata for the requested association */ private static getAssociationMetadata; /** * @internal * * Returns an array containing all of the associations for this dream class * * @returns An array containing all of the associations for this dream class */ private static associationMetadataMap; /** * Checks whether the specified association has been defined on this Dream model. * * ```ts * const user = User.new() * * user.hasAssociation('posts') * // true (if User has a posts association defined) * * user.hasAssociation('nonExistentAssociation') * // false * * // Useful for conditional association handling * if (user.hasAssociation('posts')) { * user = await user.load('posts').execute() * } * ``` * * @param associationName - The name of the association to check for * @returns `true` if the association exists on this model, `false` otherwise */ hasAssociation(associationName: string): boolean; /** * Returns all of the association names for this Dream class. * This includes all BelongsTo, HasOne, and HasMany associations * defined on the model. * * This is useful for introspection, debugging, or dynamically working * with all associations on a model. * * ```ts * class User extends ApplicationModel { * @deco.HasMany('Post') * public posts: Post[] * * @deco.HasOne('Profile') * public profile: Profile * * @deco.BelongsTo('Company') * public company: Company * } * * User.associationNames * // ['posts', 'profile', 'company'] * }) * ``` * * @returns An array containing all association names defined on this Dream class */ static get associationNames(): string[]; /** * @deprecated Use {@link removeDefaultScope} instead. This method will become private in v3. * * Returns a query which bypasses user-removable default scopes, including on * association loads. An STI child's reserved type discriminator remains * enforced; query the STI base model to span its registered children. * * @returns A query which bypasses user-removable default scopes */ static removeAllDefaultScopes(this: T): Query>; /** * imports an object from the IOC. This is usually done in rare cases, * in order to avoid circular dependencies, since you can always * use regular imports to import models or serializers otherwise. * * @returns the found model or serializer, depending on what you are looking for */ static lookup> | GlobalModelNames> = GlobalSerializerName> | GlobalModelNames>>(this: T, lookupName: LookupName): typeof Dream | DreamModelSerializerType | SimpleObjectSerializerType | undefined; /** * Prevents a specific default scope from applying when * the Query is executed * * @returns A new Query which will prevent a specific default scope from applying */ static removeDefaultScope(this: T, scopeName: AllDefaultScopeNames>): Query>; /** * Retrieves an array containing all records corresponding to * this model. Be careful using this, since it will attempt to * pull every record into memory at once. For a large number * of records, consider using `.findEach`, which will pull * the records in batches. * * ```ts * await User.all() * // [User{id: 1}, User{id: 2}...] * * // only load specific columns (always includes primary key) * await User.all({ columns: ['name'] }) * // Users will have both 'id' and 'name' properties * ``` * * @param options - Query options * @param options.columns - Array of column names to select. The primary key is always included automatically. For STI models, the type field is also automatically included. * @returns an array of dreams */ static all(this: T, options?: { columns?: DreamColumnNames>[]; }): Promise[]>; /** * Paginates the results of your query, accepting a pageSize and page argument, * which it uses to segment your query into pages, leveraging limit and offset * to deliver your query to you in pages. * * ```ts * const paginated = await User.paginate({ pageSize: 100, page: 2 }) * paginated.results * // [ { User{id: 101}, User{id: 102}, ...}] * * paginated.recordCount * // 350 * * paginated.pageCount * // 4 * * paginated.currentPage * // 2 * ``` * * @param opts - Pagination options * @param opts.page - the page number that you want to fetch results for * @param opts.pageSize - the number of results per page (optional) * @returns A paginated result object containing: * - `recordCount` - A number representing the total number of records matching your query * - `pageCount` - The number of pages needed to encapsulate all the matching records * - `currentPage` - The current page (same as what is provided in the paginate args) * - `results` - An array of records matching the current record */ static paginate(this: T, opts: PaginatedDreamQueryOptions): Promise>>; /** * @deprecated Use {@link cursorPaginate} instead. * * Paginates the results of your query using cursor-based pagination, * accepting a pageSize and cursor argument. This method * provides better performance for large datasets by using cursor-based * pagination instead of offset-based pagination. * * Default order is ascending primary key. * * ```ts * // First page (using undefined to start from beginning) * const firstPage = await User.scrollPaginate({ pageSize: 100, cursor: undefined }) * firstPage.results * // [ { User{id: 1}, User{id: 2}, ...}] * * firstPage.cursor * // "100" (or null if no more pages) * * // Next page using cursor from previous result * const nextPage = await User.scrollPaginate({ * pageSize: 100, * cursor: firstPage.cursor * }) * ``` * * @param opts - scroll pagination options * @param opts.pageSize - the number of results per page * @param opts.cursor - identifier of where to start the next page; use undefined to start from the beginning * @returns A fast pagination result object containing: * - `cursor` - identifier for the next page, or null if no more pages * - `results` - An array of records for the current page */ static scrollPaginate(this: T, opts: CursorPaginatedDreamQueryOptions): Promise>>; /** * Paginates the results of your query using cursor-based pagination, * accepting a pageSize and cursor argument. This method * provides better performance for large datasets by using cursor-based * pagination instead of offset-based pagination. * * Default order is descending primary key. * * ```ts * // First page (using undefined to start from beginning) * const firstPage = await User.cursorPaginate({ pageSize: 100, cursor: undefined }) * firstPage.results * // [ { User{id: 777}, User{id: 776}, ...}] * * firstPage.cursor * // "100" (or null if no more pages) * * // Next page using cursor from previous result * const nextPage = await User.cursorPaginate({ * pageSize: 100, * cursor: firstPage.cursor * }) * ``` * * @param opts - cursor pagination options * @param opts.pageSize - the number of results per page * @param opts.cursor - identifier of where to start the next page; use undefined to start from the beginning * @returns A fast pagination result object containing: * - `cursor` - identifier for the next page, or null if no more pages * - `results` - An array of records for the current page */ static cursorPaginate(this: T, opts: CursorPaginatedDreamQueryOptions): Promise>>; /** * Forces use of a database connection (e.g. 'primary') during the query. * * NOTE: all queries within a transaction always use the 'primary' replica, so * explicitly setting connection within a transaction has no effect. * * @param connection - The connection you wish to access ('primary' or 'replica') * @returns A Query with the requested connection */ static connection(this: T, connection: DbConnectionType): Query>; /** * Retrieves the number of records corresponding * to this model. * * ```ts * const userCount = await User.count() * // 42 * ``` * * @returns The number of records corresponding to this model */ static count(this: T): Promise; /** * Retrieves the number of records in each group, keyed by the * value of the provided group column (a SQL `GROUP BY`). * * ```ts * await User.countBy('name') * // Map(2) { 'fred' => 2, 'zed' => 1 } * ``` * * Only groups with at least one matching row appear in the Map; seed absent * groups yourself with `map.get(key) ?? 0`. When the group column is nullable, * records with a `null` value are grouped under a real `null` key. * * @param groupColumn - the column to group by * @returns A Map from each present group value to the number of records in that group */ static countBy>>(this: T, groupColumn: GroupColumnName): Promise["schema"][InstanceType["table"] & keyof InstanceType["schema"]]["columns" & keyof InstanceType["schema"][InstanceType["table"] & keyof InstanceType["schema"]]][GroupColumnName & keyof InstanceType["schema"][InstanceType["table"] & keyof InstanceType["schema"]]["columns" & keyof InstanceType["schema"][InstanceType["table"] & keyof InstanceType["schema"]]]]["coercedType" & keyof InstanceType["schema"][InstanceType["table"] & keyof InstanceType["schema"]]["columns" & keyof InstanceType["schema"][InstanceType["table"] & keyof InstanceType["schema"]]][GroupColumnName & keyof InstanceType["schema"][InstanceType["table"] & keyof InstanceType["schema"]]["columns" & keyof InstanceType["schema"][InstanceType["table"] & keyof InstanceType["schema"]]]]], number>>; /** * Retrieves the max value of the specified column * for this model's records. * * ```ts * await User.max('id') * // 99 * ``` * * @param columnName - a column name on the model * @returns the max value of the specified column for this model's records */ static max>>(this: T, columnName: ColumnName): Promise["schema"][InstanceType["table"] & keyof InstanceType["schema"]]["columns" & keyof InstanceType["schema"][InstanceType["table"] & keyof InstanceType["schema"]]][ColumnName & keyof InstanceType["schema"][InstanceType["table"] & keyof InstanceType["schema"]]["columns" & keyof InstanceType["schema"][InstanceType["table"] & keyof InstanceType["schema"]]]]["coercedType" & keyof InstanceType["schema"][InstanceType["table"] & keyof InstanceType["schema"]]["columns" & keyof InstanceType["schema"][InstanceType["table"] & keyof InstanceType["schema"]]][ColumnName & keyof InstanceType["schema"][InstanceType["table"] & keyof InstanceType["schema"]]["columns" & keyof InstanceType["schema"][InstanceType["table"] & keyof InstanceType["schema"]]]]]>; /** * Retrieves the max value of the specified column within each group, keyed by * the value of the provided group column (a SQL `GROUP BY`). * * ```ts * await CompositionAsset.maxBy('name', 'score') * // Map(2) { 'primary' => 9, 'secondary' => 4 } * ``` * * Only groups with at least one matching row appear in the Map. When the group * column is nullable, records with a `null` value are grouped under a real * `null` key; a group whose aggregated values are all `null` yields a `null` * value. * * @param groupColumn - the column to group by * @param aggregatedColumn - the column to take the max of within each group * @returns A Map from each present group value to the max of the aggregated column in that group */ static maxBy>, AggregatedColumnName extends DreamColumnNames>>(this: T, groupColumn: GroupColumnName, aggregatedColumn: AggregatedColumnName): Promise["schema"][InstanceType["table"] & keyof InstanceType["schema"]]["columns" & keyof InstanceType["schema"][InstanceType["table"] & keyof InstanceType["schema"]]][GroupColumnName & keyof InstanceType["schema"][InstanceType["table"] & keyof InstanceType["schema"]]["columns" & keyof InstanceType["schema"][InstanceType["table"] & keyof InstanceType["schema"]]]]["coercedType" & keyof InstanceType["schema"][InstanceType["table"] & keyof InstanceType["schema"]]["columns" & keyof InstanceType["schema"][InstanceType["table"] & keyof InstanceType["schema"]]][GroupColumnName & keyof InstanceType["schema"][InstanceType["table"] & keyof InstanceType["schema"]]["columns" & keyof InstanceType["schema"][InstanceType["table"] & keyof InstanceType["schema"]]]]], InstanceType["schema"][InstanceType["table"] & keyof InstanceType["schema"]]["columns" & keyof InstanceType["schema"][InstanceType["table"] & keyof InstanceType["schema"]]][AggregatedColumnName & keyof InstanceType["schema"][InstanceType["table"] & keyof InstanceType["schema"]]["columns" & keyof InstanceType["schema"][InstanceType["table"] & keyof InstanceType["schema"]]]]["coercedType" & keyof InstanceType["schema"][InstanceType["table"] & keyof InstanceType["schema"]]["columns" & keyof InstanceType["schema"][InstanceType["table"] & keyof InstanceType["schema"]]][AggregatedColumnName & keyof InstanceType["schema"][InstanceType["table"] & keyof InstanceType["schema"]]["columns" & keyof InstanceType["schema"][InstanceType["table"] & keyof InstanceType["schema"]]]]]>>; /** * Retrieves the min value of the specified column * for this model's records. * * * ```ts * await User.min('id') * // 1 * ``` * * @param columnName - a column name on the model * @returns the min value of the specified column for this model's records */ static min>>(this: T, columnName: ColumnName): Promise["schema"][InstanceType["table"] & keyof InstanceType["schema"]]["columns" & keyof InstanceType["schema"][InstanceType["table"] & keyof InstanceType["schema"]]][ColumnName & keyof InstanceType["schema"][InstanceType["table"] & keyof InstanceType["schema"]]["columns" & keyof InstanceType["schema"][InstanceType["table"] & keyof InstanceType["schema"]]]]["coercedType" & keyof InstanceType["schema"][InstanceType["table"] & keyof InstanceType["schema"]]["columns" & keyof InstanceType["schema"][InstanceType["table"] & keyof InstanceType["schema"]]][ColumnName & keyof InstanceType["schema"][InstanceType["table"] & keyof InstanceType["schema"]]["columns" & keyof InstanceType["schema"][InstanceType["table"] & keyof InstanceType["schema"]]]]]>; /** * Retrieves the min value of the specified column within each group, keyed by * the value of the provided group column (a SQL `GROUP BY`). * * ```ts * await CompositionAsset.minBy('name', 'score') * // Map(2) { 'primary' => 1, 'secondary' => 4 } * ``` * * Only groups with at least one matching row appear in the Map. When the group * column is nullable, records with a `null` value are grouped under a real * `null` key; a group whose aggregated values are all `null` yields a `null` * value. * * @param groupColumn - the column to group by * @param aggregatedColumn - the column to take the min of within each group * @returns A Map from each present group value to the min of the aggregated column in that group */ static minBy>, AggregatedColumnName extends DreamColumnNames>>(this: T, groupColumn: GroupColumnName, aggregatedColumn: AggregatedColumnName): Promise["schema"][InstanceType["table"] & keyof InstanceType["schema"]]["columns" & keyof InstanceType["schema"][InstanceType["table"] & keyof InstanceType["schema"]]][GroupColumnName & keyof InstanceType["schema"][InstanceType["table"] & keyof InstanceType["schema"]]["columns" & keyof InstanceType["schema"][InstanceType["table"] & keyof InstanceType["schema"]]]]["coercedType" & keyof InstanceType["schema"][InstanceType["table"] & keyof InstanceType["schema"]]["columns" & keyof InstanceType["schema"][InstanceType["table"] & keyof InstanceType["schema"]]][GroupColumnName & keyof InstanceType["schema"][InstanceType["table"] & keyof InstanceType["schema"]]["columns" & keyof InstanceType["schema"][InstanceType["table"] & keyof InstanceType["schema"]]]]], InstanceType["schema"][InstanceType["table"] & keyof InstanceType["schema"]]["columns" & keyof InstanceType["schema"][InstanceType["table"] & keyof InstanceType["schema"]]][AggregatedColumnName & keyof InstanceType["schema"][InstanceType["table"] & keyof InstanceType["schema"]]["columns" & keyof InstanceType["schema"][InstanceType["table"] & keyof InstanceType["schema"]]]]["coercedType" & keyof InstanceType["schema"][InstanceType["table"] & keyof InstanceType["schema"]]["columns" & keyof InstanceType["schema"][InstanceType["table"] & keyof InstanceType["schema"]]][AggregatedColumnName & keyof InstanceType["schema"][InstanceType["table"] & keyof InstanceType["schema"]]["columns" & keyof InstanceType["schema"][InstanceType["table"] & keyof InstanceType["schema"]]]]]>>; /** * Retrieves the sum for all values of the specified column * for this model's records. * * * ```ts * await Game.sum('score') * // 100 * ``` * * @param columnName - a column name on the model * @returns the sum for all values of the specified column for this model's records */ static sum>>(this: T, columnName: ColumnName): Promise["schema"][InstanceType["table"] & keyof InstanceType["schema"]]["columns" & keyof InstanceType["schema"][InstanceType["table"] & keyof InstanceType["schema"]]][ColumnName & keyof InstanceType["schema"][InstanceType["table"] & keyof InstanceType["schema"]]["columns" & keyof InstanceType["schema"][InstanceType["table"] & keyof InstanceType["schema"]]]]["coercedType" & keyof InstanceType["schema"][InstanceType["table"] & keyof InstanceType["schema"]]["columns" & keyof InstanceType["schema"][InstanceType["table"] & keyof InstanceType["schema"]]][ColumnName & keyof InstanceType["schema"][InstanceType["table"] & keyof InstanceType["schema"]]["columns" & keyof InstanceType["schema"][InstanceType["table"] & keyof InstanceType["schema"]]]]]>; /** * Retrieves the sum of the specified column within each group, keyed by * the value of the provided group column (a SQL `GROUP BY`). * * ```ts * await CompositionAsset.sumBy('name', 'score') * // Map(2) { 'primary' => 10, 'secondary' => 4 } * ``` * * Only groups with at least one matching row appear in the Map. When the group * column is nullable, records with a `null` value are grouped under a real * `null` key; a group whose aggregated values are all `null` yields a `null` * value. * * @param groupColumn - the column to group by * @param aggregatedColumn - the column to sum within each group * @returns A Map from each present group value to the sum of the aggregated column in that group */ static sumBy>, AggregatedColumnName extends DreamColumnNames>>(this: T, groupColumn: GroupColumnName, aggregatedColumn: AggregatedColumnName): Promise["schema"][InstanceType["table"] & keyof InstanceType["schema"]]["columns" & keyof InstanceType["schema"][InstanceType["table"] & keyof InstanceType["schema"]]][GroupColumnName & keyof InstanceType["schema"][InstanceType["table"] & keyof InstanceType["schema"]]["columns" & keyof InstanceType["schema"][InstanceType["table"] & keyof InstanceType["schema"]]]]["coercedType" & keyof InstanceType["schema"][InstanceType["table"] & keyof InstanceType["schema"]]["columns" & keyof InstanceType["schema"][InstanceType["table"] & keyof InstanceType["schema"]]][GroupColumnName & keyof InstanceType["schema"][InstanceType["table"] & keyof InstanceType["schema"]]["columns" & keyof InstanceType["schema"][InstanceType["table"] & keyof InstanceType["schema"]]]]], InstanceType["schema"][InstanceType["table"] & keyof InstanceType["schema"]]["columns" & keyof InstanceType["schema"][InstanceType["table"] & keyof InstanceType["schema"]]][AggregatedColumnName & keyof InstanceType["schema"][InstanceType["table"] & keyof InstanceType["schema"]]["columns" & keyof InstanceType["schema"][InstanceType["table"] & keyof InstanceType["schema"]]]]["coercedType" & keyof InstanceType["schema"][InstanceType["table"] & keyof InstanceType["schema"]]["columns" & keyof InstanceType["schema"][InstanceType["table"] & keyof InstanceType["schema"]]][AggregatedColumnName & keyof InstanceType["schema"][InstanceType["table"] & keyof InstanceType["schema"]]["columns" & keyof InstanceType["schema"][InstanceType["table"] & keyof InstanceType["schema"]]]]]>>; /** * Retrieves the average for all values of the specified column * for this model's records. * * * ```ts * await Game.avg('score') * // 100 * ``` * * @param columnName - a column name on the model * @returns the average for all values of the specified column for this model's records */ static avg>>(this: T, columnName: ColumnName): Promise["schema"][InstanceType["table"] & keyof InstanceType["schema"]]["columns" & keyof InstanceType["schema"][InstanceType["table"] & keyof InstanceType["schema"]]][ColumnName & keyof InstanceType["schema"][InstanceType["table"] & keyof InstanceType["schema"]]["columns" & keyof InstanceType["schema"][InstanceType["table"] & keyof InstanceType["schema"]]]]["coercedType" & keyof InstanceType["schema"][InstanceType["table"] & keyof InstanceType["schema"]]["columns" & keyof InstanceType["schema"][InstanceType["table"] & keyof InstanceType["schema"]]][ColumnName & keyof InstanceType["schema"][InstanceType["table"] & keyof InstanceType["schema"]]["columns" & keyof InstanceType["schema"][InstanceType["table"] & keyof InstanceType["schema"]]]]]>; /** * Retrieves the average of the specified column within each group, keyed by * the value of the provided group column (a SQL `GROUP BY`). * * ```ts * await CompositionAsset.avgBy('name', 'score') * // Map(2) { 'primary' => 5, 'secondary' => 4 } * ``` * * Only groups with at least one matching row appear in the Map. When the group * column is nullable, records with a `null` value are grouped under a real * `null` key; a group whose aggregated values are all `null` yields a `null` * value. * * @param groupColumn - the column to group by * @param aggregatedColumn - the column to average within each group * @returns A Map from each present group value to the average of the aggregated column in that group */ static avgBy>, AggregatedColumnName extends DreamColumnNames>>(this: T, groupColumn: GroupColumnName, aggregatedColumn: AggregatedColumnName): Promise["schema"][InstanceType["table"] & keyof InstanceType["schema"]]["columns" & keyof InstanceType["schema"][InstanceType["table"] & keyof InstanceType["schema"]]][GroupColumnName & keyof InstanceType["schema"][InstanceType["table"] & keyof InstanceType["schema"]]["columns" & keyof InstanceType["schema"][InstanceType["table"] & keyof InstanceType["schema"]]]]["coercedType" & keyof InstanceType["schema"][InstanceType["table"] & keyof InstanceType["schema"]]["columns" & keyof InstanceType["schema"][InstanceType["table"] & keyof InstanceType["schema"]]][GroupColumnName & keyof InstanceType["schema"][InstanceType["table"] & keyof InstanceType["schema"]]["columns" & keyof InstanceType["schema"][InstanceType["table"] & keyof InstanceType["schema"]]]]], InstanceType["schema"][InstanceType["table"] & keyof InstanceType["schema"]]["columns" & keyof InstanceType["schema"][InstanceType["table"] & keyof InstanceType["schema"]]][AggregatedColumnName & keyof InstanceType["schema"][InstanceType["table"] & keyof InstanceType["schema"]]["columns" & keyof InstanceType["schema"][InstanceType["table"] & keyof InstanceType["schema"]]]]["coercedType" & keyof InstanceType["schema"][InstanceType["table"] & keyof InstanceType["schema"]]["columns" & keyof InstanceType["schema"][InstanceType["table"] & keyof InstanceType["schema"]]][AggregatedColumnName & keyof InstanceType["schema"][InstanceType["table"] & keyof InstanceType["schema"]]["columns" & keyof InstanceType["schema"][InstanceType["table"] & keyof InstanceType["schema"]]]]]>>; /** * Persists a new record, setting the provided attributes. * Automatically sets createdAt and updatedAt timestamps. * * ```ts * const user = await User.create({ email: 'how@yadoin', password: 'secure123' }) * // Creates and saves a User with the given attributes * * await Post.create({ body: 'howdy', user }) * // Creates a Post with a BelongsTo association to the user * ``` * * @param attributes - attributes or belongs to associations you wish to set on this model before persisting * @param __namedParameters - optional parameters * @param __namedParameters.skipHooks - if true, will skip applying model hooks. Defaults to false * @returns A newly persisted dream instance */ static create(this: T, attributes?: UpdateablePropertiesForClass, { skipHooks }?: { skipHooks?: boolean; }): Promise>; /** * Attempt to create the model. If creation fails * due to uniqueness constraint, then find the existing * model. * * This is useful in situations where we want to avoid * a race condition creating duplicate records. * * IMPORTANT: A unique index/uniqueness constraint must exist on * at least one of the provided attributes * * ```ts * const logEntry = await LogEntry.createOrFindBy({ externalId }, { createWith: params }) * ``` * * @param attributes - The base attributes to persist, but also the attributes to use to find when create fails * @param extraOpts - Additional options * @param extraOpts.createWith - additional attributes to persist when creating, but not used for finding * @param extraOpts.skipHooks - if true, will skip applying model hooks. Defaults to false * @returns A dream instance */ static createOrFindBy(this: T, attributes: FindablePropertiesForClass, extraOpts?: CreateOrFindByExtraOpts): Promise>; /** * Attempt to update the model with the given attributes. * If no record is found, then a new record is created. * All lifecycle hooks are run for whichever action is taken. * * ```ts * const user = await User.updateOrCreateBy({ email }, { with: { name: 'Alice' }) * ``` * * @param attributes - The base attributes for finding which record to update, also used when creating * @param extraOpts - Additional options * @param extraOpts.with - additional attributes to persist when updating and creating, but not used for finding * @param extraOpts.skipHooks - if true, will skip applying model hooks. Defaults to false * @returns A dream instance */ static updateOrCreateBy(this: T, attributes: FindablePropertiesForClass, extraOpts?: UpdateOrCreateByExtraOpts): Promise>; /** * Attempt to create the model with the given attributes. * If creation fails due to uniqueness constraint, then find and update * the existing model. All lifecycle hooks are run for whichever * action is taken. * * This is useful in situations where we want to avoid * a race condition creating duplicate records. * * IMPORTANT: A unique index/uniqueness constraint must exist on * at least one of the provided attributes * * @param attributes - The base attributes for finding which record to update, also used when creating * @param extraOpts - Additional options * @param extraOpts.with - additional attributes to persist when updating and creating, but not used for finding * @param extraOpts.skipHooks - if true, will skip applying model hooks. Defaults to false * @returns A dream instance */ static createOrUpdateBy(this: T, attributes: FindablePropertiesForClass, extraOpts?: UpdateOrCreateByExtraOpts): Promise>; /** * Returns a new query instance with the distinct query applied. * If no columnName is provided, then distinct will apply to the * primary key by default. * * ```ts * await User.distinct('name').pluck('name') * ``` * * @param columnName - The column name you wish to apply the distinct clause to * @returns A Query scoped to this Dream model with the distinct clause applied */ static distinct, DB extends I['DB'], TableName extends InstanceType['table']>(this: T, columnName?: TableColumnNames | null | boolean): Query, Readonly<{ joinedAssociations: Readonly<[]>; rootTableName: InstanceType["table"]; rootTableAlias: InstanceType extends infer T_1 ? T_1 extends InstanceType ? T_1 extends Dream ? T_1["table"] : T_1 : never : never; allowPreload: true; allowLeftJoinPreload: true; allowLimit: true; allowOffset: true; allowPaginate: true; }>>; /** * Finds a record for the corresponding model with the * specified primary key. Returns null if not found. * * ```ts * await User.find(123) * // User{id: 123} * * await User.find(null) * // null * * await User.find(undefined) * // null * ``` * * @param primaryKey - The primaryKey of the record to look up. * @returns Either the found record, or else null */ static find>(this: T, primaryKey: PrimaryKeyForFind): Promise | null>; /** * Finds a record for the corresponding model with the * specified primary key. If not found, an exception is raised. * * ```ts * await User.query().findOrFail(123) * // User{id: 123} * ``` * * @param primaryKey - The primaryKey of the record to look up * @returns Either the found record, or else null */ static findOrFail>(this: T, primaryKey: PrimaryKeyForFind): Promise>; /** * Finds all records for the corresponding model in batches, * and then calls the provided callback * for each found record. Once all records * have been passed for a given batch, the next set of * records will be fetched and passed to your callback, until all * records matching the corresponding model have been fetched. * * ```ts * await User.findEach(user => { * console.log(user) * }) * // User{email: 'hello@world'} * // User{email: 'goodbye@world'} * ``` * * Records are always visited in ascending primary key order; any `order` * applied to the query is discarded. See {@link Query.findEach} for why. * * @param cb - The callback to call for each found record * @param opts - Optional parameters for batch processing * @param opts.batchSize - the batch size you wish to collect records in. If not provided, it will default to 1000 * @returns void */ static findEach(this: T, cb: (instance: InstanceType) => void | Promise, opts?: FindEachOpts): Promise; /** * Encrypts every value of the model's `@deco.Encrypted` properties again * with the current column encryption key, so that reading them no longer * needs the `legacy` key. It is a step of a key rotation (see * `Encrypt.generateKey`): run it in each environment, once that * environment has deployed its rotated keys, from a console or a script, * not from a migration, whose result would then depend on the keys a given * environment has configured when the migration runs. * * ```ts * await User.reencryptEncryptedColumns() * ``` * * It walks every row of the model's table, including rows its default * scopes (such as soft delete) hide and an STI model's siblings, in batches in primary key order, and opens * each non-null value with the current key or, failing that, the legacy * one (see `Encrypt.reencrypt`). Each row is written on its own, with * no model hooks and without changing `updatedAt`, and only while the * column still holds the value read, so a row an app saves in between * keeps the newer value. Reading values here does not fire * `dreamApp.on('encryption:legacy-key-used', ...)`. * * **Run it only once every server encrypts with the new key.** It walks * the table once, so a row a server still configured with the old * `current` writes behind it stays encrypted with the old key, and so does * old-key ciphertext later written straight into a backing column * (`setAttribute`, `setAttributes` and `updateAttributes` accept, for a * backing column, ciphertext that either key opens). Once no writer uses * the old key and this has run for every model with encrypted properties, * `legacy` is safe to drop. * * **A value neither key opens stops it**, throwing as reading the property * would (`DecryptionRotationError` while a `legacy` key is configured, * `DecryptionError` without one). The rows rewritten before it stay * rewritten; running it again once the value is fixed rewrites them again * harmlessly. * * @param opts - Optional parameters * @param opts.batchSize - how many rows to read per batch. Defaults to 1000 */ static reencryptEncryptedColumns(this: T, opts?: FindEachOpts): Promise; /** * Returns a new instance of Query scoped to the given * model class. Especially useful with {@link Query#toKysely}. * * ```ts * await Balloon.query().toKysely('update'). * .set({ * multicolor: sql`array_remove(multicolor, ${colorToRemoveFromAllBalloons})`, * }) * .execute() * ``` * * @returns A new Query instance scoped to this Dream class * */ static query>(this: T): Query; /** * Returns a new instance of Query scoped to the given * model class. Especially useful with {@link Query#toKysely}. * * ```ts * await balloon.query().toKysely('update') * .set({ * multicolor: sql`array_append(multicolor, 'red')`, * }) * .execute() * ``` * * @returns A new Query instance scoped to this Dream class * */ query(this: I): Query; /** * Finds the first record—ordered by primary key—matching * the corresponding model and the specified where statement. * If not found, null is returned. * * ```ts * await User.findBy({ email: 'how@yadoin' }) * // User{email: 'how@yadoin'} * ``` * * @param whereStatement - The where statement used to locate the record * @returns The first model found matching the whereStatement */ static findBy>(this: T, whereStatement: WhereStatement): Promise | null>; /** * Finds the first record—ordered by primary key—matching * the corresponding model and the specified where statement. * If not found, an exception is raised. * * ```ts * await User.findOrFailBy({ email: 'how@yadoin' }) * // User{email: 'how@yadoin'} * ``` * * @param whereStatement - The where statement used to locate the record * @returns The first model found matching the whereStatement */ static findOrFailBy>(this: T, whereStatement: WhereStatement): Promise>; /** * Attempt to find the model with the given attributes. * If no record is found, then a new record is created. * * ```ts * const user = await User.findOrCreateBy({ email }, { createWith: params }) * ``` * * @param attributes - The base attributes for finding, but also the attributes to use when creating * @param extraOpts - Additional options * @param extraOpts.createWith - additional attributes to persist when creating, but not used for finding * @param extraOpts.skipHooks - if true, will skip applying model hooks. Defaults to false * @returns A dream instance */ static findOrCreateBy(this: T, attributes: FindablePropertiesForClass, extraOpts?: CreateOrFindByExtraOpts): Promise>; /** * Returns true if a record exists for the given * model class * * ```ts * await User.exists() * // false * * await User.create({ email: 'how@yadoin' }) * * await User.exists() * // true * ``` * * @returns boolean */ static exists(this: T): Promise; /** * Load each specified association using a single SQL query. * See {@link Dream.preload} for preloading in separate queries. * * Note: since leftJoinPreload loads via single query, it has * some downsides and that may be avoided using {@link Dream.preload}: * 1. `limit` and `offset` will be automatically removed * 2. `through` associations will bring additional namespaces into the query that can conflict with through associations from other associations, creating an invalid query * 3. each nested association will result in an additional record which duplicates data from the outer record. E.g., given `.leftJoinPreload('a', 'b', 'c')`, if each `a` has 10 `b` and each `b` has 10 `c`, then for one `a`, 100 records will be returned, each of which has all of the columns of `a`. `.preload('a', 'b', 'c')` would perform three separate SQL queries, but the data for a single `a` would only be returned once. * 4. the individual query becomes more complex the more associations are included * 5. associations loading associations loading associations could result in exponential amounts of data; in those cases, `.preload(...).findEach(...)` avoids instantiating massive amounts of data at once * 6. leftJoinPreload must enumerate every compiled column of every joined * model, so unlike base-model reads, `preload`/`load`, and saves, it does * not tolerate schema/image skew from an unplanned column drop during a * rolling deploy; see {@link Query.leftJoinPreload} and the * `ignoredColumns` getter for the two-deploy process that makes a * planned drop safe. * * ```ts * const user = await User.leftJoinPreload('posts', 'comments', { and: { visibility: 'public' } }, 'replies').first() * console.log(user.posts[0].comments[0].replies) * // [Reply{id: 1}, Reply{id: 2}] * ``` * * @param args - A chain of association names and and/andNot/andAny clauses * @returns A query for this model with the include statement applied */ static leftJoinPreload, DB extends I['DB'], TableName extends InstanceType['table'], Schema extends I['schema'], const Arr extends readonly unknown[], const LastArg extends VariadicLeftJoinLoadArgs, const JoinedAssociationsCandidate = JoinedAssociationsTypeFromAssociations, const JoinedAssociations extends readonly JoinedAssociation[] = JoinedAssociationsCandidate extends readonly JoinedAssociation[] ? JoinedAssociationsCandidate : never, RetQuery = QueryWithJoinedAssociationsTypeAndNoPreload, JoinedAssociations>>(this: T, ...args: [...Arr, LastArg]): RetQuery; /** * Applies preload statement to a Query scoped to this model. * Upon instantiating records of this model type, * specified associations will be preloaded. * * Preloading/loading/including is necessary prior to accessing associations * on a Dream instance. * * Preload is useful for avoiding the N+1 query problem * * ```ts * const user = await User.preload('posts', 'comments', { and: { visibility: 'public' } }, 'replies').first() * console.log(user.posts[0].comments[0].replies) * // [Reply{id: 1}, Reply{id: 2}] * ``` * * @param args - A chain of association names and and/andNot/andAny clauses * @returns A query for this model with the preload statement applied */ static preload, DB extends I['DB'], TableName extends InstanceType['table'], Schema extends I['schema'], const Arr extends readonly unknown[]>(this: T, ...args: [...Arr, VariadicLoadArgs]): Query, Readonly<{ joinedAssociations: readonly []; rootTableName: InstanceType["table"]; rootTableAlias: InstanceType extends infer T_1 ? T_1 extends InstanceType ? T_1 extends Dream ? T_1["table"] : T_1 : never : never; allowPreload: true; allowLeftJoinPreload: false; allowLimit: true; allowOffset: true; allowPaginate: true; }>>; /** * Recursively preloads all Dream associations referenced by `rendersOne` and `rendersMany` * in a DreamSerializer. This traverses the entire content tree of serializers to automatically * load all necessary associations, eliminating N+1 query problems and removing the need to * manually remember which associations to preload for serialization. * * This method decouples data loading code from data rendering code by having the serializer * (rendering code) inform the query (loading code) about which associations are needed. * As serializers evolve over time - adding new `rendersOne` and `rendersMany` calls or * modifying existing ones - the loading code automatically adapts without requiring * corresponding modifications to preload statements. * * This method analyzes the serializer (specified by `serializerKey` or 'default') and * automatically preloads all associations that will be needed during serialization. * * ```ts * // Instead of manually specifying all associations: * await User.preload('posts', 'comments', 'replies').all() * * // Automatically preload everything needed for serialization: * await User.preloadFor('summary').all() * * // Add where conditions to specific associations during preloading: * await User.preloadFor('default', (associationName, dreamClass) => { * if (dreamClass.typeof(Post) && associationName === 'comments') { * return { and: { published: true } } * } * }).all() * * // Skip preloading specific associations to handle them manually: * await User.preloadFor('summary', (associationName, dreamClass) => { * if (dreamClass.typeof(User) && associationName === 'posts') { * return 'omit' // Handle posts preloading separately with custom logic * } * }) * .preload('posts', { and: { featured: true } }) // Custom preloading * .all() * ``` * * @param serializerKey - The serializer key to use for determining which associations to preload. * @param modifierFn - Optional callback function to modify or omit specific associations during preloading. Called for each association with the Dream class and association name. Return an object with `and`, `andAny`, or `andNot` properties to add where conditions, return 'omit' to skip preloading that association (useful when you want to handle it manually), or return undefined to use default preloading * @returns A Query with all serialization associations preloaded */ static preloadFor, SerializerKey extends DreamSerializerKey>(this: T, serializerKey: SerializerKey, modifierFn?: LoadForModifierFn): Query, Readonly<{ joinedAssociations: readonly []; rootTableName: InstanceType["table"]; rootTableAlias: InstanceType extends infer T_1 ? T_1 extends InstanceType ? T_1 extends Dream ? T_1["table"] : T_1 : never : never; allowPreload: true; allowLeftJoinPreload: false; allowLimit: true; allowOffset: true; allowPaginate: true; }>>; /** * Returns a new Query instance with the provided * inner join statement attached * * ```ts * await User.innerJoin('posts').first() * ``` * * @param args - A chain of association names and and/andNot/andAny clauses * @returns A Query for this model with the inner join clause applied */ static innerJoin, DB extends I['DB'], Schema extends I['schema'], TableName extends I['table'] & keyof Schema, const Arr extends readonly unknown[], const LastArg extends VariadicJoinsArgs, const JoinedAssociationsCandidate = JoinedAssociationsTypeFromAssociations, const JoinedAssociations extends readonly JoinedAssociation[] = JoinedAssociationsCandidate extends readonly JoinedAssociation[] ? JoinedAssociationsCandidate : never, RetQuery = QueryWithJoinedAssociationsType, JoinedAssociations>>(this: T, ...args: [...Arr, LastArg]): RetQuery; /** * Returns a new Query instance with the provided * inner join statement attached * * ```ts * await user.innerJoin('posts').first() * ``` * * @param args - A chain of association names and and/andNot/andAny clauses * @returns A Query for this model with the inner join clause applied */ innerJoin, const JoinedAssociationsCandidate = JoinedAssociationsTypeFromAssociations, const JoinedAssociations extends readonly JoinedAssociation[] = JoinedAssociationsCandidate extends readonly JoinedAssociation[] ? JoinedAssociationsCandidate : never, RetQuery = QueryWithJoinedAssociationsType, JoinedAssociations>>(this: I, ...args: [...Arr, LastArg]): RetQuery; /** * Returns a new Query instance with the provided * left join statement attached * * ```ts * await User.leftJoin('posts').first() * ``` * * @param args - A chain of association names and and/andNot/andAny clauses * @returns A Query for this model with the left join clause applied */ static leftJoin, DB extends I['DB'], Schema extends I['schema'], TableName extends I['table'] & keyof Schema, const Arr extends readonly unknown[], const LastArg extends VariadicJoinsArgs, const JoinedAssociationsCandidate = JoinedAssociationsTypeFromAssociations, const JoinedAssociations extends readonly JoinedAssociation[] = JoinedAssociationsCandidate extends readonly JoinedAssociation[] ? JoinedAssociationsCandidate : never, RetQuery = QueryWithJoinedAssociationsType, JoinedAssociations>>(this: T, ...args: [...Arr, LastArg]): RetQuery; /** * Returns a new Query instance with the provided * left join statement attached * * ```ts * await user.leftJoin('posts').first() * ``` * * @param args - A chain of association names and and/andNot/andAny clauses * @returns A Query for this model with the left join clause applied */ leftJoin, const JoinedAssociationsCandidate = JoinedAssociationsTypeFromAssociations, const JoinedAssociations extends readonly JoinedAssociation[] = JoinedAssociationsCandidate extends readonly JoinedAssociation[] ? JoinedAssociationsCandidate : never, RetQuery = QueryWithJoinedAssociationsType, JoinedAssociations>>(this: I, ...args: [...Arr, LastArg]): RetQuery; /** * Returns the first record corresponding to the * model, ordered by primary key. * * ```ts * await User.first() * // User{id: 1} * ``` * * @returns First record, or null if no record exists */ static first(this: T): Promise | null>; /** * Returns the first record corresponding to the * model, ordered by primary key. If no record is * found, an exception is raised. * * ```ts * await User.firstOrFail() * // User{id: 1} * ``` * * @returns First record */ static firstOrFail(this: T): Promise>; /** * Returns the last record corresponding to the * model, ordered by primary key. * * ```ts * await User.last() * // User{id: 99} * ``` * * @returns Last record, or null if no record exists */ static last(this: T): Promise | null>; /** * Returns the last record corresponding to the * model, ordered by primary key. If no record * is found, an exception is raised. * * ```ts * await User.lastOrFail() * // User{id: 99} * ``` * * @returns Last record */ static lastOrFail(this: T): Promise>; /** * Returns a new Query instance, specifying a limit * * ```ts * await User.limit(2).all() * // [User{}, User{}] * ``` * * @returns A Query for this model with the limit clause applied */ static limit(this: T, count: number | null): Query, Readonly<{ joinedAssociations: readonly []; rootTableName: InstanceType["table"]; rootTableAlias: InstanceType extends infer T_1 ? T_1 extends InstanceType ? T_1 extends Dream ? T_1["table"] : T_1 : never : never; allowPreload: true; allowLeftJoinPreload: false; allowLimit: true; allowOffset: true; allowPaginate: false; }>>; /** * Returns a new Query instance, specifying an offset * * ```ts * await User.offset(2).order('id').limit(2).all() * // [User{id: 3}, User{id: 4}] * ``` * * @returns A Query for this model with the offset clause applied */ static offset(this: T, offset: number | null): Query, Readonly<{ joinedAssociations: readonly []; rootTableName: InstanceType["table"]; rootTableAlias: InstanceType extends infer T_1 ? T_1 extends InstanceType ? T_1 extends Dream ? T_1["table"] : T_1 : never : never; allowPreload: true; allowLeftJoinPreload: false; allowLimit: true; allowOffset: true; allowPaginate: false; }>>; /** * Returns a new Query instance, attaching the provided * order statement * * ```ts * await User.order('id').all() * // [User{id: 1}, User{id: 2}, ...] * ``` * * ```ts * await User.order({ name: 'asc', id: 'desc' }).all() * // [User{name: 'a', id: 99}, User{name: 'a', id: 97}, User{ name: 'b', id: 98 } ...] * ``` * * @param orderStatement - Either a string or an object specifying order. If a string, the order is implicitly ascending. If the orderStatement is an object, statements will be provided in the order of the keys set in the object * @returns A query for this model with the order clause applied */ static order>(this: T, orderStatement: DreamColumnNames | Partial, OrderDir>> | null): Query>; /** * Plucks the provided fields from the corresponding model * * ```ts * await User.pluck('id') * // [1, 3, 2] * ``` * * If more than one column is requested, a multi-dimensional * array is returned: * * ```ts * await User.order('id').pluck('id', 'email') * // [[1, 'a@a.com'], [2, 'b@b.com']] * ``` * * @param columnNames - The column or array of columns to pluck * @returns An array of pluck results */ static pluck, ColumnNames extends TableColumnNames[], ReturnValue extends ColumnNames['length'] extends 1 ? BaseModelColumnTypes[0][] : BaseModelColumnTypes[]>(this: T, ...columnNames: ColumnNames): Promise; /** * Plucks the specified fields from the given dream class table * in batches, passing each found columns into the * provided callback function * * ```ts * await User.order('id').pluckEach('id', (id) => { * console.log(id) * }) * // 1 * // 2 * // 3 * ``` * * @param args - a list of fields to pluck, followed by a callback function to call for each set of found fields * @returns void */ static pluckEach, const ColumnNames extends unknown[]>(this: T, ...args: PluckEachArgs>, BaseModelColumnTypes>, I>>): Promise; static pluckEach, const ColumnNames extends TableColumnNames[]>(this: T, ...args: PluckEachArgs>): Promise; /** * Used in conjunction with the Sortable decorator, `resort` * takes a list of sortable fields, and for each one, finds and * sorts each record in the DB matching the field based on the * scope provided for each Sortable decorator found. * * Calling this method shouldn't be necessary, but if * the contents of the database have shifted without firing the * correct callback mechanisms at the application layer, calling * `resort` will ensure that all sortable fields are set from 1..n * with no gaps, accounting for the scopes specified in the * corresponding Sortable decorator. * * Each sort scope is renumbered in its own transaction, under the advisory * lock every direct position-mutating path takes on that scope, from a read * of the scope taken inside that lock — so a concurrent create or direct * destroy cannot make the renumbering land on positions it never read. A * cascaded destroy or undestroy takes no scope lock (see `@deco.Sortable`), * so one running at the same moment can still change a scope under `resort`; * a later `resort` repairs whatever that leaves. * * **Do not call `resort` from inside a transaction you own.** It opens those * transactions itself, on connections of its own, so it can neither join * yours nor roll back with it: its writes commit on their own. Worse, if your * transaction already holds the scope lock for a scope being renumbered — any * direct position-mutating write to that scope earlier in the transaction * takes it; a cascaded destroy or undestroy takes none — * `resort` waits on that lock from the outside, where no deadlock detector * can see the cycle, until the wait bound expires and it throws * `SortableScopeLockWaitTimedOut`. * * On an STI hierarchy the position space is shared by every class in the * hierarchy, exactly as it is for creates, updates and destroys. Calling * `resort` on a child class therefore renumbers the hierarchy's rows, not * only the receiver's. * * ```ts * class Post extends ApplicationModel { * @deco.Sortable({ scope: ['user']}) * public position: DreamColumn * } * * await Post.all() * // [Post{position: 1}, Post{position: 3}, Post{position: 5}] * * await Post.resort('position') * await Post.all() * // [Post{position: 1}, Post{position: 2}, Post{position: 3}] * ``` * * @param fields - A list of sortable fields to resort * @returns void * */ static resort(this: T, ...fields: DreamColumnNames>[]): Promise; /** * Returns a Query scoped to this model with * the specified scope applied. * * ```ts * class User extends ApplicationModel { * @deco.Scope() * public visible(query: Query) { * return query.where({ hidden: false }) * } * } * * await User.scope('visible').all() * // [User{hidden: false}, User{hidden: false}] * ``` * * @param scopeName - The name of the scope * @returns a Query scoped to this model with the specified scope applied */ static scope(this: T, scopeName: DefaultOrNamedScopeName>): Query>; /** * Returns the sql that would be executed by a Query * scoped to this model. * * ```ts * User.sql() * // { * // query: { * // kind: 'SelectQueryNode', * // from: { kind: 'FromNode', froms: [Array] }, * // selections: [ [Object] ], * // distinctOn: undefined, * // joins: undefined, * // groupBy: undefined, * // orderBy: undefined, * // where: { kind: 'WhereNode', where: [Object] }, * // frontModifiers: undefined, * // endModifiers: undefined, * // limit: undefined, * // offset: undefined, * // with: undefined, * // having: undefined, * // explain: undefined, * // setOperations: undefined * // }, * // sql: 'select "users".* from "users" where "users"."deleted_at" is null', * // parameters: [] * //} * ``` * * @returns An object representing the underlying sql statement * */ static sql(this: T): CompiledQuery; /** * Converts the given Dream class into a Kysely query, enabling * you to build custom queries using the Kysely API * * ```ts * await User.toKysely('select').where('email', '=', 'how@yadoin').execute() * ``` * * @param type - The type of Kysely query builder instance you would like to obtain * @returns A Kysely query. Depending on the type passed, it will return either a SelectQueryBuilder, DeleteQueryBuilder, UpdateQueryBuilder, or an InsertQueryBuilder */ static toKysely(this: T, type: QueryType): QueryType extends "select" ? import("kysely").SelectQueryBuilder["DB"], InstanceType["table"] & keyof InstanceType["DB"], unknown> : QueryType extends "delete" ? import("kysely").DeleteQueryBuilder["DB"], InstanceType["table"] & keyof InstanceType["DB"], unknown> : QueryType extends "update" ? import("kysely").UpdateQueryBuilder["DB"], InstanceType["table"] & keyof InstanceType["DB"], InstanceType["table"] & keyof InstanceType["DB"], unknown> : never; /** * Applies transaction to a new Query scoped * to this model * * ```ts * await ApplicationModel.transaction(async txn => { * await User.txn(txn).create({ email: 'how@yadoin' }) * }) * ``` * * @param txn - A DreamTransaction instance (usually collected by calling `ApplicationModel.transaction`) * @returns A Query scoped to this model with the transaction applied */ static txn>(this: T, txn: DreamTransaction | null): DreamClassTransactionBuilder; /** * Builds a new DreamTransaction instance, provides * the instance to the provided callback. * * ```ts * await ApplicationModel.transaction(async txn => { * const user = await User.txn(txn).create({ email: 'how@yadoin' }) * await Pet.txn(txn).create({ user }) * }) * ``` * * @param callback - A callback function to call. The transaction provided to the callback can be passed to subsequent database calls within the transaction callback * @returns void */ static transaction>) => unknown, RetType extends ReturnType>(this: T, callback: CB): Promise; /** * Sends data through for use as passthrough data * for the associations that require it. * * ```ts * class Post { * @deco.HasMany('LocalizedText') * public localizedTexts: LocalizedText[] * * @deco.HasOne('LocalizedText', { * where: { locale: DreamConst.passthrough }, * }) * public currentLocalizedText: LocalizedText * } * * await User.passthrough({ locale: 'es-ES' }) * .preload('posts', 'currentLocalizedText') * .first() * ``` * * @param passthroughWhereStatement - Where statement used for associations that require passthrough data * @returns A Query for this model with the passthrough data */ static passthrough, PassthroughColumns extends PassthroughColumnNames>(this: T, passthroughWhereStatement: PassthroughOnClause): Query>; /** * Applies a where statement to a new Query instance * scoped to this model * * ```ts * await User.where({ email: 'how@yadoin' }).first() * // User{email: 'how@yadoin'} * ``` * * @param whereStatement - Where statement to apply to the Query * @returns A Query for this model with the where clause applied */ static where>(this: T, whereStatement: WhereStatement): Query>; /** * Applies "OR"'d where statements to a Query scoped * to this model. * * ```ts * await User.whereAny([{ email: 'how@yadoin' }, { name: 'fred' }]).first() * // [User{email: 'how@yadoin'}, User{name: 'fred'}, User{name: 'fred'}] * ``` * * @param statements - a list of where statements to `OR` together * @returns A Query for this model with the whereAny clause applied */ static whereAny>(this: T, statements: WhereStatement[]): Query>; /** * Applies a whereNot statement to a new Query instance * scoped to this model. * * ```ts * await User.whereNot({ email: 'how@yadoin' }).first() * // User{email: 'hello@world'} * ``` * * @param attributes - A where statement to negate and apply to the Query * @returns A Query for this model with the whereNot clause applied */ static whereNot>(this: T, attributes: WhereStatement): Query>; /** * @internal * * Given a column, checks to see if it is a foreign key * belonging to a BelongsTo association * * ```ts * Post.isBelongsToAssociationForeignKey('id') * // false * Post.isBelongsToAssociationForeignKey('userId') * // true * ``` * * @param column - A column on this Dream class * @returns A boolean */ private static isBelongsToAssociationForeignKey; /** * @internal * * Given a column, checks to see if it is a belongs to * polymorphic type field * * ```ts * LocalizedText.isBelongsToAssociationPolymorphicTypeField('localizableId') * // false * LocalizedText.isBelongsToAssociationPolymorphicTypeField('localizableType') * // true * ``` * * @param column - a column on this dream class * @returns A boolean */ private static isBelongsToAssociationPolymorphicTypeField; /** * @internal * * Returns an array of column names that are belongs * to foreign keys on this dream class * * ```ts * Post.belongsToAssociationForeignKeys() * // ['userId'] * ``` * * @returns An array of column names that are belongs to foreign keys on this dream class */ private static belongsToAssociationForeignKeys; /** * @internal * * Returns all polymorphic type columns * * ```ts * LocalizedText.polymorphicTypeColumns() * // ['localizableType'] * ``` * * @returns An array of column names that are polymorphic type fields on the given dream class */ private static polymorphicTypeColumns; /** * @internal * * Returns a list of association names which * correspond to belongs to associations * on this model class. * * ```ts * Post.belongsToAssociationNames() * // ['user'] * ``` * * @returns An array of belongs to association names */ private static belongsToAssociationNames; /** * @internal * * Returns a list of association names which * have `dependent: destroy` set * * ```ts * Post.dependentDestroyAssociationNames() * // ['user'] * ``` * * @returns An array of HasOne/HasMany association names with `dependent: 'destroy'` defined */ private static dependentDestroyAssociationNames; /** * @internal * * Returns the metadata for the provided association * * ```ts * new Post()['getAssociationMetadata']('user') * // { * // modelCB: [Function (anonymous)], * // type: 'BelongsTo', * // as: 'user', * // optional: false, * // polymorphic: false, * // primaryKeyOverride: null, * // primaryKey: [Function: primaryKey], * // primaryKeyValue: [Function: primaryKeyValue], * // foreignKey: [Function: foreignKey], * // foreignKeyTypeField: [Function: foreignKeyTypeField] * // } * ``` * * @returns Association metadata * */ private getAssociationMetadata; /** * @internal * * Returns all association metadata for the given dream class * * ```ts * new Post()['associationMetadataMap']() * // { * // user: { * // modelCB: [Function (anonymous)], * // type: 'BelongsTo', * // as: 'user', * // optional: false, * // polymorphic: false, * // primaryKeyOverride: null, * // primaryKey: [Function: primaryKey], * // primaryKeyValue: [Function: primaryKeyValue], * // foreignKey: [Function: foreignKey], * // foreignKeyTypeField: [Function: foreignKeyTypeField] * // }, * // ratings: { * // modelCB: [Function (anonymous)], * // type: 'HasMany', * // as: 'ratings', * // polymorphic: true, * // source: 'ratings', * // and: undefined, * // andNot: undefined, * // selfAnd: undefined, * // selfAndNot: undefined, * // primaryKeyOverride: null, * // primaryKey: [Function: primaryKey], * // primaryKeyValue: [Function: primaryKeyValue], * // through: undefined, * // distinct: undefined, * // order: undefined, * // foreignKey: [Function: foreignKey], * // foreignKeyTypeField: [Function: foreignKeyTypeField] * // }, * // ... * // } * ``` * * @returns association metadata */ private associationMetadataMap; /** * @internal * * returns all association data for the given dream class, * organized by the type of association * * ```ts * new Post().associationMetadataByType * // { * // belongsTo: [ * // { * // modelCB: [Function (anonymous)], * // type: 'BelongsTo', * // as: 'user', * // optional: false, * // polymorphic: false, * // primaryKeyOverride: null, * // primaryKey: [Function: primaryKey], * // primaryKeyValue: [Function: primaryKeyValue], * // foreignKey: [Function: foreignKey], * // foreignKeyTypeField: [Function: foreignKeyTypeField] * // }, * // ], * // hasMany: [ * // ], * // hasOne: [] * // } * // } * ``` * * @returns association metadata by type * */ private get associationMetadataByType(); /** * @internal * * Returns an array of association names for the * given dream class * * ```ts * new Post().associationNames * // [ 'user', 'postVisibility', 'ratings', 'heartRatings' ] * ``` * * @returns association names array */ get associationNames(): string[]; /** * Returns true if any of the attributes on the instance * have changed since it was last pulled from the database. * On an unpersisted instance, it returns true when at least * one column has a value other than `undefined`. * * ```ts * const post = Post.new({ body: 'howyadoin' }) * post.isDirty * // true * * await post.save() * post.isDirty * // false * * post.body = 'groiyyyt' * post.isDirty * // true * ``` * * @returns A boolean */ get isDirty(): boolean; /** * Runs validation checks against all validations * declared using the Validate and Validates decorators, * and returns true if any of them fail. * * ```ts * class User extends ApplicationModel { * @deco.Validates('presence') * public email: DreamColumn * } * const user = User.new() * user.isInvalid * // true * * user.email = 'how@yadoin' * user.isInvalid * // false * ``` * * @returns A boolean */ get isInvalid(): boolean; /** * Returns true if the model has not been persisted * to the database. * * ```ts * const user = User.new({ email: 'howyadoin' }) * user.isNewRecord * // true * * await user.save() * user.isNewRecord * // false * ``` * * @returns A boolean */ get isNewRecord(): boolean; /** * Runs validation checks against all validations * declared using the Validate and Validates decorators. * Returns true if none of the validations fail. * * NOTE: Any validations that fail will leave the errors * field populated on your model instance. * * ```ts * class User extends ApplicationModel { * @deco.Validates('presence') * public email: DreamColumn * } * * const user = User.new() * user.isValid * // false * * user.email = 'how@yadoin' * user.isValid * // true * ``` * * @returns A boolean */ get isValid(): boolean; /** * @internal * * The primary key may be customized on a Dream model: * * ```ts * class User extends ApplicationModel { * public customIdField: DreamColumn * * public get primaryKey() { * return 'customIdField' as const * } * } * ``` * * @returns The primary key column name */ private get _primaryKey(); /** * Returns the value of the primary key * * @returns The value of the primary key field for this Dream instance */ primaryKeyValue(this: I): DreamPrimaryKeyType; /** * This must be defined on every model, and it must point * to a table in your database. In addition, you must * return the value using the `as const` suffix, like so: * * ```ts * class User extends ApplicationModel { * public override get table() { * return 'users' as const * } * } * ``` * * @returns The table name for this model */ get table(): AssociationTableNames; /** * Columns that Dream should behave as though they do not exist. * * Declaring a column ignored removes it from the generated types the next * time `sync` runs: it is omitted from both the db types file (the Kysely * `DB` interface) and the dream schema file, so it disappears from * `columns()` and from every place that flows from `columns()` — select * lists built for `preload`/`load` and `leftJoinPreload`, save hydration, * attribute definition, and param safety. References to the column in * application code become type errors, which is the point: they must be * removed before the column can be dropped. * * This enables safely dropping a column under rolling deploys — the same * problem Rails solves with `ignored_columns`. The safety requirement is * that no image that can run against the post-drop schema names the * column in any SQL it generates. To satisfy it: remove all application * code that uses the column, declare it here, and run `sync`; then let * the drop migration run only once no image lacking the declaration can * run against the database — whether the migration ships in a later * deploy of its own, or together with the declaration in a pipeline that * runs migrations only after the new images have rolled out. Once the * column is dropped, remove this declaration and resync. * * Precondition: as soon as an image built with this declaration runs, * the column is never placed in INSERT column lists, so before that * image can run, the column must be nullable or carry a database * default — a live `NOT NULL` column without a default fails every * create against the table. * * Dropping the column while an image that names it can still run leaves * a window during which those containers (including a rolled-back image) * fail with `42703 column does not exist` — `leftJoinPreload` in * particular has no runtime tolerance for this, since it must enumerate * aliased columns. * * This is a mechanism for the drop window, not for permanently hiding * wide columns: the ignored column is still transferred from the * database on every `RETURNING *` / `select *` until it is actually * dropped. * * The declaration is read only while `sync` generates the types files; it * has no runtime behavior of its own. A declared-but-not-synced model is * therefore not yet protected — CI should verify that `sync` produces no * diff. `sync` will fail loudly if a declared name is not camelCase * (generated column names are camelized, so any other shape could never * match and would be silently inert), if models sharing a table declare * different ignored columns, or if a model attempts to ignore a column * the framework itself reads and writes by name: its primary key, an STI * model's `type` column, any association's foreign key, polymorphic type * field, or `primaryKeyOverride` column, an `@Sortable` position field or * plain-column `@Sortable` scope, an `@Encrypted` backing column, or a * SoftDelete model's `deletedAt` column. * * Because an ignored column vanishes from `columns()`, runtime access via * type escape hatches behaves exactly like any unknown attribute: reads * return `undefined`, and writes assign a plain instance property that is * never persisted. * * ```ts * class User extends ApplicationModel { * public override get ignoredColumns() { * return ['legacyEmail'] as const * } * } * ``` * * NOTE: this getter is intentionally typed as `readonly string[]` rather * than as a union of known column names: during the deploy that declares * a column ignored, the regenerated types no longer contain the column, * so a column-name-derived type would reject the very declaration that * removed it. * * @returns The list of column names this model should ignore */ get ignoredColumns(): readonly string[]; /** * @internal * * _errors is used to inform validation errors, * and is built whenever validations are run on * a dream instance. */ private _errors; /** * @internal * * Used for the changes api */ private frozenAttributes; /** * @internal * * Used for the changes api */ private originalAttributes; /** * @internal * * Used for the changes api */ private currentAttributes; /** * @internal * * Used for the changes api */ private attributesFromBeforeLastSave; /** * @internal * * Stores whether the record has been persisted or not */ private _isPersisted; /** * Returns true if the model has been persisted * to the database. * * ```ts * const user = User.new({ email: 'howyadoin' }) * user.isPersisted * // false * * await user.save() * user.isPersisted * // true * ``` * * @returns A boolean */ get isPersisted(): boolean; private set isPersisted(value); /** * Since typescript prevents constructor functions * from absorbing type generics, we provide the `new` * method to instantiate with type protection * * ```ts * const user = User.new({ email: 'how@yadoin' }) * ``` * * @param opts - the attributes to set on the new instance * @param additionalOpts - optional parameters * @param additionalOpts.bypassUserDefinedSetters - if true, sets the attributes * as {@link Dream.setAttributes | setAttributes} does, bypassing any * custom-defined setters. An encrypted property (e.g. `secret`) is still * encrypted, and an `@Encrypted` backing column (e.g. `encryptedSecret`) * accepts only ciphertext that decrypts with this app's column encryption * keys, or `null`; anything else throws (see * {@link Dream.setAttribute | setAttribute}). Defaults to false * @returns A new (unpersisted) instance of the provided dream class */ static new(this: T, opts?: UpdateablePropertiesForClass, additionalOpts?: { bypassUserDefinedSetters?: boolean; }): InstanceType; /** * @internal * * NOTE: avoid using the constructor function directly. * Use the static `.new` or `.create` methods instead, which * will provide type guarding for your attributes. * * Since typescript prevents constructor functions * from absorbing type generics, we provide the `new` * method to instantiate with type protection. * * ```ts * const user = User.new({ email: 'how@yadoin' }) * ``` * * @returns A new (unpersisted) instance of the provided dream class */ constructor(opts: any, additionalOpts: { bypassUserDefinedSetters?: boolean; fromDatabase?: boolean; isPersisted?: boolean; _internalUseOnly: true; }); /** * @internal * * Used for determining which attributes to update */ private static extractAttributesFromUpdateableProperties; /** * @internal * * defines attribute setters and getters for every column * set within your types/dream.ts file */ private static defineAttributeAccessors; /** * @internal * * Modern Javascript applies implicit accessors to instance properties that * shadow prototype accessors applied by Dream. This method is called after * every Dream model is initialized to delete the instance accessors so that * the prototype accessors can be reached. */ private unshadowColumnPropertyPrototypeAccessors; private finalizeConstruction; /** * @internal * * Returns true if the column is a column in the database * * @param columnName - the name of the property you are checking for * @returns boolean */ isColumn(this: T, columnName: string): boolean; /** * Returns true if the columnName passed is marked by a * Virtual attribute decorator * * @param columnName - A property on this model to check * @returns A boolean */ isVirtualColumn(this: T, columnName: string): boolean; /** * Returns an object with column names for keys, and an * array of strings representing validation errors for values. * * @returns An error object */ get errors(): { [key: string]: ValidationType[]; }; /** * Adds an error to the model. Any errors added to the model * will cause the model to be invalid, and will prevent the * model from saving. * * ```ts * class User extends ApplicationModel { * ... * @deco.Validate() * public async validateName() { * if (typeof this.name === 'number') * this.addError('name', 'name cannot be a number') * } * } * ``` * * @returns void */ addError>(this: I, column: Key & string, error: string): void; /** * Changes the attribute value for a single attribute, * leveraging any custom-defined setters. If you would like to * bypass custom-defined setters, use #setAttribute instead * * ```ts * const user = new User() * user.assignAttribute('email', 'sally@gmail.com') * ``` */ assignAttribute>(this: I, attr: Key & string, val: any): void; /** * Changes the attribute value for a single attribute internally, * bypassing any custom-defined setters. If you would like to set attributes * without bypassing custom-defined setters, use #assignAttribute instead * * An `@Encrypted` backing column (e.g. `encryptedSecret`) accepts only * a string of ciphertext that decrypts with this app's column encryption * keys (current or legacy), or `null`, which clears it. Anything else, * including plaintext, `undefined`, a non-string value, and ciphertext * made with a key this app does not hold, throws and leaves the column * unchanged. The check covers the backing columns of this model's class, * including those it inherits from an STI parent; a backing column * declared only by another class in the same STI hierarchy is not * checked. * * Given the name of an encrypted property (e.g. `secret`), setAttribute * neither encrypts the value nor stores it: it keeps the value, * unencrypted, under that name in this instance's attributes, which a * save does not write, while the backing column and the property keep * the value they had. To store a new value, assign the property * (`user.secret = 'new secret'`) or pass it to * {@link Dream.setAttributes | setAttributes}, each of which encrypts it. * * ```ts * const user = new User() * user.setAttribute('email', 'sally@gmail.com') * ``` */ setAttribute>(this: I, column: Key & string, val: any): void; /** * @internal * * Writes the value into the current attributes as given, with none of * setAttribute's checks. Only for values Dream already trusts: a row read * from the database, and the ciphertext the @Encrypted property setter * writes (freshly encrypted, or the ciphertext as loaded or last saved, * which it keeps when that already holds the assigned plaintext). Every * caller-supplied value goes through setAttribute. */ private setAttributeUnchecked; /** * Returns the value for a columnName provided, * bypassing the getters. * * ```ts * const user = User.new({ email: 'how@yadoin' }) * user.getAttribute('email') // 'how@yadoin' * ``` */ getAttribute>(this: I, column: Key & string): DreamAttributes[Key]; /** * Returns an object containing all the columns * on this dream class, as well as their values, * bypassing getters. * * ```ts * const user = User.new({ email: 'how@yadoin' }) * user.getAttributes() * // { * // email: 'how@yadoin', * // ... * // } * ``` */ getAttributes(this: I): Selectable; /** * Returns the attributes that have changed since * being persisted to the database, with the values * that were last persisted to the database. * * On a record that has not been persisted, only columns * with values other than `undefined` count as changed. * * ```ts * const user = User.new({ email: 'original@email', password: 'howyadoin' }) * await user.save() * * user.email = 'new@email' * user.changedAttributes() * // { email: 'original@email' } * * user.email = 'original@email' * user.changedAttributes() * // {} * ``` * * @returns An object containing changed attributes and their original values */ changedAttributes & keyof DB = I['table'] & AssociationTableNames, Table extends DB[keyof DB] = DB[TableName]>(this: I): Partial>; /** * Returns an object containing the attributes that have * changed since last persisting, along with their current * and previously persisted values. * * For a column with an unsaved change, `was` is its value * as of the most recent save. For every other column, `was` * is its value from before that save, so the changes the * save made keep appearing, beside any later unsaved change, * until the next save. * * On a create, that earlier value is `undefined` for every * column of an instance from `new` or `create`, assigned or * not, and the copied value for an instance from * {@link Dream.dup | dup}. So after a create from `new` or * `create`, every column the insert returned counts as * changed: the primary key, timestamps, database defaults, * and `null` for each nullable column nobody assigned. After * a create from `dup`, the columns `dup` cleared (the primary * key, timestamps and sortable positions) count, and a copied * column counts only if the insert returned a different value. * * ```ts * const pet = Pet.new({ species: 'dog' }) * pet.changes() * // { * // species: { was: undefined, now: 'dog' }, * // } * * await pet.save() * pet.changes() * // { * // id: { was: undefined, now: 1 }, * // species: { was: undefined, now: 'dog' }, * // name: { was: undefined, now: null }, * // ...every other column the insert returned * // } * * pet.species = 'cat' * pet.species = 'frog' * pet.changes() * // { * // id: { was: undefined, now: 1 }, * // species: { was: 'dog', now: 'frog' }, * // name: { was: undefined, now: null }, * // ...every other change the create made * // } * * await pet.save() * pet.changes() * // { * // species: { was: 'dog', now: 'frog' }, * // } * // (and `updatedAt`, on a model with that column) * ``` * * @returns An object containing changed attributes */ changes, { was: Selectable[DreamColumnNames]; now: Selectable
[DreamColumnNames]; }>>>(this: I): RetType; /** * Returns the value the column held before the most * recent save. For a column with an unsaved change, it * returns the column's value as of that save instead. * * On a create, the value before the save is `undefined` * for every column of an instance from `new` or `create`, * assigned or not, and the copied value for an instance * from {@link Dream.dup | dup} (`undefined` for the * columns `dup` cleared). So after a create from `new` or * `create`, this returns `undefined` for every column * without an unsaved change, not the value the insert * wrote. After an update, it returns the column's value * from before that update, which for a column the update * did not change is its current value. On a record loaded * from the database and not saved since, it returns the * loaded value. * * ```ts * const pet = Pet.new({ species: 'cat' }) * pet.previousValueForAttribute('species') * // undefined * * await pet.save() * pet.previousValueForAttribute('species') * // undefined * * pet.species = 'dog' * pet.previousValueForAttribute('species') * // 'cat' * * await pet.save() * pet.previousValueForAttribute('species') * // 'cat' * * await pet.update({ species: 'cat' }) * pet.previousValueForAttribute('species') * // 'dog' * ``` * * @param columnName - The column name you want the previous value for * @returns Returns the previous value for an attribute */ previousValueForAttribute>(this: I, columnName: ColumnName): Selectable
[ColumnName]; /** * Returns true if the columnName provided has * changes that were persisted during the most * recent save. An unsaved change does not count, * and an unsaved edit made after that save does * not undo a change the save made. On a record * that has not been persisted, this returns false. * * A create compares the value the insert returned * with `undefined` for every column of an instance * from `new` or `create`, assigned or not, and with * the copied value for an instance from * {@link Dream.dup | dup}. So after a create from * `new` or `create`, this returns true for every * column the insert returned: the primary key, * timestamps, database defaults, and a nullable * column nobody assigned, which comes back `null`. * To react only to the columns a create assigned, * use the `ifChanged` option of an after hook. * * ```ts * const pet = await Pet.create({ species: 'cat' }) * pet.savedChangeToAttribute('species') * // true * pet.savedChangeToAttribute('name') * // true (unassigned, so the insert returned null) * * await pet.update({ name: 'Snoopy' }) * pet.savedChangeToAttribute('species') * // false * * pet.species = 'dog' * pet.savedChangeToAttribute('species') * // false (not saved yet) * ``` * * @param columnName - the column name to check * @returns A boolean */ savedChangeToAttribute(this: I, columnName: DreamColumnNames): boolean; /** * Returns true if the columnName provided has * changes that have not yet been persisted. * * On a record that has not been persisted, this returns * true only for a column with a value other than `undefined`. * An explicit `null` counts as a change. * * @param attribute - the column name to check * @returns A boolean */ willSaveChangeToAttribute(this: I, attribute: DreamColumnNames): boolean; /** * Returns the column names for the given model * * @returns The column names for the given model */ columns(this: I): Set; /** * Returns an object containing the column names * of columns that have changed since last persist, * and their current values. * * On a record that has not been persisted, the object * contains only columns with values other than `undefined`. * An explicit `null` is included. * * ```ts * const user = User.new({ email: 'hello@world' }) * user.dirtyAttributes() * // { email: 'hello@world' } * * await user.save() * * user.email = 'hello@world' * user.dirtyAttributes() * // {} * * user.email = 'goodbye@world' * user.dirtyAttributes() * // { email: 'goodbye@world' } * * user.email = 'hello@world' * user.dirtyAttributes() * // {} * ``` * * @returns An object containing the changed attributes */ dirtyAttributes & keyof DB = I['table'] & AssociationTableNames, Table extends DB[keyof DB] = DB[TableName]>(this: I): Partial>; /** * Returns true if an attribute has changes since last persist * * @returns A boolean */ private attributeIsDirty; /** * Deletes the record represented by this instance * from the database, calling any destroy * hooks on this model. * * ```ts * const user = await User.last() * await user.destroy() * ``` * * @param options - Options for destroying the instance * @param options.skipHooks - If true, skips applying model hooks during the destroy operation. Defaults to false * @param options.cascade - If false, skips destroying associations marked `dependent: 'destroy'`. Defaults to true * @param options.bypassAllDefaultScopes - If true, bypasses user-removable default scopes when cascade destroying; an STI child's reserved discriminator remains enforced, including when associations are loaded. Defaults to false * @param options.defaultScopesToBypass - An array of default scope names to bypass when cascade destroying. Defaults to an empty array * @returns The instance that was destroyed */ destroy(this: I, options?: InstanceDestroyOptions): Promise; /** * Deletes the record represented by this instance * from the database, calling any destroy * hooks on this model. * * If the record being destroyed is using * a SoftDelete decorator, the soft delete * will be bypassed, causing the record * to be permanently removed from the database. * * ```ts * const user = await User.last() * await user.reallyDestroy() * ``` * * @param options - Options for destroying the instance * @param options.skipHooks - If true, skips applying model hooks during the destroy operation. Defaults to false * @param options.cascade - If false, skips destroying associations marked `dependent: 'destroy'`. Defaults to true * @param options.bypassAllDefaultScopes - If true, bypasses user-removable default scopes when cascade destroying; an STI child's reserved discriminator remains enforced, including when associations are loaded. Defaults to false * @param options.defaultScopesToBypass - An array of default scope names to bypass when cascade destroying. Defaults to an empty array * @returns The instance that was destroyed */ reallyDestroy(this: I, options?: InstanceDestroyOptions): Promise; /** * Undestroys a SoftDelete model, unsetting * the `deletedAt` field in the database. * * If the model is not a SoftDelete model, * this will raise an exception. * * ```ts * const user = await User.removeDefaultScope('dream:SoftDelete').last() * await user.undestroy() * ``` * * @param options - Options for undestroying the instance * @param options.skipHooks - If true, skips applying model hooks during the undestroy operation. Defaults to false * @param options.cascade - If false, skips undestroying associations marked `dependent: 'destroy'`. Defaults to true * @param options.bypassAllDefaultScopes - If true, bypasses user-removable default scopes when cascade undestroying; an STI child's reserved discriminator remains enforced, including when associations are loaded. Defaults to false * @param options.defaultScopesToBypass - An array of default scope names to bypass when cascade undestroying (soft delete is always bypassed). Defaults to an empty array * @returns The undestroyed record */ undestroy(this: I, options?: InstanceDestroyOptions): Promise; /** * Returns true if the argument is the same Dream class * with the same primary key value. * * NOTE: This does not compare attribute values other than * the primary key. * * @returns A boolean */ equals(other: any): boolean; get comparisonKey(): string; /** * @internal * * Used for changes API */ private freezeAttributes; /** * Deep clones the model and its non-association attributes. * Unsets primaryKey, created and updated fields. * * @returns Non-persisted, cloned Dream instance */ dup(this: I): I; /** * Deep clones the model and it's attributes, but maintains references to * loaded associations */ private clone; /** * Creates an association for an instance. Automatically * handles setting foreign key and, in the case of polymorphism, * foreign key type. * * ```ts * await user.createAssociation('posts', { body: 'hello world' }) * ``` * * @param associationName - the name of the association to create * @param attributes - the attributes with which to create the associated model * @returns The created association */ createAssociation, PossibleArrayAssociationType extends I[AssociationName & keyof I], AssociationType extends PossibleArrayAssociationType extends (infer ElementType)[] ? ElementType : PossibleArrayAssociationType, RestrictedAssociationType extends AssociationType extends Dream ? AssociationType : never>(this: I, associationName: AssociationName, attributes?: UpdateableAssociationProperties): Promise>; destroyAssociation, RequiredOnClauseKeysForThisAssociation extends RequiredOnClauseKeys, AssociationDream extends AssociationNameToDream, AssociationTableName extends AssociationDream['table']>(this: I, associationName: AssociationName, options: InstanceDestroyOptions & JoinAndStatements): Promise; destroyAssociation, AssociationDream extends AssociationNameToDream, AssociationTableName extends AssociationDream['table']>(this: I, associationName: AssociationName, options?: InstanceDestroyOptions & JoinAndStatements): Promise; reallyDestroyAssociation, RequiredOnClauseKeysForThisAssociation extends RequiredOnClauseKeys, AssociationDream extends AssociationNameToDream, AssociationTableName extends AssociationDream['table']>(this: I, associationName: AssociationName, options: InstanceDestroyOptions & JoinAndStatements): Promise; reallyDestroyAssociation, AssociationDream extends AssociationNameToDream, AssociationTableName extends AssociationDream['table']>(this: I, associationName: AssociationName, options?: InstanceDestroyOptions & JoinAndStatements): Promise; undestroyAssociation, RequiredOnClauseKeysForThisAssociation extends RequiredOnClauseKeys, AssociationDream extends AssociationNameToDream, AssociationTableName extends AssociationDream['table']>(this: I, associationName: AssociationName, options: InstanceDestroyOptions & JoinAndStatements): Promise; undestroyAssociation, AssociationDream extends AssociationNameToDream, AssociationTableName extends AssociationDream['table']>(this: I, associationName: AssociationName, options?: InstanceDestroyOptions & JoinAndStatements): Promise; associationQuery, RequiredOnClauseKeysForThisAssociation extends RequiredOnClauseKeys, AssociationDream extends AssociationNameToDream, AssociationTableName extends AssociationDream['table']>(this: I, associationName: AssociationName, joinAndStatements: JoinAndStatements): Query>; associationQuery, AssociationDream extends AssociationNameToDream, AssociationTableName extends AssociationDream['table']>(this: I, associationName: AssociationName, joinAndStatements?: JoinAndStatements): Query>; updateAssociation, RequiredOnClauseKeysForThisAssociation extends RequiredOnClauseKeys, AssociationDream extends AssociationNameToDream, AssociationTableName extends AssociationDream['table'], PossibleArrayAssociationType extends I[AssociationName & keyof I], AssociationType extends PossibleArrayAssociationType extends (infer ElementType)[] ? ElementType : PossibleArrayAssociationType, RestrictedAssociationType extends AssociationType extends Dream ? AssociationType : never>(this: I, associationName: AssociationName, attributes: UpdateableAssociationProperties, updateAssociationOptions: InstanceUpdateOptions & { bypassAllDefaultScopes?: boolean; defaultScopesToBypass?: AllDefaultScopeNames[]; } & JoinAndStatements): Promise; updateAssociation, AssociationDream extends AssociationNameToDream, AssociationTableName extends AssociationDream['table'], PossibleArrayAssociationType extends I[AssociationName & keyof I], AssociationType extends PossibleArrayAssociationType extends (infer ElementType)[] ? ElementType : PossibleArrayAssociationType, RestrictedAssociationType extends AssociationType extends Dream ? AssociationType : never>(this: I, associationName: AssociationName, attributes: UpdateableAssociationProperties, updateAssociationOptions?: InstanceUpdateOptions & { bypassAllDefaultScopes?: boolean; defaultScopesToBypass?: AllDefaultScopeNames[]; } & JoinAndStatements): Promise; /** * Sends data through for use as passthrough data * for the associations that require it. * * ```ts * class Post { * @deco.HasMany('LocalizedText') * public localizedTexts: LocalizedText[] * * @deco.HasOne('LocalizedText', { * where: { locale: DreamConst.passthrough }, * }) * public currentLocalizedText: LocalizedText * } * * await User.passthrough({ locale: 'es-ES' }) * .preload('posts', 'currentLocalizedText') * .first() * ``` * * @param passthroughWhereStatement - where statement used for associations that require passthrough data * @returns A cloned Query with the passthrough data */ passthrough>(this: I, passthroughWhereStatement: PassthroughOnClause): LoadBuilder; /** * Loads the requested associations upon execution. * * **IMPORTANT:** `load().execute()` returns a **new clone** of the model with the * associations loaded. It does NOT modify the original instance. You must use the * returned value: * * ```ts * // CORRECT — use the returned clone: * const loaded = await user.load('posts').execute() * loaded.posts // works * * // WRONG — original is not modified: * await user.load('posts').execute() * user.posts // association not loaded! * ``` * * NOTE: {@link Dream.preload} is often a preferrable way of achieving the * same goal. Alternatively, `await model.association('posts')` loads only * if the association is not already loaded. * * ```ts * const loadedUser = await user * .load('posts', { and: { body: ops.ilike('%hello world%') } }, 'comments', 'replies') * .load('images') * .execute() * * loadedUser.posts[0].comments[0].replies[0] * // Reply{} * * loadedUser.images[0] * // Image{} * ``` * * @param args - A chain of association names and and/andNot/andAny clauses * @returns A chainable LoadBuilder instance. Call `.execute()` to get the cloned model with associations loaded. */ load(this: I, ...args: [...Arr, VariadicLoadArgs]): LoadBuilder; /** * If the association is already loaded on the instance, it returns the loaded value * immediately without making a database query. If the association is not loaded, * it performs a database query to fetch the association. * * If a query is performed, the association is set on the model so that future calls to * access this association can access the already loaded value. * * For HasMany associations, returns an array (empty if no records). For BelongsTo/HasOne * associations, returns the associated model or null if not found. * * @remarks * **Warning:** Using `association` in loops or when processing multiple records can lead to the * [N+1 query problem](https://guides.rubyonrails.org/active_record_querying.html#eager-loading-associations). * Each call to `association` on an unloaded association will trigger a separate database query. * To avoid this, use {@link Dream.load | `load`} or {@link Dream.preload | `preload`} to eager load * associations before accessing them. * * ```ts * // Basic association loading * const user = await post.association('user') * const comments = await post.association('comments') * * // With required clause (for associations with DreamConst.required `and` clauses) * const pets = await user.association('petsWithRequiredName', { * required: { name: 'Woodstock' } * }) * * // With passthrough clause (for associations with DreamConst.passthrough `and` clauses) * const pets = await user.association('petsWithPassthroughName', { * passthrough: { name: 'Woodstock' } * }) * * // With both required and passthrough clauses (for associations with DreamConst.required and DreamConst.passthrough `and` clauses) * const text = await composition.association('passthroughAndRequiredCurrentLocalizedText', { * passthrough: { name: 'My Composition' }, * required: { locale: 'de-DE' } * }) * * // With a specific database connection * const comments = await post.association('comments', { connection: 'replica' }) * ``` * * @param associationName - The name of the BelongsTo, HasOne, or HasMany association * @param options - Optional configuration for required and passthrough clauses and the database connection * @param options.required - for associations with DreamConst.required `and` clauses * @param options.passthrough - for associations with DreamConst.passthrough `and` clauses * @param options.connection - the database connection to use when loading the association ('primary' or 'replica') * @returns The associated model instance(s) or null/empty array if not found */ association | DreamHasOneAssociationNames | DreamHasManyAssociationNames, AssociationName extends MaybeAssociationName extends DreamAssociationNamesWithPassthroughOnClauses | DreamAssociationNamesWithRequiredOnClauses ? MaybeAssociationName : never, AssociationDream extends AssociationNameToDream, AssociationTableName extends AssociationDream['table'], DB extends I['DB'], TableName extends I['table'], Schema extends I['schema'], PassthroughOnStatement extends Required & string[]>>, RequiredOnStatement extends Required & string[]>>, Opts extends AssociationName extends DreamAssociationNamesWithPassthroughOnClauses & DreamAssociationNamesWithRequiredOnClauses ? { passthrough: PassthroughOnStatement; required: RequiredOnStatement; connection?: DbConnectionType; } : AssociationName extends DreamAssociationNamesWithPassthroughOnClauses ? { passthrough: PassthroughOnStatement; connection?: DbConnectionType; } : AssociationName extends DreamAssociationNamesWithRequiredOnClauses ? { required: RequiredOnStatement; connection?: DbConnectionType; } : never, ReturnType extends AssociationName extends DreamHasManyAssociationNames ? AssociationNameToDream[] : AssociationNameToDream | null>(this: I, associationName: AssociationName, options: Opts): Promise; association | DreamHasOneAssociationNames | DreamHasManyAssociationNames, AssociationName extends MaybeAssociationName extends DreamAssociationNamesWithPassthroughOnClauses | DreamAssociationNamesWithRequiredOnClauses ? never : MaybeAssociationName, ReturnType extends AssociationName extends DreamHasManyAssociationNames ? AssociationNameToDream[] : AssociationNameToDream | null>(this: I, associationName: AssociationName, options?: { connection?: DbConnectionType; }): Promise; /** * If the association is already loaded on the instance, it returns the loaded value * immediately without making a database query. If the association is not loaded, * it performs a database query to fetch the association. * * Unlike `association`, this method throws an exception if no associated * record is found for BelongsTo/HasOne associations, guaranteeing a non-null result. * For HasMany associations, returns an empty array instead of throwing when no records exist. * * If a query is performed, the association is set on the model so that future calls to * access this association can access the already loaded value. * * For HasMany associations, returns an array (empty if no records). For BelongsTo/HasOne * associations, returns the associated model or throws RecordNotFound if not found. * * @remarks * **Warning:** Using `associationOrFail` in loops or when processing multiple records can lead to the * [N+1 query problem](https://guides.rubyonrails.org/active_record_querying.html#eager-loading-associations). * Each call to `associationOrFail` on an unloaded association will trigger a separate database query. * To avoid this, use {@link Dream.load | `load`} or {@link Dream.preload | `preload`} to eager load * associations before accessing them. * * ```ts * // Basic association loading - throws RecordNotFound if user doesn't exist * const user = await post.associationOrFail('user') * * // HasMany - returns empty array if no comments, never throws * const comments = await post.associationOrFail('comments') * * // With required clause (for associations with DreamConst.required `and` clauses) * const pets = await user.associationOrFail('petsWithRequiredName', { * required: { name: 'Woodstock' } * }) * * // With passthrough clause (for associations with DreamConst.passthrough `and` clauses) * const pets = await user.associationOrFail('petsWithPassthroughName', { * passthrough: { name: 'Woodstock' } * }) * * // With both required and passthrough clauses (for associations with DreamConst.required and DreamConst.passthrough `and` clauses) * const text = await composition.associationOrFail('passthroughAndRequiredCurrentLocalizedText', { * passthrough: { name: 'My Composition' }, * required: { locale: 'de-DE' } * }) * * // With a specific database connection * const comments = await post.associationOrFail('comments', { connection: 'replica' }) * ``` * * @param associationName - The name of the BelongsTo, HasOne, or HasMany association * @param options - Optional configuration for required and passthrough clauses and the database connection * @param options.required - for associations with DreamConst.required `and` clauses * @param options.passthrough - for associations with DreamConst.passthrough `and` clauses * @param options.connection - the database connection to use when loading the association ('primary' or 'replica') * @returns The associated model instance(s) (never null for BelongsTo/HasOne) * @throws RecordNotFound if no associated record exists (BelongsTo/HasOne only) */ associationOrFail | DreamHasOneAssociationNames | DreamHasManyAssociationNames, AssociationName extends MaybeAssociationName extends DreamAssociationNamesWithPassthroughOnClauses | DreamAssociationNamesWithRequiredOnClauses ? MaybeAssociationName : never, AssociationDream extends AssociationNameToDream, AssociationTableName extends AssociationDream['table'], DB extends I['DB'], TableName extends I['table'], Schema extends I['schema'], PassthroughOnStatement extends Required & string[]>>, RequiredOnStatement extends Required & string[]>>, Opts extends AssociationName extends DreamAssociationNamesWithPassthroughOnClauses & DreamAssociationNamesWithRequiredOnClauses ? { passthrough: PassthroughOnStatement; required: RequiredOnStatement; connection?: DbConnectionType; } : AssociationName extends DreamAssociationNamesWithPassthroughOnClauses ? { passthrough: PassthroughOnStatement; connection?: DbConnectionType; } : AssociationName extends DreamAssociationNamesWithRequiredOnClauses ? { required: RequiredOnStatement; connection?: DbConnectionType; } : never, ReturnType extends AssociationName extends DreamHasManyAssociationNames ? AssociationNameToDream[] : AssociationNameToDream>(this: I, associationName: AssociationName, options: Opts): Promise; associationOrFail | DreamHasOneAssociationNames | DreamHasManyAssociationNames, AssociationName extends MaybeAssociationName extends DreamAssociationNamesWithPassthroughOnClauses | DreamAssociationNamesWithRequiredOnClauses ? never : MaybeAssociationName, ReturnType extends AssociationName extends DreamHasManyAssociationNames ? AssociationNameToDream[] : AssociationNameToDream>(this: I, associationName: AssociationName, options?: { connection?: DbConnectionType; }): Promise; /** * Recursively loads all Dream associations referenced by `rendersOne` and `rendersMany` * in a DreamSerializer. This traverses the entire content tree of serializers to automatically * load all necessary associations, eliminating N+1 query problems and removing the need to * manually remember which associations to preload for serialization. * * This method decouples data loading code from data rendering code by having the serializer * (rendering code) inform the query (loading code) about which associations are needed. * As serializers evolve over time - adding new `rendersOne` and `rendersMany` calls or * modifying existing ones - the loading code automatically adapts without requiring * corresponding modifications to preload statements. * * This method analyzes the serializer (specified by `serializerKey` or 'default') and * automatically preloads all associations that will be needed during serialization. * * ```ts * // Instead of manually specifying all associations: * await User.preload('posts', 'comments', 'replies').all() * * // Automatically preload everything needed for serialization: * await user.loadFor('summary').execute() * * // Add where conditions to specific associations during preloading: * await user.loadFor('detailed', (associationName, dreamClass) => { * if (dreamClass.typeof(Post) && associationName === 'comments') { * return { and: { published: true } } * } * }) * .execute() * * // Skip preloading specific associations to handle them manually: * await user * .loadFor('summary', (associationName, dreamClass) => { * if (dreamClass.typeof(User) && associationName === 'posts') { * return 'omit' // Handle posts preloading separately with custom logic * } * }) * .load('posts', { and: { featured: true } }) // Custom preloading * .execute() * ``` * * @param serializerKey - The serializer key to use for determining which associations to preload. * @param modifierFn - Optional callback function to modify or omit specific associations during preloading. Called for each association with the Dream class and association name. Return an object with `and`, `andAny`, or `andNot` properties to add where conditions, return 'omit' to skip preloading that association (useful when you want to handle it manually), or return undefined to use default preloading * @returns A Query with all serialization associations preloaded */ loadFor>(this: I, serializerKey: SerializerKey, modifierFn?: LoadForModifierFn): LoadBuilder; /** * Load each specified association using a single SQL query. * See {@link Dream.load} for loading in separate queries. * * **IMPORTANT:** Like `load()`, `leftJoinLoad().execute()` returns a **new clone** of the * model with associations loaded. It does NOT modify the original instance. * * Note: since leftJoinLoad loads via single query, it has * some downsides that may be avoided using {@link Dream.load}: * 1. `limit` and `offset` will be automatically removed * 2. `through` associations will bring additional namespaces into the query that can conflict with through associations from other associations, creating an invalid query * 3. each nested association will result in an additional record which duplicates data from the outer record. E.g., given `.leftJoinLoad('a', 'b', 'c')`, if each `a` has 10 `b` and each `b` has 10 `c`, then for one `a`, 100 records will be returned, each of which has all of the columns of `a`. `.load('a', 'b', 'c')` would perform three separate SQL queries, but the data for a single `a` would only be returned once. * 4. the individual query becomes more complex the more associations are included * 5. associations loading associations loading associations could result in exponential amounts of data; in those cases, `.load(...).findEach(...)` avoids instantiating massive amounts of data at once * * ```ts * const loadedUser = await user * .leftJoinLoad('posts', { and: { body: ops.ilike('%hello world%') } }, 'comments', 'replies') * .leftJoinLoad('images') * .execute() * * loadedUser.posts[0].comments[0].replies[0] * // Reply{} * * loadedUser.images[0] * // Image{} * ``` * * @param args - A chain of association names and and/andNot/andAny clauses * @returns A chainable LeftJoinLoadBuilder instance. Call `.execute()` to get the cloned model with associations loaded. */ leftJoinLoad(this: I, ...args: [...Arr, VariadicLeftJoinLoadArgs]): LeftJoinLoadBuilder; /** * Returns true if the association specified has * been loaded on this instance * * Since accessing associations that haven't been * loaded/preloaded will raise an exception, `loaded` * is useful for conditionally loading from the database * in contexts where an association may not yet be loaded. * * ```ts * user.loaded('posts') * // false * * user = await user.load('posts').execute() * user.loaded('posts') * // true * ``` * * @param associationName - the association name you wish to check the loading of * @returns A boolean */ loaded>(this: I, associationName: AssociationName): boolean; /** * Reloads an instance, refreshing all it's attribute values * to those in the database. * * NOTE: this does not refresh associations * * ```ts * await user.reload() * ``` * * @returns void */ reload(this: I): Promise; /** * Takes the attributes passed in and sets their values, * leveraging any custom setters defined for these attributes. * * NOTE: * To bypass custom-defined setters, use `#setAttributes` instead. * * ```ts * const user = new User() * user.assignAttributes({ email: 'my@email', password: 'my password' }) * ``` */ assignAttributes(this: I, attributes: UpdateableProperties): Partial | { [x: string]: any; }>>; /** * Takes the attributes passed in and sets their values internally, * bypassing any custom setters defined for these attributes. * * An encrypted property (e.g. `secret`) is still encrypted. Its * `@Encrypted` backing column (e.g. `encryptedSecret`) accepts only * ciphertext that decrypts with this app's column encryption keys, or * `null`; anything else throws (see {@link Dream.setAttribute | setAttribute}). * Attributes are set in order, so those before the rejected one have * already been set when it throws. * * NOTE: * To leverage custom-defined setters, use `#assignAttributes` instead. * * ```ts * const user = new User() * user.setAttributes({ email: 'my@email', password: 'my password' }) * ``` */ setAttributes(this: I, attributes: UpdateableProperties): Partial | { [x: string]: any; }>>; private _setAttributes; /** * Saves the state of the current instance to the * database. For new instances (that have not been * persisted), `save` and `create` model hooks will be called. * For instances that have already been persisted, * the `save` and `update` model hooks will be called. * * Upon saving a new instance, the primary key, timestamp * fields (if defined), and `type` (if STI) will be set on * the model. * * Upon updating an instance, the update timestamp (if defined) * will be updated on the model. * * A column whose value is `undefined` is skipped, not written: a new * record gets the column's database default, and a persisted record * keeps the value stored for it. Set a column to `null` to clear it. * * ```ts * const user = User.new({ email: 'how@yadoin' }) * user.name = 'fred' * await user.save() * * user.email // 'how@yadoin' * user.name // 'fred' * user.id // 1 * user.createdAt // A DateTime object * user.updatedAt // A DateTime object * * // If User were STI: * user.type // 'User' * ``` * * @param __namedParameters - optional parameters * @param __namedParameters.skipHooks - if true, will skip applying model hooks. Defaults to false * @returns void */ save(this: I, { skipHooks }?: { skipHooks?: boolean; }): Promise; /** * Applies transaction to a new Query instance * * ```ts * await ApplicationModel.transaction(async txn => { * const user = await User.txn(txn).create({ email: 'how@yadoin' }) * await Pet.txn(txn).create({ user }) * }) * ``` * * @param txn - A DreamTransaction instance (collected by calling `ApplicationModel.transaction`) * @returns A Query scoped to this model with the transaction applied */ txn(this: I, txn: DreamTransaction | null): DreamInstanceTransactionBuilder; /** * Applies all attribute changes passed to the Dream * instance, leveraging any custom-defined setters, * and then saves the Dream instance. Can be called * on an unpersisted Dream instance. * * See {@link Dream.save | save} for details on * the side effects of saving. * * A column whose value is still `undefined` after setters run is * skipped, not written (see {@link Dream.save | save}), with two * `@Encrypted` exceptions: `undefined` for an encrypted property (e.g. * `secret`) is encrypted to `null`, so it clears the column, and * `undefined` for its backing column (e.g. `encryptedSecret`) throws. * * NOTE: * To bypass custom-defined setters, use {@link Dream.updateAttributes | updateAttributes} instead. * * ```ts * const user = await User.create({ email: 'saly@gmail.com' }) * await user.update({ email: 'sally@gmail.com' }) * ``` * * @param attributes - the attributes to set on the model * @param __namedParameters - optional parameters * @param __namedParameters.skipHooks - if true, will skip applying model hooks. Defaults to false * @returns void */ update(this: I, attributes: UpdateableProperties, { skipHooks }?: InstanceUpdateOptions): Promise; /** * Applies all attribute changes passed to the dream, * bypassing any custom-defined setters, * and then saves the dream instance. Does not bypass * model hooks. * * See {@link Dream.save | save} for details on * the side effects of saving. * * An encrypted property (e.g. `secret`) is still encrypted. Its * `@Encrypted` backing column (e.g. `encryptedSecret`) accepts only * ciphertext that decrypts with this app's column encryption keys, or * `null`; anything else throws before anything is saved (see * {@link Dream.setAttribute | setAttribute}). * * A column whose value is still `undefined` after setters run is * skipped, not written (see {@link Dream.save | save}), with two * `@Encrypted` exceptions: `undefined` for an encrypted property (e.g. * `secret`) is encrypted to `null`, so it clears the column, and * `undefined` for its backing column (e.g. `encryptedSecret`) throws. * * NOTE: * To update the values without bypassing any custom-defined * setters, use {@link Dream.update | update} instead. * * ```ts * const user = await User.create({ email: 'saly@gmail.com' }) * await user.updateAttributes({ email: 'sally@gmail.com' }) * ``` * * @param attributes - The attributes to update on this instance * @param __namedParameters - optional parameters * @param __namedParameters.skipHooks - if true, will skip applying model hooks. Defaults to false * @returns - void */ updateAttributes(this: I, attributes: UpdateableProperties, { skipHooks }?: InstanceUpdateOptions): Promise; /** * Flags a dream model so that it does not * actually destroy when in a destroy phase. * This is usually used for a soft-delete * pattern * * The veto is fail-closed, and the record is left * entirely untouched: on a soft-delete model the soft * delete is skipped as well, no `afterDestroy` or * `afterDestroyCommit` hooks run, since the record is * still present, and the destroy returns before the * `dependent: 'destroy'` cascade, so this record's * dependent associations survive too. * * A veto only stops the subtree rooted at the record * that called it. When a record is reached by another * record's cascade, vetoing spares it and its own * dependents; the destroy that reached it carries on. * * ```ts * class User extends ApplicationModel { * @deco.BeforeDestroy() * public softDelete() { * await this.update({ deletedAt: DateTime.now() }) * this.preventDeletion() * } * } * ``` * * @returns void */ preventDeletion(): void; /** * Flags a dream model so that it allows * deletion once again. * * Undoes {@link Dream.preventDeletion} * * ```ts * class User extends ApplicationModel { * @deco.BeforeDestroy() * public async softDelete() { * await this.update({ deletedAt: DateTime.now() }) * this.preventDeletion() * } * * @deco.BeforeDestroy() * public async undoSoftDelete() { * await this.update({ deletedAt: null }) * this.unpreventDeletion() * } * } * ``` * * @returns void */ unpreventDeletion(): this; private _preventDeletion; }