@unthrown/prisma
@unthrown/prisma
Classes
ForeignKeyViolation
Defined in: packages/prisma/src/index.ts:64
A foreign key constraint was violated (Prisma error P2003).
Extends
TaggedErrorInstance<"ForeignKeyViolation", {cause:unknown; }>
Constructors
Constructor
new ForeignKeyViolation(args): ForeignKeyViolation;Defined in: packages/core/dist/index.d.mts:2034
Parameters
| Parameter | Type |
|---|---|
args | object & object |
Returns
Inherited from
TaggedError("ForeignKeyViolation")<{ cause: unknown }>.constructorProperties
InvalidCursor
Defined in: packages/prisma/src/index.ts:91
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(args): InvalidCursor;Defined in: packages/core/dist/index.d.mts:2034
Parameters
| Parameter | Type |
|---|---|
args | object & object |
Returns
Inherited from
TaggedError("InvalidCursor")<{ cause: unknown }>.constructorProperties
RecordNotFound
Defined in: packages/prisma/src/index.ts:78
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(args): RecordNotFound;Defined in: packages/core/dist/index.d.mts:2034
Parameters
| Parameter | Type |
|---|---|
args | object & object |
Returns
Inherited from
TaggedError("RecordNotFound")<{ cause: unknown }>.constructorProperties
UniqueConstraintViolation
Defined in: packages/prisma/src/index.ts:58
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(args): UniqueConstraintViolation;Defined in: packages/core/dist/index.d.mts:2034
Parameters
| Parameter | Type |
|---|---|
args | object & object |
Returns
Inherited from
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:2011 |
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:59 |
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
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:47
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 String(row.id). | packages/prisma/src/pagination.ts:49 |
parseCursor()? | (cursor) => Cursor | Parse an opaque cursor back into the model's cursor input. | packages/prisma/src/pagination.ts:51 |
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). 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:299
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:305 |
PrismaQueryError
type PrismaQueryError =
| UniqueConstraintViolation
| ForeignKeyViolation
| RecordNotFound;Defined in: packages/prisma/src/index.ts:105
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:348
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:285
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).
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">, ForeignKeyViolation>;
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:523
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">, ForeignKeyViolation>; 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:171
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);