@unthrown/prisma
@unthrown/prisma
Classes
ForeignKeyViolation
Defined in: packages/prisma/src/index.ts:84
A foreign key constraint was violated (Prisma error P2003).
Extends
TaggedErrorInstance<"ForeignKeyViolation", {cause:unknown; }>
Constructors
Constructor
new ForeignKeyViolation(fields): ForeignKeyViolation;Defined in: packages/prisma/src/index.ts:85
Parameters
| Parameter | Type |
|---|---|
fields | { cause: unknown; } |
fields.cause | unknown |
Returns
Overrides
TaggedError("ForeignKeyViolation")<{ cause: unknown }>.constructorProperties
InvalidCursor
Defined in: packages/prisma/src/index.ts:121
The cursor handed to withCursor could not be used — the caller's parseCursor rejected it, or the query it produced was one Prisma refuses.
Remarks
The one anticipated failure of pagination, and the reason it is modeled at all: a cursor is an opaque string from the outside world (a query parameter), so a client sending garbage is input you answer with a 400 — not a bug in your code. Every other pagination failure is a defect, like any other query.
Extends
TaggedErrorInstance<"InvalidCursor", {cause:unknown; }>
Constructors
Constructor
new InvalidCursor(fields): InvalidCursor;Defined in: packages/prisma/src/index.ts:122
Parameters
| Parameter | Type |
|---|---|
fields | { cause: unknown; } |
fields.cause | unknown |
Returns
Overrides
TaggedError("InvalidCursor")<{ cause: unknown }>.constructorProperties
RecordNotFound
Defined in: packages/prisma/src/index.ts:103
A record required by the operation does not exist (Prisma errors P2025 and P2018) — the missing row of a findUniqueOrThrow, update, or delete, or the missing target of a nested connect.
Remarks
The nested-connect case is why tryCreate and tryUpsert carry this error despite never "missing" a row of their own: create({ data: { author: { connect: { id } } } }) raises P2025 when that author does not exist (and P2018 for the to-many side of the same mistake). Both codes say the same thing — a record the write depended on was not found — so both map here.
Extends
TaggedErrorInstance<"RecordNotFound", {cause:unknown; }>
Constructors
Constructor
new RecordNotFound(fields): RecordNotFound;Defined in: packages/prisma/src/index.ts:104
Parameters
| Parameter | Type |
|---|---|
fields | { cause: unknown; } |
fields.cause | unknown |
Returns
Overrides
TaggedError("RecordNotFound")<{ cause: unknown }>.constructorProperties
UniqueConstraintViolation
Defined in: packages/prisma/src/index.ts:73
A unique constraint was violated (Prisma error P2002).
Remarks
fields carries the offending column set from the error's meta.target (empty when the driver does not report it).
Extends
TaggedErrorInstance<"UniqueConstraintViolation", {cause:unknown;fields: readonlystring[]; }>
Constructors
Constructor
new UniqueConstraintViolation(fields): UniqueConstraintViolation;Defined in: packages/prisma/src/index.ts:77
Parameters
| Parameter | Type |
|---|---|
fields | { cause: unknown; fields: readonly string[]; } |
fields.cause | unknown |
fields.fields | readonly string[] |
Returns
Overrides
TaggedError("UniqueConstraintViolation")<{
fields: readonly string[];
cause: unknown;
}>.constructorProperties
| Property | Modifier | Type | Inherited from | Defined in |
|---|---|---|---|---|
_tag | readonly | "UniqueConstraintViolation" | TaggedError("UniqueConstraintViolation")._tag | packages/core/dist/index.d.mts:2360 |
cause | public | unknown | TaggedError("UniqueConstraintViolation").cause | node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.es2022.error.d.ts:24 |
fields | readonly | readonly string[] | TaggedError("UniqueConstraintViolation").fields | packages/prisma/src/index.ts:74 |
message | public | string | TaggedError("UniqueConstraintViolation").message | node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.es5.d.ts:1075 |
name | public | string | TaggedError("UniqueConstraintViolation").name | node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.es5.d.ts:1074 |
stack? | public | string | TaggedError("UniqueConstraintViolation").stack | node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.es5.d.ts:1076 |
Type Aliases
CreateError
type CreateError =
| UniqueConstraintViolation
| ForeignKeyViolation
| RecordNotFound;Defined in: packages/prisma/src/index.ts:250
What tryCreate can fail with.
CreateManyError
type CreateManyError =
| UniqueConstraintViolation
| ForeignKeyViolation;Defined in: packages/prisma/src/index.ts:252
What tryCreateMany / tryCreateManyAndReturn can fail with.
CursorPaginationMeta
type CursorPaginationMeta = object &
| {
endCursor: string;
startCursor: string;
}
| {
endCursor: null;
startCursor: null;
};Defined in: packages/prisma/src/pagination.ts:20
The page metadata of withCursor.
Type Declaration
| Name | Type | Defined in |
|---|---|---|
hasNextPage | boolean | packages/prisma/src/pagination.ts:22 |
hasPreviousPage | boolean | packages/prisma/src/pagination.ts:21 |
Remarks
startCursor / endCursor are the cursors of the page's boundary rows, so they are null together exactly when the page is empty — checking one narrows the other. They are deliberately NOT coupled to the flags: the last page has hasNextPage: false with a non-null endCursor, and an empty page past the end has hasPreviousPage: true with a null startCursor.
CursorPaginationOptions
type CursorPaginationOptions<Row, Cursor> = object &
| {
after?: string;
before?: never;
limit: number;
}
| {
after?: never;
before?: string;
limit: number;
}
| {
after?: string;
before?: never;
limit: null;
};Defined in: packages/prisma/src/pagination.ts:49
Options of withCursor, in the style of prisma-extension-pagination.
Type Declaration
| Name | Type | Description | Defined in |
|---|---|---|---|
getCursor()? | (row) => string | Serialize a row into an opaque cursor. Defaults to row.id, as described above. | packages/prisma/src/pagination.ts:51 |
parseCursor()? | (cursor) => Cursor | Parse an opaque cursor back into the model's cursor input. | packages/prisma/src/pagination.ts:53 |
Type Parameters
| Type Parameter | Description |
|---|---|
Row | the (selection-narrowed) result row type. |
Cursor | the model's cursor input (its unique-where shape). |
Remarks
after and before are mutually exclusive — a page runs in one direction, and passing both used to silently drop after. Pick a direction.
limit: null returns everything (from the after cursor when given). Combining limit: null with before is a compile error — "everything before the cursor, backwards, unbounded" is not something Prisma's negative take can express.
The default cursor is the record's id field, serialized with String and parsed back to a number when it is all digits (autoincrement ids) — a bigint once it exceeds Number.MAX_SAFE_INTEGER, so BigInt ids never lose precision — or kept as a string otherwise (uuid / cuid ids). A string id that is itself all digits is serialized with a leading ~ ("~123"), so it parses back to the string rather than to a number. Provide getCursor / parseCursor for composite keys, or when the selection omits id.
CursorPaginator
type CursorPaginator<Results, Cursor> = object;Defined in: packages/prisma/src/index.ts:352
What tryPaginate returns: a builder holding the query, consumed by withCursor.
Type Parameters
| Type Parameter | Description |
|---|---|
Results extends readonly unknown[] | the (selection-narrowed) findMany payload. |
Cursor | the model's cursor input (its unique-where shape). |
Properties
| Property | Modifier | Type | Description | Defined in |
|---|---|---|---|---|
withCursor | readonly | (options) => AsyncResult<[Results, CursorPaginationMeta], InvalidCursor> | Run the paginated query: the page and its metadata, or an InvalidCursor — the only modeled failure, since the cursor is the only part of the query that came from outside. | packages/prisma/src/index.ts:358 |
DeleteError
type DeleteError =
| RecordNotFound
| UniqueConstraintViolation
| ForeignKeyViolation;Defined in: packages/prisma/src/index.ts:260
What tryDelete can fail with — UniqueConstraintViolation included, raised when an onDelete: SetDefault / SetNull rewrite of the referencing rows collides with a unique index.
DeleteManyError
type DeleteManyError =
| UniqueConstraintViolation
| ForeignKeyViolation;Defined in: packages/prisma/src/index.ts:266
What tryDeleteMany can fail with — see DeleteError.
PrismaQueryError
type PrismaQueryError =
| UniqueConstraintViolation
| ForeignKeyViolation
| RecordNotFound;Defined in: packages/prisma/src/index.ts:140
The full union of domain errors a Prisma query can surface.
Remarks
This is the RUNTIME-side union: qualifyPrismaError maps into it. Each try* method narrows the static type to the codes its operation can actually hit — a read hits none of them, so it is typed never.
Infrastructure failures are deliberately absent: they are defects, not values (see qualifyPrismaError). InvalidCursor is absent too — it belongs to pagination, not to a query.
TransactionClient
type TransactionClient<C> = Omit<C, TxDenyList>;Defined in: packages/prisma/src/index.ts:405
The client an interactive $tryTransaction callback receives — the extended client minus what a transaction cannot do.
Type Parameters
| Type Parameter | Description |
|---|---|
C | the extended client, usually typeof db. |
Remarks
Name the tx parameter of a helper factored out of a callback with this, rather than restating the deny list: $tryTransaction uses the very same alias, so the two cannot drift. Restating it by hand does drift silently — Omit of a key that does not exist is not an error, so a hand-copied list keeps compiling after the library's own list changes.
Not to be confused with Prisma's own generated Prisma.TransactionClient — that one is non-generic and, notably, does not remove $tryTransaction.
Example
type Tx = TransactionClient<typeof db>;
const chargeFees = (tx: Tx, id: number) =>
tx.invoice.tryUpdate({ where: { id }, data: { charged: true } });
db.$tryTransaction((tx) => chargeFees(tx, 1));TransactionIsolationLevel
type TransactionIsolationLevel =
| "ReadUncommitted"
| "ReadCommitted"
| "RepeatableRead"
| "Snapshot"
| "Serializable";Defined in: packages/prisma/src/index.ts:338
The isolation levels Prisma accepts across databases, as a closed union.
Remarks
The schema-derived Prisma.TransactionIsolationLevel of a generated client is narrower (it lists only what YOUR database supports), but a shareable extension cannot name a generated type — this union at least rejects typos at compile time; a level your database does not support still fails at runtime as a defect (an unsupported level is a bug, not an outcome).
TryTransaction
type TryTransaction = {
<C, T, E> (this, fn, options?): AsyncResult<T, PrismaQueryError | E>;
<C, P> (this, operations, options?): AsyncResult<UnwrapPrismaTuple<P>, PrismaQueryError>;
};Defined in: packages/prisma/src/index.ts:425
The type of $tryTransaction: overloaded exactly as Prisma's own $transaction is — an interactive callback, or a batch of raw operations.
Call Signature
<C, T, E>(
this,
fn,
options?
): AsyncResult<T, PrismaQueryError | E>;An interactive transaction whose callback speaks AsyncResult: an Err triggers a ROLLBACK and comes out as the same typed Err.
Type Parameters
| Type Parameter |
|---|
C |
T |
E |
Parameters
| Parameter | Type |
|---|---|
this | C |
fn | (tx) => AsyncResult<T, E> |
options? | { isolationLevel?: TransactionIsolationLevel; maxWait?: number; timeout?: number; } |
options.isolationLevel? | TransactionIsolationLevel |
options.maxWait? | number |
options.timeout? | number |
Returns
AsyncResult<T, PrismaQueryError | E>
Remarks
The callback's Err is thrown internally as a sentinel so Prisma aborts the transaction, then unwrapped back into the typed error channel — AsyncResult<T, E | PrismaQueryError>. A defect inside the callback also rolls back and stays a defect — including a callback that throws instead of returning an AsyncResult (a bug, never downgraded to a modeled error). The try* methods are available on tx (extensions propagate into the interactive transaction); the deny list additionally removes $tryTransaction itself — no nesting. Name a helper's tx parameter with TransactionClient.
The | PrismaQueryError is not merely defensive: a deferred constraint surfaces its violation at COMMIT rather than at the statement, so the transaction boundary itself can produce one. Prisma's own machinery failing any other way (a maxWait / timeout expiry, a lost connection) is infrastructure, and so a defect like everywhere else.
Example
const moved = db.$tryTransaction((tx) =>
tx.account
.tryUpdate({ where: { id: from }, data: { balance: { decrement: amount } } })
.flatMap(() =>
tx.account.tryUpdate({
where: { id: to },
data: { balance: { increment: amount } },
}),
),
);
// Err anywhere → both updates rolled back, and the Err is in `moved`.Call Signature
<C, P>(
this,
operations,
options?
): AsyncResult<UnwrapPrismaTuple<P>, PrismaQueryError>;A batch transaction: the operations run in one round trip, all or nothing, with no application code held open in between.
Type Parameters
| Type Parameter |
|---|
C |
P extends readonly PrismaPromise<unknown>[] |
Parameters
| Parameter | Type |
|---|---|
this | C |
operations | readonly [P] |
options? | { isolationLevel?: TransactionIsolationLevel; } |
options.isolationLevel? | TransactionIsolationLevel |
Returns
AsyncResult<UnwrapPrismaTuple<P>, PrismaQueryError>
Remarks
Two things follow from Prisma's batch form taking unexecutedPrismaPromises, and both are deliberate:
- The array holds the raw delegate methods —
db.user.create(...), nottryCreate. Atry*method has already executed and returns anAsyncResult, so passing one is a compile error. Eis the whole PrismaQueryError union rather than the per-operation narrowing thetry*methods give: a rawPrismaPromisecarries no error-type information. Every infrastructure failure is still a defect, exactly as everywhere else.
A fixed tuple keeps positional types; a dynamic array (a .map(...)) collapses to AsyncResult<T[], PrismaQueryError>.
maxWait and timeout are absent by design — they govern an interactive transaction's open window, which a single-round-trip batch does not have.
Example
const rows = db.$tryTransaction(
inputs.map((data) => db.user.create({ data })),
);
// ^? AsyncResult<User[], PrismaQueryError>
// Any constraint violation → nothing is written, and the Err is modeled.TxDenyList
type TxDenyList =
| "$connect"
| "$disconnect"
| "$on"
| "$transaction"
| "$use"
| "$extends"
| "$tryTransaction";Defined in: packages/prisma/src/index.ts:370
What an interactive-transaction client cannot do: Prisma's own ITXClientDenyList, plus $tryTransaction itself — nested transactions are not a thing, and the itx client has no $transaction for the bridge to delegate to. Name a tx with TransactionClient rather than Omit-ing this by hand.
UnwrapPrismaTuple
type UnwrapPrismaTuple<P> = { -readonly [K in keyof P]: P[K] extends Prisma.PrismaPromise<infer X> ? X : never };Defined in: packages/prisma/src/index.ts:417
The results of a batch $tryTransaction([...]): each PrismaPromise<T> unwrapped to its T. A fixed tuple keeps positional types; a dynamic PrismaPromise<T>[] collapses to T[] — the same duality as core's all.
Type Parameters
| Type Parameter |
|---|
P extends readonly unknown[] |
Remarks
Prisma's own UnwrapTuple lives at runtime.Types.Utils.UnwrapTuple, behind a runtime entry path this package deliberately never imports (it moved between Prisma 6 and 7), so the mapping is written here.
UpdateError
type UpdateError =
| RecordNotFound
| UniqueConstraintViolation
| ForeignKeyViolation;Defined in: packages/prisma/src/index.ts:254
What tryUpdate can fail with.
UpdateManyError
type UpdateManyError =
| UniqueConstraintViolation
| ForeignKeyViolation;Defined in: packages/prisma/src/index.ts:264
What tryUpdateMany / tryUpdateManyAndReturn can fail with.
UpsertError
type UpsertError =
| UniqueConstraintViolation
| ForeignKeyViolation
| RecordNotFound;Defined in: packages/prisma/src/index.ts:262
What tryUpsert can fail with.
Variables
unthrownPrisma
const unthrownPrisma: (client) => PrismaClientExtends<InternalArgs<{
}, {
$allModels: {
tryAggregate: AsyncResult<Result<T, A, "aggregate">, never>;
tryCount: AsyncResult<Result<T, A, "count">, never>;
tryCreate: AsyncResult<Result<T, A, "create">, CreateError>;
tryCreateMany: AsyncResult<Result<T, A, "createMany">, CreateManyError>;
tryCreateManyAndReturn: AsyncResult<Result<T, A, "createManyAndReturn">, CreateManyError>;
tryDelete: AsyncResult<Result<T, A, "delete">, DeleteError>;
tryDeleteMany: AsyncResult<Result<T, A, "deleteMany">, DeleteManyError>;
tryFindFirst: AsyncResult<Result<T, A, "findFirst">, never>;
tryFindFirstOrThrow: AsyncResult<Result<T, A, "findFirstOrThrow">, RecordNotFound>;
tryFindMany: AsyncResult<Result<T, A, "findMany">, never>;
tryFindUnique: AsyncResult<Result<T, A, "findUnique">, never>;
tryFindUniqueOrThrow: AsyncResult<Result<T, A, "findUniqueOrThrow">, RecordNotFound>;
tryGroupBy: AsyncResult<Result<T, A, "groupBy">, never>;
tryPaginate: CursorPaginator<Result<T, A, "findMany">, NonNullable<Args<T, "findMany">["cursor"]>>;
tryUpdate: AsyncResult<Result<T, A, "update">, UpdateError>;
tryUpdateMany: AsyncResult<Result<T, A, "updateMany">, UpdateManyError>;
tryUpdateManyAndReturn: AsyncResult<Result<T, A, "updateManyAndReturn">, UpdateManyError>;
tryUpsert: AsyncResult<Result<T, A, "upsert">, UpsertError>;
};
}, {
}, {
$tryTransaction: TryTransaction;
}>>;Defined in: packages/prisma/src/index.ts:589
The Prisma Client extension. Apply it with $extends to add the try* methods to every model delegate, and $tryTransaction to the client.
Parameters
| Parameter | Type |
|---|---|
client | any |
Returns
PrismaClientExtends<InternalArgs<{ }, { $allModels: { tryAggregate: AsyncResult<Result<T, A, "aggregate">, never>; tryCount: AsyncResult<Result<T, A, "count">, never>; tryCreate: AsyncResult<Result<T, A, "create">, CreateError>; tryCreateMany: AsyncResult<Result<T, A, "createMany">, CreateManyError>; tryCreateManyAndReturn: AsyncResult<Result<T, A, "createManyAndReturn">, CreateManyError>; tryDelete: AsyncResult<Result<T, A, "delete">, DeleteError>; tryDeleteMany: AsyncResult<Result<T, A, "deleteMany">, DeleteManyError>; tryFindFirst: AsyncResult<Result<T, A, "findFirst">, never>; tryFindFirstOrThrow: AsyncResult<Result<T, A, "findFirstOrThrow">, RecordNotFound>; tryFindMany: AsyncResult<Result<T, A, "findMany">, never>; tryFindUnique: AsyncResult<Result<T, A, "findUnique">, never>; tryFindUniqueOrThrow: AsyncResult<Result<T, A, "findUniqueOrThrow">, RecordNotFound>; tryGroupBy: AsyncResult<Result<T, A, "groupBy">, never>; tryPaginate: CursorPaginator<Result<T, A, "findMany">, NonNullable<Args<T, "findMany">["cursor"]>>; tryUpdate: AsyncResult<Result<T, A, "update">, UpdateError>; tryUpdateMany: AsyncResult<Result<T, A, "updateMany">, UpdateManyError>; tryUpdateManyAndReturn: AsyncResult<Result<T, A, "updateManyAndReturn">, UpdateManyError>; tryUpsert: AsyncResult<Result<T, A, "upsert">, UpsertError>; }; }, { }, { $tryTransaction: TryTransaction; }>>
Remarks
Typing follows Prisma's documented $allModels pattern: this: T binds the concrete delegate, Prisma.Exact checks args, and Prisma.Result computes the payload — so select / include inference survives the wrap.
Example
import { PrismaClient } from "./generated/prisma/client.ts";
import { unthrownPrisma } from "@unthrown/prisma";
const db = new PrismaClient({ adapter }).$extends(unthrownPrisma);
const users = db.user.tryFindMany({ select: { id: true } });
// ^? AsyncResult<{ id: number }[], never> — a read has no modeled failureFunctions
qualifyPrismaError()
function qualifyPrismaError<D>(cause, defect): D | PrismaQueryError;Defined in: packages/prisma/src/index.ts:206
Qualify a Prisma rejection — the runtime half of the bridge, and a ready-made qualify for any boundary you build yourself (raw SQL).
Type Parameters
| Type Parameter |
|---|
D |
Parameters
| Parameter | Type | Description |
|---|---|---|
cause | unknown | the rejected value from a Prisma query. |
defect | (cause) => D | the defect helper the boundary injects (never import it). |
Returns
D | PrismaQueryError
Remarks
The three P-codes that describe a domain outcome map to their tagged errors — P2002 → UniqueConstraintViolation, P2003 → ForeignKeyViolation, P2025 / P2018 → RecordNotFound.
Everything else is a defect, with the original cause preserved: a dropped connection, a pool timeout, a deadlock, an unmapped P-code, a malformed query, a client that could not start, an engine panic. None of those is something domain code branches on — they are logged and turned into a 500 at the edge, which is precisely what the defect channel is for. Modelling them would force every call site to carry an arm that duplicates its own defect arm.
A defect is not a crash: it flows through the pipeline untouched and is folded by match's defect handler like any other unmodeled failure.
Example
// Pass it straight to a boundary — `defect` is injected for you.
const rows = fromPromise(db.$queryRaw`SELECT 1`, qualifyPrismaError);