Persistence
Records are saved, updated, and destroyed with save, create, update, and destroy.
Saving records
Save a record with save. It validates the record, runs its callbacks, and resolves to true or false:
import { User } from "./models";
const user = User.build({ email: "ada@example.com" });
if (await user.save()) {
// saved: user.isPersisted() is true
} else {
console.log(user.errors.fullMessages()); // ["Email has already been taken"]
}
const created = await User.create({ email: "grace@example.com" }); // saved, or not
await created.update({ isActive: false }); // true or falseA new record is inserted, and a persisted one updates only the attributes that changed. save resolves to false for one of two reasons:
- Validation failed. The reasons are in
record.errors. - A callback aborted with
abort(). The reason is inrecord.abortReason.
create(attrs) builds and saves a record, and resolves to it whether it saved or not. Check isPersisted() or errors. update(attrs) assigns, then saves.
Throwing on failure
When a failure is exceptional, use the OrThrow version of each method:
import { RecordInvalid } from "@forestfuture/d1-record";
import { User } from "./models";
try {
const grace = await User.createOrThrow({ email: "grace@example.com" });
await grace.updateOrThrow({ isActive: true });
} catch (error) {
if (error instanceof RecordInvalid) {
return Response.json({ errors: error.record.errors.fullMessages() }, { status: 422 });
}
throw error; // anything else is a real failure
}Instead of returning false, they throw:
RecordInvalidwhen validation failed, with the record onerror.record.RecordNotSavedwhen a callback aborted a save, with the abort’s reason as its message.RecordNotDestroyedfromdestroyOrThrow(), when a callback aborted the destroy or arestrictWithErrorassociation refused it.
Compared with Rails
The OrThrow methods are Rails’ save!, create!, update!, and destroy!.
What each method returns
| Method | Resolves to | Throws |
|---|---|---|
save() / update(attrs) | true / false | constraint errors, RecordDestroyed |
saveOrThrow() / updateOrThrow(attrs) | true | same as above, plus RecordInvalid, RecordNotSaved |
create(attrs) | record, persisted or not | constraint errors |
createOrThrow(attrs) | persisted record | same as above, plus RecordInvalid, RecordNotSaved |
destroy() | destroyed record, or false on abort | constraint errors, RecordDestroyed |
destroyOrThrow() | destroyed record | same as above, plus RecordNotDestroyed |
find(id) | record | RecordNotFound |
findBy(conds) | record or null | none |
findByOrThrow(conds) | record | RecordNotFound |
false only ever means validation failed or a callback aborted. Every method throws for anything else:
- Constraint errors, such as
RecordNotUnique, since the database refused the write. See Errors. - An error thrown by a callback, other than
abort(), even from an after-callback once the record is saved. - D1’s own errors.
Destroying records
Destroy a record with destroy:
const destroyed = await created.destroy(); // the record, or false if a callback aborted
if (destroyed) {
destroyed.isDestroyed(); // true: readable, but can't be changed or saved
}It runs the destroy callbacks and the associations’ dependent options, then deletes the row. A destroyed record’s attributes stay readable, but setting one, or calling save, update, destroy, or reload, throws RecordDestroyed.
TIP
If another request already deleted the row, save and destroy still succeed.
Checking a record’s state
| State | isNewRecord() | isPersisted() | isDestroyed() |
|---|---|---|---|
New (from build(), or after a failed create) | true | false | false |
| Persisted (loaded, or after a successful save) | false | true | false |
Destroyed (after a successful destroy()) | false | false | true |
Reloading records
Load a record’s row again with reload, discarding unsaved changes and loaded associations:
import { User } from "./models";
const fresh = await User.find(user.id);
fresh.email = "changed@example.com";
await fresh.reload(); // back to what D1 holdsIt throws RecordNotFound if the row is gone, or the record was never saved.
Writing many rows
To insert, update, or delete many rows at once, without validations and callbacks, use insertAll, upsertAll, updateAll, and deleteAll. See Bulk writes and batches.