Errors
Every error d1-record throws extends D1RecordError, so one check separates them from anything else:
import { D1RecordError } from "@forestfuture/d1-record";
if (error instanceof D1RecordError) {
// one of d1-record's own
}Lifecycle errors
| Error | When |
|---|---|
RecordNotFound | find, findByOrThrow, or reload found nothing |
RecordInvalid | an OrThrow save found the record invalid; the record is on .record |
RecordNotSaved | a save was aborted by a callback, or needs an owner that isn’t saved |
RecordNotDestroyed | a destroy was aborted, or restricted by restrictWithError |
RecordDestroyed | writing to a record that’s already destroyed |
CallbackAbort | what abort() makes; caught by the lifecycle |
AbortAfterPersistence | abort() in an after-callback, too late to stop the write (.callback) |
Usage errors
A usage error means the code needs to change:
| Error | When |
|---|---|
InvalidModel | a Model’s declaration is wrong, detected the first time it’s used |
UnknownAttribute | an attribute that isn’t declared, in build, where, update, … |
MissingAttribute | reading an attribute that select didn’t load |
UnknownAssociation | an association that isn’t declared |
AssociationNotLoaded | loaded() on an association that wasn’t loaded |
IncompatibleRelation | two Relations or writes can’t be used together (below) |
IrreversibleOrder | last() on a Relation ordered by an sql fragment |
DeleteRestriction | destroying an owner whose restrictWithException association still has records |
NoDatabase | using a record made with new rather than build |
NoSession | db.bookmark() on a Database opened without a session |
TIP
A name from a request, such as ?include= or a JSON key, only ever matches the Model’s own attributes and associations; constructor and __proto__ are unknown like any other. Return a 400 for these errors.
When Relations are incompatible
IncompatibleRelation is thrown for combinations that can’t make a correct query or write:
or(),merge(), or a scope with a Relation of another Model, or one that resolves to another database when it runs.or()also needs both sides to have the sameselect, order,limit,offset,distinct, andincludes, since only their conditions can be combined.batch([...]),db.batch([...]),save(), ordestroy()with statements or records from two databases, such as abelongsTotarget built on anotherconnect(). A write runs on one database. The message names where each database came from.includeswithselect(...).distinct(). Preloading needs the keys, and adding them would undo thedistinct.- Preloading an association scope that uses
limit,offset, ordistinct. One preload query serves every owner, so the limit would apply to all of their records together. updateAllordeleteAllafterdistinct(): they reject with it, andtoUpdateAll/toDeleteAllthrow it.
Constraint errors
When D1 refuses a write because of a constraint, it throws a subclass of ConstraintViolation:
| Error | Constraint |
|---|---|
RecordNotUnique | UNIQUE, PRIMARY KEY |
InvalidForeignKey | FOREIGN KEY |
NotNullViolation | NOT NULL |
CheckViolation | CHECK |
Each has D1’s original error as cause, the table, and, where D1’s message names them, the columns and attributes:
import { RecordNotUnique } from "@forestfuture/d1-record";
try {
await user.save();
} catch (error) {
if (error instanceof RecordNotUnique && error.attributes.includes("email")) {
user.errors.add("email", "has already been taken", { type: "taken" });
} else {
throw error;
}
}Every method throws them, save() as well as saveOrThrow(). The record keeps its changes, so you can fix them and save again.