- Summary
- Installation
- Supported DBs
- Usage
- API
- Future updates
- Changelog / Versioning
- License
prisma-ts-select is a TypeScript utility for enhancing the Prisma ORM. It simplifies the selection of fields in Prisma queries, ensuring type safety and reducing boilerplate when working with nested fields. Ideal for developers seeking an efficient, type-safe way to select data with Prisma in TypeScript.
[!NOTE]
This has been built mostly around MySQL. Most methods should work across the board.
Known exceptions include:
- HAVING
- SQLite
- Requires you to have either an aggregate function in the
SELECT
or make use ofGROUP BY
- Can only use columns that are specified in
SELECT
orGROUP BY
Install via:
npm install prisma-ts-select
pnpm add prisma-ts-select
I have tested this currently on the following databases.
- SQLite
- MySQL
Most items should also work for
- PostgreSQL
Other DBs will be added when I have chance.
Set up the needed generator.
generator prisma-ts-select {
provider = "prisma-ts-select"
}
Run the prisma generator to build the needed files
pnpm exec prisma generate --generator prisma-ts-select
After that is done, we can extend the PrismaClient:
import { PrismaClient } from "@prisma/client";
import prismaTSSelect from "prisma-ts-select/extend";
const prisma = new PrismaClient().$extends(prismaTSSelect);
Then we can use it like:
const results = await prisma.$from("<table>")
.select("<column>")
.run()
console.log(results);
The way the methods are chained, are heavily inspired by Dr Milan Milanović with his How To Learn SQL? > Queries Execution Order post.
- Sources
from
-
join
(s)
where
groupBy
select
having
orderBy
limit
offset
This takes the base
table to work from.
Using the defined links (foreign keys) defined in the schema, provides a type-safe way of joining on tables.
prisma.$from("User")
.join("Post", "authorId", "User.id");
The resulting SQL will look like:
FROM User
JOIN Post ON authorId = User.id;
column | Description |
---|---|
table |
The table to join on. TS autocomplete will show tables that can join with previously defined tables on. |
field |
Column on table. TS autocomplete will show known columns that this table, can join with previously defined tables on. |
reference |
Table.Column to a previously defined table (either the base, or another join), with a FK that is defined in the schema definition. |
Unlike the .join
command, this will allow you to join on columns that are not explicitly linked by a FK, but have the same type.
prisma.$from("User")
.joinUnsafeTypeEnforced("Post", "title", "User.name");
The resulting SQL will look like:
FROM User
JOIN Post ON Post.title = User.name;
column | Description |
---|---|
table |
The table to join on. TS autocomplete will show tables that can join with previously defined tables on. |
field |
Column on table. TS autocomplete will show known columns that this table, can join with previously defined tables on. |
reference |
Table.Column to a previously defined table (either the base, or another join), with a column that is of the same type. |
Unlike the .joinUnsafeIgnoreType
command, this will allow you to join on columns that are not explicitly linked by a FK, and do not have the same type.
prisma.$from("User")
.joinUnsafeIgnoreType("Post", "id", "User.name");
The resulting SQL will look like:
FROM User
JOIN Post ON Post.id = User.name
column | Description |
---|---|
table |
The table to join on. TS autocomplete will show tables that can join with previously defined tables on. |
field |
Column on table. TS autocomplete will show known columns that this table, can join with previously defined tables on. |
reference |
Table.Column to a previously defined table (either the base, or another join). Referencing any column, of any type. |
The where
syntax takes inspiration from how mongoDB does queries.
type WhereClause = {
"Table.Column": <value> | { "op": "<condition>", "value": <value> }
"$AND": [WhereClause, ...Array<WhereClause>],
"$OR": [WhereClause, ...Array<WhereClause>],
"$NOT": [WhereClause, ...Array<WhereClause>],
"$NOR": [WhereClause, ...Array<WhereClause>]
}
Op | Description | Supported Types |
---|---|---|
IN | Numbers, String, Date | |
NOT IN | Numbers, String, Date | |
BETWEEN | Numbers, Date | |
LIKE | String | |
NOT LIKE | String | |
IS NULL | * | |
IS NOT NULL | * | |
> | Numbers, Date | |
>= | Numbers, Date | |
< | Numbers, Date | |
<= | Numbers, Date | |
!= | Numbers, String, Date |
Type | Description | Example | SQL |
---|---|---|---|
Table.Column | A particular Table.Column name | .where({ |
(User.age = 20 AND User.name LIKE "Stuart%") |
$AND | Will join all items with a AND
|
.where({ |
(User.age > 20 AND User.age < 60) |
$OR | Will join all items with a OR
|
.where({ |
(User.name LIKE "a%" OR User.name LIKE "d%") |
$NOT | Will wrap statement in a NOT (/*...*/) and join any items with a AND
|
.where({ |
(NOT (User.age = 20 AND (User.age = 60 AND User.name = "Bob"))) |
$NOR | Will wrap statement in a NOT (/*...*/) and join any items with a OR
|
.where({ |
(NOT (User.age = 20 OR (User.age != 60 AND User.name = "Bob"))) |
prisma.$from("User")
.join("Post", "id", "User.name")
.where({
"User.age": 20,
"User.name": {op: "LIKE", value: "Stuart%"},
});
prisma.$from("User")
.join("Post", "id", "User.name")
.where({
$AND: [
{"User.age": {op: ">", value: 20}},
{"User.age": {op: "<", value: 60}},
]
});
prisma.$from("User")
.join("Post", "id", "User.name")
.where({
$OR: [
{"User.name": {op: "LIKE", value: "a%"}},
{"User.name": {op: "LIKE", value: "d%"}},
]
});
prisma.$from("User")
.join("Post", "id", "User.name")
.where({
$NOT: [
{"User.age": 20},
{
"User.age": {op: "=", value: 60},
"User.name": "Bob",
},
]
});
prisma.$from("User")
.join("Post", "id", "User.name")
.where({
$NOR: [
{"User.age": 20},
{
"User.age": {op: "!=", value: 60},
"User.name": "Bob",
},
]
});
This will remove the null
type from the union of types of the current table column.
To use .whereNotNull
, you need to add it before a .where
.
prisma.$from("User")
.join("Post", "authorId", "User.id")
.whereNotNull("User.name");
The resulting SQL will look like:
FROM User
JOIN Post ON authorId = User.id
WHERE User.name IS NOT NULL;
This will remove the NonNull type from the union of types of the current table column.
To use .whereIsNull
, you need to add it before a .where
.
prisma.$from("User")
.join("Post", "authorId", "User.id")
.whereIsNull("Post.content");
The resulting SQL will look like:
FROM User
JOIN Post ON authorId = User.id
WHERE Post.content IS NULL;
When you want to write a complex where
, or you just don't want the TypeSafety offered by the other methods, you can use .whereRaw
.
prisma.$from("User")
.join("Post", "authorId", "User.id")
.whereRaw("this is a raw where statement");
The resulting SQL will look like:
FROM User
JOIN Post ON authorId = User.id
WHERE this is a raw where statement;
Will allow you to pass a list of columns, that haven been specified from the .$from
and any .join
methods.
prisma.$from("User")
.join("Post", "authorId", "User.id")
.groupBy(["name", "Post.content"]);
The resulting SQL will look like:
FROM User
JOIN Post ON authorId = User.id
GROUP BY name, Post.content;
Will add the keyword DISTINCT
after the select.
prisma.$from("User")
.selectDistinct();
The resulting SQL will look like:
SELECT DISTINCT
FROM User;
Works slightly differently to *
. The limitation of *
in JS, is that if you have 2 tables with the same name, you will only get back the last, based on a join.
This method will explicitly list all the tables from the $from
and .join
. So you get the table.column
in the respose.
prisma.$from("User")
.selectAll();
The resulting SQL will look like:
SELECT id, email, name
FROM User
prisma.$from("User")
.join("Post", "authorId", "User.id")
.selectAll();
The resulting SQL will look like:
SELECT User.id, User. email, User.name, Post.id, Post.title, Post.content, Post.published, Post.author, Post.authorId, Post.LastModifiedBy, Post.lastModifiedById
FROM User
JOIN Post ON authorId = User.id
You can supply either; *
OR table.field
and then chain them together.
prisma.$from("User")
.select("*");
The resulting SQL will look like:
SELECT *
FROM User;
prisma.$from("User")
.select("name")
.select("email");
The resulting SQL will look like:
SELECT name, email
FROM User;
prisma.$from("User")
.join("Post", "authorId", "User.id")
.select("name")
.select("Post.title");
[!NOTE]
Support for
Table.*
isn't complete yet. This will be tracked here.
The resulting SQL will look like:
SELECT name, email
FROM User;
.having
uses the same syntax as .where
. Please see the previous section for details.
prisma.$from("User")
.join("Post", "authorId", "User.id")
.groupBy(["name", "Post.content"])
.having({
"User.name": {
"op": "LIKE",
"value": "bob%"
}
});
prisma.$from("User")
.join("Post", "authorId", "User.id")
.having({
"User.name": {
"op": "LIKE",
"value": "stuart%"
}
});
FROM User
JOIN Post ON authorId = User.id
GROUP BY name, Post.content
HAVING (User.name LIKE 'bob%');
FROM User
JOIN Post ON authorId = User.id
HAVING (User.name LIKE 'stuart%');
.orderBy
, takes an array of column names, with the optional suffix of ASC
or DESC
.
prisma.$from("User")
.join("Post", "authorId", "User.id")
.orderBy(["name", "Post.content DESC"]);
FROM User
JOIN Post ON authorId = User.id
ORDER BY name, Post.content DESC;
.limit
, takes the number of rows you would like to return.
prisma.$from("User")
.join("Post", "authorId", "User.id")
.limit(1);
FROM User
JOIN Post ON authorId = User.id
LIMIT 1;
.offSet
, the number of rows to skip. Requires .limit
to have been used first.
prisma.$from("User")
.join("Post", "authorId", "User.id")
.limit(1)
.offset(1);
FROM User
JOIN Post ON authorId = User.id
LIMIT 1
OFFSET 1
- Support specifying
JOIN
type issue#2 - Support Select Functions
- Support a
Many-To-Many
join #19 - Select column alias #27
- Table name alias #28
- whereRaw supporting Prisma.sql
Changelog is available here. We use semantic versioning for versioning.
This project is licensed under the MIT License. See the LICENSE file for details.
Things of note!!!!
- remove typeof from
type _db = DeepWriteable<typeof DB>;
}[keyof typeof DB];
- Merge Items missing //@ts-expect-error - might not be needed
- groupBy -> having,
- missing @deprecated
- ts-exptect-error - might not be needed
- GetColsFromTableType missing ts-expect-error - might not be needed
- DB needs to be in the same file.
npm i prisma-ts-select
pnpm add prisma-ts-select