Writes & offline
Write with the same model
Section titled “Write with the same model”const { id } = await db.task.create({ data: { title: "Ship it", column: { connect: columnId } },});
await db.task.update({ where: { id }, data: { done: true } });await db.task.upsert({ where: { id }, create: { title: "A" }, update: { title: "B" } });await db.task.delete({ where: { id } });
await db.column.update({ where: { id: columnId }, data: { tasks: { connect: [a, b], disconnect: [c] } },});
// Several writes, one log entry, all or nothingawait db.$transaction([ db.task.update({ where: { id: a }, data: { position: 2 } }), db.task.update({ where: { id: b }, data: { position: 1 } }),]);create fails if the id exists; update fails if the row is missing; upsert chooses inside the engine’s transaction, so it cannot race.
Writes apply optimistically, queue in an IndexedDB outbox while offline, and resolve to { id, status: "applied" | "queued" }. A write the engine refuses for good during a background flush reaches onWriteRejected.
Retries are deduplicated by client transaction id. Durable receipts survive snapshots, log pruning and recovery from R2, so a previously applied request is acknowledged without applying it again. Reusing an id with different operations is rejected.
Related rows
Section titled “Related rows”Related rows can be created with the row that needs them:
await db.board.create({ data: { name: "Launch", columns: { create: [{ name: "Todo", position: 1, tasks: { create: { title: "Ship" } } }] }, },});
await db.column.create({ data: { name: "Solo", board: { create: { name: "New board" } } } });It is one transaction, ordered so each row exists before anything points at it, and one failure rolls the whole tree back. The types follow one level; deeper nesting is checked by the engine.
Writing many rows
Section titled “Writing many rows”const { count } = await db.task.updateMany({ where: { done: true }, data: { archived: true } });await db.task.deleteMany({ where: { columnId: null } });The engine resolves which rows match, inside the transaction, and logs the rows rather than the filter. A replay has to change what the original changed, and re-running a filter later cannot promise that. A bulk write reaches only rows the caller may read, and still runs the row rules for update and delete on each one.
Up to 1,000 matching rows commit as one transaction. Beyond that the client writes batches of 1,000 in id order, each its own transaction, until the filter is exhausted; count is the total. If a batch fails, BulkWriteError.written says how many rows the earlier batches changed, and running the same call again carries on with what still matches.
Unlike single-row writes, bulk writes are not applied optimistically and not queued offline: which rows match is the engine’s answer, not the cache’s.