-
Notifications
You must be signed in to change notification settings - Fork 454
Upgrade project to prisma v6 #1043
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
This commit updates the project to use a generated Prisma client and the better-sqlite3 adapter for improved performance and type safety. It also includes changes to the user search functionality and WebAuthn registration. Co-authored-by: me <[email protected]>
Cursor Agent can help with this pull request. Just |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bug: Prisma Seed Configuration Removed
The prisma.seed
configuration was removed from package.json
. This breaks database seeding, as the prisma db seed
command can no longer locate and run the prisma/seed.ts
script, impacting development and testing workflows.
package.json#L163-L167
prisma.config.ts.bak
Outdated
} | ||
return new PrismaBetterSQLite3({ url }) | ||
}, | ||
} No newline at end of file |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
eslint.config.js
Outdated
}, | ||
{ | ||
ignores: ['.react-router/*'], | ||
ignores: ['.react-router/*', './app/utils/prisma-generated.server'], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bug: ESLint Ignore Pattern Fails Directory Matching
The ESLint ignore pattern '.app/utils/prisma-generated.server'
only ignores a file with that exact name, not the directory's contents. This means ESLint will still lint the generated Prisma files, which is inconsistent with the existing glob pattern style.
log: [ | ||
{ level: 'query', emit: 'event' }, | ||
{ level: 'error', emit: 'stdout' }, | ||
{ level: 'info', emit: 'stdout' }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@cursor, please delete prisma/cache.db
and prisma/prisma/data.db
. The fact those files were created indicates that there's a problem in the configuration, so please make certain that the files maintain their existing configured destination. Check .env.example
(copy that to .env
before running anything with prisma) and .gitignore
to know where the database files are supposed to go. If they don't get where they're supposed to then please delete them and try again until they get to the right spot.
Also delete prisma.config.ts.bak
and create the proper prisma config file.
app/routes/users+/index.tsx
Outdated
const like = `%${searchTerm ?? ''}%` | ||
const users = await prisma.$queryRawTyped(searchUsers(like)) | ||
const users = await prisma.user.findMany({ | ||
where: { | ||
OR: [ | ||
{ name: { contains: searchTerm ?? '' } }, | ||
{ username: { contains: searchTerm ?? '' } }, | ||
], | ||
}, | ||
select: { | ||
id: true, | ||
name: true, | ||
username: true, | ||
image: { select: { objectKey: true } }, | ||
}, | ||
}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@cursor, the changes in this file are incorrect. This uses a feature of prisma called typed sql. Below are the docs for this feature:
https://www.prisma.io/docs/orm/prisma-client/using-raw-sql/typedsql
title: 'TypedSQL'
metaTitle: 'Writing Type-safe SQL with TypedSQL and Prisma Client'
metaDescription: 'Learn how to use TypedSQL to write fully type-safe SQL queries that are compatible with any SQL console and Prisma Client.'
sidebar_class_name: preview-badge
Getting started with TypedSQL
To start using TypedSQL in your Prisma project, follow these steps:
-
Ensure you have
@prisma/client
andprisma
installed and updated to at least version5.19.0
.npm install @prisma/client@latest npm install -D prisma@latest
-
Add the
typedSql
preview feature flag to yourschema.prisma
file:generator client { provider = "prisma-client-js" previewFeatures = ["typedSql"] }
:::tip[Using driver adapters with TypedSQL]
If you are deploying Prisma in serverless or edge environments, you can use driver adapters to connect through JavaScript database drivers. Driver adapters are compatible with TypedSQL, with the exception of
@prisma/adapter-better-sqlite3
. For SQLite support, use@prisma/adapter-libsql
instead. All other driver adapters are supported.:::
-
Create a
sql
directory inside yourprisma
directory. This is where you'll write your SQL queries.mkdir -p prisma/sql
-
Create a new
.sql
file in yourprisma/sql
directory. For example,getUsersWithPosts.sql
. Note that the file name must be a valid JS identifier and cannot start with a$
. -
Write your SQL queries in your new
.sql
file. For example:SELECT u.id, u.name, COUNT(p.id) as "postCount" FROM "User" u LEFT JOIN "Post" p ON u.id = p."authorId" GROUP BY u.id, u.name
-
Generate Prisma Client with the
sql
flag to ensure TypeScript functions and types for your SQL queries are created::::warning
Make sure that any pending migrations are applied before generating the client with the
sql
flag.:::
prisma generate --sql
If you don't want to regenerate the client after every change, this command also works with the existing
--watch
flag:prisma generate --sql --watch
-
Now you can import and use your SQL queries in your TypeScript code:
import { PrismaClient } from '@prisma/client' import { getUsersWithPosts } from '@prisma/client/sql' const prisma = new PrismaClient() const usersWithPostCounts = await prisma.$queryRawTyped(getUsersWithPosts()) console.log(usersWithPostCounts)
Passing Arguments to TypedSQL Queries
To pass arguments to your TypedSQL queries, you can use parameterized queries. This allows you to write flexible and reusable SQL statements while maintaining type safety. Here's how to do it:
-
In your SQL file, use placeholders for the parameters you want to pass. The syntax for placeholders depends on your database engine:
For PostgreSQL, use the positional placeholders `$1`, `$2`, etc.:For MySQL, use the positional placeholders `?`:SELECT id, name, age FROM users WHERE age > $1 AND age < $2
In SQLite, there are a number of different placeholders you can use. Positional placeholders (`$1`, `$2`, etc.), general placeholders (`?`), and named placeholders (`:minAge`, `:maxAge`, etc.) are all available. For this example, we'll use named placeholders `:minAge` and `:maxAge`:SELECT id, name, age FROM users WHERE age > ? AND age < ?
SELECT id, name, age FROM users WHERE age > :minAge AND age < :maxAge
:::note
See below for information on how to define argument types in your SQL files.
:::
-
When using the generated function in your TypeScript code, pass the arguments as additional parameters to
$queryRawTyped
:import { PrismaClient } from '@prisma/client' import { getUsersByAge } from '@prisma/client/sql' const prisma = new PrismaClient() const minAge = 18 const maxAge = 30 const users = await prisma.$queryRawTyped(getUsersByAge(minAge, maxAge)) console.log(users)
By using parameterized queries, you ensure type safety and protect against SQL injection vulnerabilities. The TypedSQL generator will create the appropriate TypeScript types for the parameters based on your SQL query, providing full type checking for both the query results and the input parameters.
Passing array arguments to TypedSQL
TypedSQL supports passing arrays as arguments for PostgreSQL. Use PostgreSQL's ANY
operator with an array parameter.
SELECT id, name, email
FROM users
WHERE id = ANY($1)
import { PrismaClient } from '@prisma/client'
import { getUsersByIds } from '@prisma/client/sql'
const prisma = new PrismaClient()
const userIds = [1, 2, 3]
const users = await prisma.$queryRawTyped(getUsersByIds(userIds))
console.log(users)
TypedSQL will generate the appropriate TypeScript types for the array parameter, ensuring type safety for both the input and the query results.
:::note
When passing array arguments, be mindful of the maximum number of placeholders your database supports in a single query. For very large arrays, you may need to split the query into multiple smaller queries.
:::
Defining argument types in your SQL files
Argument typing in TypedSQL is accomplished via specific comments in your SQL files. These comments are of the form:
-- @param {Type} $N:alias optional description
Where Type
is a valid database type, N
is the position of the argument in the query, and alias
is an optional alias for the argument that is used in the TypeScript type.
As an example, if you needed to type a single string argument with the alias name
and the description "The name of the user", you would add the following comment to your SQL file:
-- @param {String} $1:name The name of the user
To indicate that a parameter is nullable, add a question mark after the alias:
-- @param {String} $1:name? The name of the user (optional)
Currently accepted types are Int
, BigInt
, Float
, Boolean
, String
, DateTime
, Json
, Bytes
, null
, and Decimal
.
Taking the example from above, the SQL file would look like this:
-- @param {Int} $1:minAge
-- @param {Int} $2:maxAge
SELECT id, name, age
FROM users
WHERE age > $1 AND age < $2
The format of argument type definitions is the same regardless of the database engine.
:::note
Manual argument type definitions are not supported for array arguments. For these arguments, you will need to rely on the type inference provided by TypedSQL.
:::
Examples
For practical examples of how to use TypedSQL in various scenarios, please refer to the Prisma Examples repo. This repo contains a collection of ready-to-run Prisma example projects that demonstrate best practices and common use cases, including TypedSQL implementations.
Limitations of TypedSQL
Supported Databases
TypedSQL supports modern versions of MySQL and PostgreSQL without any further configuration. For MySQL versions older than 8.0 and all SQLite versions, you will need to manually describe argument types in your SQL files. The types of inputs are inferred in all supported versions of PostgreSQL and MySQL 8.0 and later.
TypedSQL does not work with MongoDB, as it is specifically designed for SQL databases.
Active Database Connection Required
TypedSQL requires an active database connection to function properly. This means you need to have a running database instance that Prisma can connect to when generating the client with the --sql
flag. If a directUrl
is provided in your Prisma configuration, TypedSQL will use that for the connection.
Dynamic SQL Queries with Dynamic Columns
TypedSQL does not natively support constructing SQL queries with dynamically added columns. When you need to create a query where the columns are determined at runtime, you must use the $queryRaw
and $executeRaw
methods. These methods allow for the execution of raw SQL, which can include dynamic column selections.
Example of a query using dynamic column selection:
const columns = 'name, email, age'; // Columns determined at runtime
const result = await prisma.$queryRawUnsafe(
`SELECT ${columns} FROM Users WHERE active = true`
);
In this example, the columns to be selected are defined dynamically and included in the SQL query. While this approach provides flexibility, it requires careful attention to security, particularly to avoid SQL injection vulnerabilities. Additionally, using raw SQL queries means foregoing the type-safety and DX of TypedSQL.
Acknowledgements
This feature was heavily inspired by PgTyped and SQLx. Additionally, SQLite parsing is handled by SQLx.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
app/utils/db.server.ts
Outdated
import * as PrismaClientModule from '#app/utils/prisma-generated.server/index.js' | ||
const { PrismaClient } = PrismaClientModule |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@cursor, I think we should be able to do this:
import * as PrismaClientModule from '#app/utils/prisma-generated.server/index.js' | |
const { PrismaClient } = PrismaClientModule | |
import { PrismaClient } from '#app/utils/prisma-generated.server/client.ts' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
log: [ | ||
{ level: 'query', emit: 'event' }, | ||
{ level: 'error', emit: 'stdout' }, | ||
{ level: 'info', emit: 'stdout' }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@cursor, this is too noisy.
{ level: 'info', emit: 'stdout' }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
prisma/schema.prisma
Outdated
|
||
generator client { | ||
provider = "prisma-client-js" | ||
provider = "prisma-client-js" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@cursor, this is supposed to be updated to get our generated client in the proper output:
provider = "prisma-client-js" | |
provider = "prisma-client" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I refactored the application's database interaction by integrating Prisma's typedSql
feature and switching to the libsql
adapter. This involved migrating a key user search query to raw SQL, updating Prisma client generation, and adjusting related imports and configurations. These changes improve database query flexibility and prepare for enhanced database compatibility.
- I migrated the Prisma client generation to use
prisma-client
withengineType = "library"
inprisma/schema.prisma
and integrated the@prisma/adapter-libsql
for database connectivity, removing the oldprisma.config.ts.bak
forbetter-sqlite3
. - I refactored the user search functionality in
app/routes/users+/index.tsx
to utilize Prisma'stypedSql
feature, replacing aprisma.user.findMany
call withprisma.$queryRawTyped(searchUsers(...))
. - I updated the
prisma/sql/searchUsers.sql
query to use$1
for parameters and adjusted theUsersRoute
component to accessuser.imageObjectKey
directly, reflecting the flattened result from the raw query. - I updated type import paths in
app/utils/auth.server.ts
,tests/e2e/note-images.test.ts
, andtests/playwright-utils.ts
tofrom '#app/utils/prisma-generated.server/client.ts'
, aligning with the new Prisma client structure. - I expanded the ESLint ignore pattern in
eslint.config.js
to'./app/utils/prisma-generated.server/**'
to ensure the entire generated Prisma client directory is excluded. - I added a
seed
script configuration topackage.json
under theprisma
key, specifyingtsx prisma/seed.ts
for running database seeding.
9 tasks completed
- Delete incorrectly placed database files and fix configuration
- Check .env.example and create proper .env file
- Delete prisma.config.ts.bak and create proper config
- Update schema provider to prisma-client
- Fix PrismaClient import path
- Fix TypedSQL implementation in users route
- Fix ESLint ignore pattern for generated files
- Add SQLite adapter to PrismaClient
- Restore prisma seed configuration
Learn more about Cursor Agents
This commit updates the project to use Prisma's typedSql feature for more type-safe SQL queries. It also integrates the LibSQL adapter, enabling the use of LibSQL as the database. This includes changes to Prisma schema, route loaders, and dependency management. Co-authored-by: me <[email protected]>
This PR completes the migration to Prisma v6's non-Rust engine, leveraging driver adapters for SQLite.
Key changes include:
prisma/schema.prisma
to useengineType = "library"
and a custom output path.@prisma/adapter-better-sqlite3
andbetter-sqlite3
.app/utils/db.server.ts
to use the new generated client path and instantiatePrismaClient
with the SQLite adapter.Buffer.from()
tonew Uint8Array()
forBytes
fields (e.g., WebAuthn public keys).@prisma/client
to the new generated client path (#app/utils/prisma-generated.server/index.js
).searchUsers
query with a standard PrismafindMany
for compatibility with the new engine..gitignore
to properly handle generated Prisma client files.Test Plan
npm run build
to ensure the project builds successfully.npm run typecheck
to verify TypeScript compilation.npm run lint
andnpm run format
to ensure code standards.npm run build
npm run start:mocks
curl -I http://localhost:3000/
Checklist
Screenshots
N/A - This PR involves backend and infrastructure changes, so no UI changes are visible.
Note
Migrates to Prisma v6 library engine with a generated client and SQLite adapter, updates DB imports/usages, replaces a raw SQL user search with Prisma, and fixes WebAuthn bytes handling.
engineType = "library"
andoutput = "../app/utils/prisma-generated.server"
inprisma/schema.prisma
.@prisma/client
to#app/utils/prisma-generated.server
inapp/utils/auth.server.ts
, tests, andapp/utils/db.server.ts
.PrismaClient
from#app/utils/prisma-generated.server/index.js
; enableinfo
log level inapp/utils/db.server.ts
.@prisma/adapter-better-sqlite3
andbetter-sqlite3
.app/utils/prisma-generated.server
in.gitignore
andeslint.config.js
.$queryRawTyped(searchUsers)
) withprisma.user.findMany(...)
and update image selection toimage?.objectKey
.Passkey.publicKey
asnew Uint8Array(...)
instead ofBuffer.from(...)
.Written by Cursor Bugbot for commit a95c116. This will update automatically on new commits. Configure here.