InternalA new (unpersisted) instance of the provided dream class
InternalReturns an array of association names for the given dream class
new Post().associationNames
// [ 'user', 'postVisibility', 'ratings', 'heartRatings' ]
association names array
Returns an object with column names for keys, and an array of strings representing validation errors for values.
An error object
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.
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.
The list of column names this model should ignore
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.
const post = Post.new({ body: 'howyadoin' })
post.isDirty
// true
await post.save()
post.isDirty
// false
post.body = 'groiyyyt'
post.isDirty
// true
A boolean
Runs validation checks against all validations declared using the Validate and Validates decorators, and returns true if any of them fail.
class User extends ApplicationModel {
@deco.Validates('presence')
public email: DreamColumn<User, 'email'>
}
const user = User.new()
user.isInvalid
// true
user.email = 'how@yadoin'
user.isInvalid
// false
A boolean
Returns true if the model has not been persisted to the database.
const user = User.new({ email: 'howyadoin' })
user.isNewRecord
// true
await user.save()
user.isNewRecord
// false
A boolean
Returns true if the model has been persisted to the database.
const user = User.new({ email: 'howyadoin' })
user.isPersisted
// false
await user.save()
user.isPersisted
// true
A 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.
class User extends ApplicationModel {
@deco.Validates('presence')
public email: DreamColumn<User, 'email'>
}
const user = User.new()
user.isValid
// false
user.email = 'how@yadoin'
user.isValid
// true
A boolean
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:
// 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'
The string identifier for this model type used in database storage
this.constructor.name may be prefixed with an underscore during conversion to Javascript. This method returns the constructor name without a leading underscore.
A string
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:
class User extends ApplicationModel {
public override get table() {
return 'users' as const
}
}
The table name for this model
StaticassociationReturns 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.
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']
})
An array containing all association names defined on this Dream class
StaticglobalInternalReturns a unique global name for the given model.
A string representing a unique key for this model
StaticisInternalProvided to distinguish between Dream and other classes
true
StaticprimaryShadows #primaryKey, a getter which can be overwritten to customize the id field for a given model.
string
StaticreferenceReturns 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:
// 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'
The string identifier for this model type used in database storage
StaticsanitizedInternalReturns 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
string
StatictableShadows #table, a getter which can be overwritten to customize the table field for a given model.
string
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.
class User extends ApplicationModel {
...
@deco.Validate()
public async validateName() {
if (typeof this.name === 'number')
this.addError('name', 'name cannot be a number')
}
}
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
const user = new User()
user.assignAttribute('email', 'sally@gmail.com')
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.
const user = new User()
user.assignAttributes({ email: 'my@email', password: 'my password' })
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.
The name of the BelongsTo, HasOne, or HasMany association
Optional configuration for required and passthrough clauses and the database connection
The associated model instance(s) or null/empty array if not found
Warning: Using association in loops or when processing multiple records can lead to the
N+1 query problem.
Each call to association on an unloaded association will trigger a separate database query.
To avoid this, use load or preload to eager load
associations before accessing them.
// 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' })
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.
The name of the BelongsTo, HasOne, or HasMany association
Optionaloptions: { connection?: DbConnectionType }Optional configuration for required and passthrough clauses and the database connection
Optionalconnection?: DbConnectionTypethe database connection to use when loading the association ('primary' or 'replica')
The associated model instance(s) or null/empty array if not found
Warning: Using association in loops or when processing multiple records can lead to the
N+1 query problem.
Each call to association on an unloaded association will trigger a separate database query.
To avoid this, use load or preload to eager load
associations before accessing them.
// 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' })
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.
The name of the BelongsTo, HasOne, or HasMany association
Optional configuration for required and passthrough clauses and the database connection
The associated model instance(s) (never null for BelongsTo/HasOne)
Warning: Using associationOrFail in loops or when processing multiple records can lead to the
N+1 query problem.
Each call to associationOrFail on an unloaded association will trigger a separate database query.
To avoid this, use load or preload to eager load
associations before accessing them.
// 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' })
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.
The name of the BelongsTo, HasOne, or HasMany association
Optionaloptions: { connection?: DbConnectionType }Optional configuration for required and passthrough clauses and the database connection
Optionalconnection?: DbConnectionTypethe database connection to use when loading the association ('primary' or 'replica')
The associated model instance(s) (never null for BelongsTo/HasOne)
Warning: Using associationOrFail in loops or when processing multiple records can lead to the
N+1 query problem.
Each call to associationOrFail on an unloaded association will trigger a separate database query.
To avoid this, use load or preload to eager load
associations before accessing them.
// 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' })
OptionaljoinAndStatements: {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.
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()
// {}
An object containing changed attributes and their original values
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
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.
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)
An object containing changed attributes
Creates an association for an instance. Automatically handles setting foreign key and, in the case of polymorphism, foreign key type.
await user.createAssociation('posts', { body: 'hello world' })
the name of the association to create
the attributes with which to create the associated model
The created association
Deletes the record represented by this instance from the database, calling any destroy hooks on this model.
const user = await User.last()
await user.destroy()
Options for destroying the instance
If true, skips applying model hooks during the destroy operation. Defaults to false
If false, skips destroying associations marked dependent: 'destroy'. Defaults to true
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
An array of default scope names to bypass when cascade destroying. Defaults to an empty array
The instance that was destroyed
Optionaloptions: DestroyOptions<I> & InstanceMutationOptions & {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.
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()
// {}
An object containing the changed attributes
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.
A boolean
Returns the value for a columnName provided, bypassing the getters.
const user = User.new({ email: 'how@yadoin' })
user.getAttribute('email') // 'how@yadoin'
Returns an object containing all the columns on this dream class, as well as their values, bypassing getters.
const user = User.new({ email: 'how@yadoin' })
user.getAttributes()
// {
// email: 'how@yadoin',
// ...
// }
Checks whether the specified association has been defined on this Dream model.
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()
}
The name of the association to check for
true if the association exists on this model, false otherwise
Returns a new Query instance with the provided inner join statement attached
await user.innerJoin('posts').first()
A Query for this model with the inner join clause applied
Returns a new Query instance with the provided left join statement attached
await user.leftJoin('posts').first()
A Query for this model with the left join clause applied
Load each specified association using a single SQL query. See 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 Dream.load:
limit and offset will be automatically removedthrough associations will bring additional namespaces into the query that can conflict with through associations from other associations, creating an invalid query.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..load(...).findEach(...) avoids instantiating massive amounts of data at onceconst 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{}
A chain of association names and and/andNot/andAny clauses
A chainable LeftJoinLoadBuilder instance. Call .execute() to get the cloned model with associations loaded.
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:
// 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: 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.
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{}
A chainable LoadBuilder instance. Call .execute() to get the cloned model with associations loaded.
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.
user.loaded('posts')
// false
user = await user.load('posts').execute()
user.loaded('posts')
// true
the association name you wish to check the loading of
A boolean
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.
// 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()
The serializer key to use for determining which associations to preload.
OptionalmodifierFn: LoadForModifierFnOptional 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
A Query with all serialization associations preloaded
Sends data through for use as passthrough data for the associations that require it.
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()
where statement used for associations that require passthrough data
A cloned Query with the passthrough data
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.
class User extends ApplicationModel {
@deco.BeforeDestroy()
public softDelete() {
await this.update({ deletedAt: DateTime.now() })
this.preventDeletion()
}
}
void
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 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.
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'
The column name you want the previous value for
Returns the previous value for an attribute
Returns a new instance of Query scoped to the given model class. Especially useful with Query#toKysely.
await balloon.query().toKysely('update')
.set({
multicolor: sql`array_append(multicolor, 'red')`,
})
.execute()
A new Query instance scoped to this Dream class
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.
const user = await User.last()
await user.reallyDestroy()
Options for destroying the instance
If true, skips applying model hooks during the destroy operation. Defaults to false
If false, skips destroying associations marked dependent: 'destroy'. Defaults to true
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
An array of default scope names to bypass when cascade destroying. Defaults to an empty array
The instance that was destroyed
Optionaloptions: DestroyOptions<I> & InstanceMutationOptions & {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.
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'
optional parameters
OptionalskipHooks?: booleanif true, will skip applying model hooks. Defaults to false
void
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
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.
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)
A boolean
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
setAttributes, each of which encrypts it.
const user = new User()
user.setAttribute('email', 'sally@gmail.com')
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 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.
const user = new User()
user.setAttributes({ email: 'my@email', password: 'my password' })
Applies transaction to a new Query instance
await ApplicationModel.transaction(async txn => {
const user = await User.txn(txn).create({ email: 'how@yadoin' })
await Pet.txn(txn).create({ user })
})
A DreamTransaction instance (collected by calling ApplicationModel.transaction)
A Query scoped to this model with the transaction applied
Undestroys a SoftDelete model, unsetting
the deletedAt field in the database.
If the model is not a SoftDelete model, this will raise an exception.
const user = await User.removeDefaultScope('dream:SoftDelete').last()
await user.undestroy()
Options for undestroying the instance
If true, skips applying model hooks during the undestroy operation. Defaults to false
If false, skips undestroying associations marked dependent: 'destroy'. Defaults to true
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
An array of default scope names to bypass when cascade undestroying (soft delete is always bypassed). Defaults to an empty array
The undestroyed record
Optionaloptions: DestroyOptions<I> & InstanceMutationOptions & {Flags a dream model so that it allows deletion once again.
Undoes Dream.preventDeletion
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()
}
}
void
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 save for details on the side effects of saving.
A column whose value is still undefined after setters run is
skipped, not written (see 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 updateAttributes instead.
const user = await User.create({ email: 'saly@gmail.com' })
await user.update({ email: 'sally@gmail.com' })
the attributes to set on the model
optional parameters
if true, will skip applying model hooks. Defaults to false
void
OptionalupdateAssociationOptions: InstanceUpdateOptions & {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 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
setAttribute).
A column whose value is still undefined after setters run is
skipped, not written (see 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 update instead.
const user = await User.create({ email: 'saly@gmail.com' })
await user.updateAttributes({ email: 'sally@gmail.com' })
The attributes to update on this instance
optional parameters
if true, will skip applying model hooks. Defaults to false
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.
A boolean
StaticallRetrieves 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.
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
an array of dreams
StaticavgRetrieves the average for all values of the specified column for this model's records.
await Game.avg('score')
// 100
a column name on the model
the average for all values of the specified column for this model's records
StaticavgRetrieves the average of the specified column within each group, keyed by
the value of the provided group column (a SQL GROUP BY).
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.
the column to group by
the column to average within each group
A Map from each present group value to the average of the aggregated column in that group
StaticcolumnsStaticconnectionForces 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.
The connection you wish to access ('primary' or 'replica')
A Query with the requested connection
StaticcountStaticcountRetrieves the number of records in each group, keyed by the
value of the provided group column (a SQL GROUP BY).
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.
the column to group by
A Map from each present group value to the number of records in that group
StaticcreatePersists a new record, setting the provided attributes. Automatically sets createdAt and updatedAt timestamps.
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
Optionalattributes: Partial<attributes or belongs to associations you wish to set on this model before persisting
optional parameters
OptionalskipHooks?: booleanif true, will skip applying model hooks. Defaults to false
A newly persisted dream instance
StaticcreateAttempt 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
const logEntry = await LogEntry.createOrFindBy({ externalId }, { createWith: params })
The base attributes to persist, but also the attributes to use to find when create fails
Additional options
additional attributes to persist when creating, but not used for finding
if true, will skip applying model hooks. Defaults to false
A dream instance
StaticcreateAttempt 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
The base attributes for finding which record to update, also used when creating
Additional options
additional attributes to persist when updating and creating, but not used for finding
if true, will skip applying model hooks. Defaults to false
A dream instance
StaticcursorPaginates 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.
// 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
})
cursor pagination options
the number of results per page
identifier of where to start the next page; use undefined to start from the beginning
A fast pagination result object containing:
cursor - identifier for the next page, or null if no more pagesresults - An array of records for the current pageStaticdistinctReturns a new query instance with the distinct query applied. If no columnName is provided, then distinct will apply to the primary key by default.
await User.distinct('name').pluck('name')
A Query scoped to this Dream model with the distinct clause applied
StaticexistsStaticfindFinds a record for the corresponding model with the specified primary key. Returns null if not found.
await User.find(123)
// User{id: 123}
await User.find(null)
// null
await User.find(undefined)
// null
Either the found record, or else null
StaticfindFinds the first record—ordered by primary key—matching the corresponding model and the specified where statement. If not found, null is returned.
await User.findBy({ email: 'how@yadoin' })
// User{email: 'how@yadoin'}
The first model found matching the whereStatement
StaticfindFinds 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.
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 Query.findEach for why.
void
StaticfindAttempt to find the model with the given attributes. If no record is found, then a new record is created.
const user = await User.findOrCreateBy({ email }, { createWith: params })
The base attributes for finding, but also the attributes to use when creating
Additional options
additional attributes to persist when creating, but not used for finding
if true, will skip applying model hooks. Defaults to false
A dream instance
StaticfindFinds a record for the corresponding model with the specified primary key. If not found, an exception is raised.
await User.query().findOrFail(123)
// User{id: 123}
Either the found record, or else null
StaticfindFinds the first record—ordered by primary key—matching the corresponding model and the specified where statement. If not found, an exception is raised.
await User.findOrFailBy({ email: 'how@yadoin' })
// User{email: 'how@yadoin'}
The first model found matching the whereStatement
StaticfirstStaticfirstStaticinnerReturns a new Query instance with the provided inner join statement attached
await User.innerJoin('posts').first()
A Query for this model with the inner join clause applied
StaticisStaticisStaticlastStaticlastStaticleftReturns a new Query instance with the provided left join statement attached
await User.leftJoin('posts').first()
A Query for this model with the left join clause applied
StaticleftLoad each specified association using a single SQL query. See Dream.preload for preloading in separate queries.
Note: since leftJoinPreload loads via single query, it has some downsides and that may be avoided using Dream.preload:
limit and offset will be automatically removedthrough associations will bring additional namespaces into the query that can conflict with through associations from other associations, creating an invalid query.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..preload(...).findEach(...) avoids instantiating massive amounts of data at oncepreload/load, and saves, it does
not tolerate schema/image skew from an unplanned column drop during a
rolling deploy; see Query.leftJoinPreload and the
ignoredColumns getter for the two-deploy process that makes a
planned drop safe.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}]
A query for this model with the include statement applied
StaticlimitReturns a new Query instance, specifying a limit
await User.limit(2).all()
// [User{}, User{}]
A Query for this model with the limit clause applied
Staticlookupimports 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.
the found model or serializer, depending on what you are looking for
StaticmaxRetrieves the max value of the specified column for this model's records.
await User.max('id')
// 99
a column name on the model
the max value of the specified column for this model's records
StaticmaxRetrieves the max value of the specified column within each group, keyed by
the value of the provided group column (a SQL GROUP BY).
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.
the column to group by
the column to take the max of within each group
A Map from each present group value to the max of the aggregated column in that group
StaticminRetrieves the min value of the specified column for this model's records.
await User.min('id')
// 1
a column name on the model
the min value of the specified column for this model's records
StaticminRetrieves the min value of the specified column within each group, keyed by
the value of the provided group column (a SQL GROUP BY).
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.
the column to group by
the column to take the min of within each group
A Map from each present group value to the min of the aggregated column in that group
StaticnewSince typescript prevents constructor functions
from absorbing type generics, we provide the new
method to instantiate with type protection
const user = User.new({ email: 'how@yadoin' })
Optionalopts: Partial<the attributes to set on the new instance
optional parameters
OptionalbypassUserDefinedSetters?: booleanif true, sets the attributes
as 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
setAttribute). Defaults to false
A new (unpersisted) instance of the provided dream class
StaticoffsetReturns a new Query instance, specifying an offset
await User.offset(2).order('id').limit(2).all()
// [User{id: 3}, User{id: 4}]
A Query for this model with the offset clause applied
StaticorderReturns a new Query instance, attaching the provided order statement
await User.order('id').all()
// [User{id: 1}, User{id: 2}, ...]
await User.order({ name: 'asc', id: 'desc' }).all()
// [User{name: 'a', id: 99}, User{name: 'a', id: 97}, User{ name: 'b', id: 98 } ...]
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
A query for this model with the order clause applied
StaticpaginatePaginates 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.
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
Pagination options
the page number that you want to fetch results for
the number of results per page (optional)
A paginated result object containing:
recordCount - A number representing the total number of records matching your querypageCount - The number of pages needed to encapsulate all the matching recordscurrentPage - The current page (same as what is provided in the paginate args)results - An array of records matching the current recordStaticpassthroughSends data through for use as passthrough data for the associations that require it.
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()
Where statement used for associations that require passthrough data
A Query for this model with the passthrough data
StaticpluckPlucks the provided fields from the corresponding model
await User.pluck('id')
// [1, 3, 2]
If more than one column is requested, a multi-dimensional array is returned:
await User.order('id').pluck('id', 'email')
// [[1, 'a@a.com'], [2, 'b@b.com']]
The column or array of columns to pluck
An array of pluck results
StaticpluckPlucks the specified fields from the given dream class table in batches, passing each found columns into the provided callback function
await User.order('id').pluckEach('id', (id) => {
console.log(id)
})
// 1
// 2
// 3
a list of fields to pluck, followed by a callback function to call for each set of found fields
void
Plucks the specified fields from the given dream class table in batches, passing each found columns into the provided callback function
await User.order('id').pluckEach('id', (id) => {
console.log(id)
})
// 1
// 2
// 3
a list of fields to pluck, followed by a callback function to call for each set of found fields
void
StaticpreloadApplies 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
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}]
A query for this model with the preload statement applied
StaticpreloadRecursively 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.
// 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()
The serializer key to use for determining which associations to preload.
OptionalmodifierFn: LoadForModifierFnOptional 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
A Query with all serialization associations preloaded
StaticqueryReturns a new instance of Query scoped to the given model class. Especially useful with Query#toKysely.
await Balloon.query().toKysely('update').
.set({
multicolor: sql`array_remove(multicolor, ${colorToRemoveFromAllBalloons})`,
})
.execute()
A new Query instance scoped to this Dream class
StaticreencryptEncrypts 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.
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.
Optionalopts: FindEachOptsOptional parameters
how many rows to read per batch. Defaults to 1000
StaticremoveA query which bypasses user-removable default scopes
Use 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.
StaticremoveStaticresortUsed 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.
class Post extends ApplicationModel {
@deco.Sortable({ scope: ['user']})
public position: DreamColumn<User, 'position'>
}
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}]
void
StaticscopeReturns a Query scoped to this model with the specified scope applied.
class User extends ApplicationModel {
@deco.Scope()
public visible(query: Query<User>) {
return query.where({ hidden: false })
}
}
await User.scope('visible').all()
// [User{hidden: false}, User{hidden: false}]
a Query scoped to this model with the specified scope applied
Staticscrollscroll pagination options
the number of results per page
identifier of where to start the next page; use undefined to start from the beginning
A fast pagination result object containing:
cursor - identifier for the next page, or null if no more pagesresults - An array of records for the current pageUse 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.
// 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
})
StaticsqlReturns the sql that would be executed by a Query scoped to this model.
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: []
//}
An object representing the underlying sql statement
StaticsumRetrieves the sum for all values of the specified column for this model's records.
await Game.sum('score')
// 100
a column name on the model
the sum for all values of the specified column for this model's records
StaticsumRetrieves the sum of the specified column within each group, keyed by
the value of the provided group column (a SQL GROUP BY).
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.
the column to group by
the column to sum within each group
A Map from each present group value to the sum of the aggregated column in that group
StatictoConverts the given Dream class into a Kysely query, enabling you to build custom queries using the Kysely API
await User.toKysely('select').where('email', '=', 'how@yadoin').execute()
A Kysely query. Depending on the type passed, it will return either a SelectQueryBuilder, DeleteQueryBuilder, UpdateQueryBuilder, or an InsertQueryBuilder
StatictransactionBuilds a new DreamTransaction instance, provides the instance to the provided callback.
await ApplicationModel.transaction(async txn => {
const user = await User.txn(txn).create({ email: 'how@yadoin' })
await Pet.txn(txn).create({ user })
})
void
StatictxnApplies transaction to a new Query scoped to this model
await ApplicationModel.transaction(async txn => {
await User.txn(txn).create({ email: 'how@yadoin' })
})
A DreamTransaction instance (usually collected by calling ApplicationModel.transaction)
A Query scoped to this model with the transaction applied
StatictypeofDetermines 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.
// 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
}
The Dream class to compare against this class
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
StaticupdateAttempt 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.
const user = await User.updateOrCreateBy({ email }, { with: { name: 'Alice' })
The base attributes for finding which record to update, also used when creating
Additional options
additional attributes to persist when updating and creating, but not used for finding
if true, will skip applying model hooks. Defaults to false
A dream instance
StaticwhereStaticwhereApplies "OR"'d where statements to a Query scoped to this model.
await User.whereAny([{ email: 'how@yadoin' }, { name: 'fred' }]).first()
// [User{email: 'how@yadoin'}, User{name: 'fred'}, User{name: 'fred'}]
a list of where statements to OR together
A Query for this model with the whereAny clause applied
Staticwhere
NOTE: avoid using the constructor function directly. Use the static
.newor.createmethods instead, which will provide type guarding for your attributes.Since typescript prevents constructor functions from absorbing type generics, we provide the
newmethod to instantiate with type protection.