prisma icon indicating copy to clipboard operation
prisma copied to clipboard

Remove requirement for a running database when using TypedSQL

Open sorenbs opened this issue 1 year ago • 12 comments

Problem

When using the new TypedSQL functionality of Prisma ORM, you need to have a running database. This is because the functionality relies on the database to parse the SQL and return type information. This is causing problem for users running prisma generate in environments where they don't otherwise need a running database. See discussion here: https://github.com/prisma/prisma/discussions/25106#discussioncomment-10493523

Suggested solution

As Postgres is the most used database, we could bundle https://pglite.dev/ with the Prisma CLI and use that for type generation. This would eliminate the need for a running database when using TypedSQL, and would allow us to deprecate the new --sql flag.

Alternatives

We could alternatively replicate the full SQL parsing logic of each database in Prisma CLI. This is the approach we are using for SQLite, as it doesn't support returning type information for a SQL query.

Additional context

TypedSQL documentation.

sorenbs avatar Aug 30 '24 10:08 sorenbs

@sorenbs +1

Or at least to have a native way to specify where "raw query types" are (and must be). It would allow committing files locally instead of within node_modules, so there is no issue in CI/CD for example.

It's not perfect if the real DB defers, but it would be the same risk with pglite.

A hack is still possible to copy/paste myself types at the right place, but it's a hack.

sneko avatar Sep 17 '24 22:09 sneko

Or at least to have a native way to specify where "raw query types" are (and must be). It would allow committing files locally instead of within node_modules, so there is no issue in CI/CD for example.

You should be able to achieve this already by setting a custom output path in the generator block in the schema: https://www.prisma.io/docs/orm/prisma-client/setup-and-configuration/generating-prisma-client#using-a-custom-output-path

sorenbs avatar Sep 24 '24 18:09 sorenbs

Or at least to have a native way to specify where "raw query types" are (and must be). It would allow committing files locally instead of within node_modules, so there is no issue in CI/CD for example.

You should be able to achieve this already by setting a custom output path in the generator block in the schema: https://www.prisma.io/docs/orm/prisma-client/setup-and-configuration/generating-prisma-client#using-a-custom-output-path

We have to upload the engine itself that's 10mb.

afonsomatos avatar Oct 09 '24 06:10 afonsomatos

Instead of generating the types at CI/CD time, we are generating them at dev time and including them in our source code via a gulp task (see below).

It would be nice if we could generate these files without reaching inside the npm package.

// Prisma's typedSql capability stores its generated types deep within the pnpm package store.
// We use a gulp task to copy the generated typed sql into ./src/sql/*
gulp.task('copy-prisma-client', () => {
  const output = execSync('pnpm list --depth 1 --parseable @prisma/client', {
    encoding: 'utf8',
  })

  const prismaClientLine = output
    .split('\n')
    .find(line => line.includes('@prisma/client'))

  if (!prismaClientLine) {
    throw new Error('@prisma/client not found')
  }

  const prismaClientWithoutSuffix = prismaClientLine.replace(
    /@prisma\/client$/,
    '',
  )

  const prismaClientPath = path.join(
    prismaClientWithoutSuffix,
    '.prisma',
    'client',
    'sql',
  )

  return gulp
    .src([
      `${prismaClientPath}/**/*.mjs`,
      `${prismaClientPath}/**/*.d.ts`,
      `!${prismaClientPath}/**/*.edge.js`,
      `!${prismaClientPath}/**/*.edge.mjs`,
    ])
    .pipe(
      rename(path => {
        // Although our generated files have .mjs extension, the barrel file must be .js
        // in order for BOTH the typescript type check AND the esm package import to work
        if (path.basename === 'index' && path.extname === '.mjs') {
          path.extname = '.js'
        }
      }),
    )
    .pipe(gulp.dest('./src/sql'))
})

canadaduane avatar Oct 17 '24 19:10 canadaduane

@sorenbs +1

KlausNie avatar Oct 21 '24 05:10 KlausNie

PGlite maintainer here, let me know if there is anything you need if you decide to do this. Happy to help out.

samwillis avatar Oct 23 '24 06:10 samwillis

Having started using the typedSql feature recently after having to resort to hand-written queries, I can say that this is an awesome feature!

However, the burden of needing a real database available when generating the client is quite high. In fact, I had to modify our CI infrastructure substantially. We have a postinstall hook which would generate the client during pnpm install, which now requires a real database instance to be available. Having split our test, docker builds and release process, it required each step to be modified, availing an instance.

The most cumbersome part was the docker build, which basically had to be put into network=host mode, together with spinning up a database instance.

Bundling something like PGlite would make a massive difference in terms of using this, and improving the (potential) flakiness introduced by this extra, non-trivial dependency.

Personally, I like to have fully reproducible builds, meaning I'm not keen on generating the client and committing it to version control, which is the only workaround that exists today.

jacquesg avatar Oct 29 '24 23:10 jacquesg

For anybody else is struggling with this and not wanting to modify continuous deployment by adding files from local runs, this is our Dockerfile running a postgres server for the build phase:

FROM oven/bun:1.1.24-alpine AS installer

RUN apk add git libstdc++ postgresql postgresql-contrib

RUN mkdir -p /run/postgresql /var/lib/postgresql/data && \
  chown -R postgres:postgres /run/postgresql /var/lib/postgresql/data

RUN su postgres -c "initdb -D /var/lib/postgresql/data"
RUN su postgres -c "pg_ctl start -D /var/lib/postgresql/data && \
  psql -c \"ALTER USER postgres PASSWORD 'postgres';\" && \
  pg_ctl stop -D /var/lib/postgresql/data"

WORKDIR /app
COPY . .
COPY --from=node:22.6.0-alpine3.20 /usr/local/bin/node /usr/local/bin/node
WORKDIR /app/apps/backend

RUN bun install

ENV DATABASE_URL=postgresql://postgres:postgres@localhost:5432/postgres
RUN su postgres -c "pg_ctl start -D /var/lib/postgresql/data" && \
  pg_isready -h localhost -p 5432 -U postgres && \
  bunx prisma migrate deploy && \
  bunx prisma generate --sql && \
  su postgres -c "pg_ctl stop -D /var/lib/postgresql/data"

RUN bun run build

FROM oven/bun:1.1.24-alpine AS runner
WORKDIR /app
COPY --from=installer /app/apps/backend/dist .
COPY --from=installer /app/node_modules node_modules
COPY --from=installer /app/apps/backend/prisma prisma
COPY --from=node:22.6.0-alpine3.20 /usr/local/bin/node /usr/local/bin/node

CMD if [ \"$STAGING\" = \"1\" ]; then bunx prisma migrate reset --force && SEED=1 node main.js && node main.js; else bunx prisma migrate deploy && node main.js; fi```

coryoso avatar Nov 29 '24 13:11 coryoso

=> [event-service 3/6] COPY . .                                                                                        0.1s
 => [event-service 4/6] COPY yarn.lock ./                                                                               0.0s
 => [event-service 5/6] RUN yarn install                                                                               36.5s
 => ERROR [event-service 6/6] RUN yarn build-local                                                                      3.8s
------
 > [event-service 6/6] RUN yarn build-local:
0.363 yarn run v1.22.22
0.394 $ NODE_ENV=local nest build
3.705 src/user-stage/user-stage.repository.ts:5:10 - error TS2305: Module '"@prisma/client/sql"' has no exported member 'getUserGroupedEventsWithStages'.
3.705 
3.705 5 import { getUserGroupedEventsWithStages } from '@prisma/client/sql';
3.705            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3.705 
3.706 Found 1 error(s).
3.706 
3.748 error Command failed with exit code 1.
3.748 info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.

Rossella-Mascia-Neosyn avatar Jan 08 '25 12:01 Rossella-Mascia-Neosyn

Any updates?

Red-Killer avatar Mar 19 '25 18:03 Red-Killer

Using TypedSql with Docker is even easier in Prisma >= 6.8.0 thanks to the introduction of the dev server.

RUN \
    # Start Prisma's local dev server and capture the output
    npx prisma dev --port 54321 > /tmp/prisma-output.txt 2>&1 & \
    PRISMA_PID=$! && \
    # Wait for it to start
    sleep 10 && \
    # Extract the DATABASE_URL from the output
    export DATABASE_URL=$(grep -o 'DATABASE_URL="prisma+postgres://localhost:[0-9]*/?api_key=[^"]*"' /tmp/prisma-output.txt | sed 's/DATABASE_URL=//g' | tr -d '"' || echo "") && \
    # If extraction failed, show an error
    if [ -z "$DATABASE_URL" ]; then \
      echo "Failed to extract DATABASE_URL from Prisma dev output" && \
      exit 1; \
    fi && \
    # Run migrations
    npx prisma migrate deploy && \
    # Build the server
    npx prisma generate --sql  && \
    npm run build && \
    # Kill the Prisma dev server
    kill $PRISMA_PID || true

// Updated version.

only problem is that this is "time" based.

kbzowski avatar May 16 '25 13:05 kbzowski

Use Case: Build Environment Without Database Access

We're encountering challenges with typed-sql in our GCP Cloud Build + Docker workflow that might be relevant to others in similar situations.

Current Setup:

  • Building applications on GCP Cloud Build with Docker
  • No database access during build time (by design)
  • Using Prisma with typed-sql

The Problem: When building without database access, typed-sql fails because it cannot verify that referenced fields exist in the database schema. This creates a chicken-and-egg problem:

  1. We need to run migrations before the build can succeed
  2. But we can't be certain the build will complete successfully before running migrations
  3. This introduces risk and prevents us from properly splitting our deployment workflow

Impact:

  • Reduces our ability to create clean separation between build and deployment phases
  • Forces us to either:
    • Run migrations speculatively (risky)
    • Maintain database connections during build (not ideal for our architecture)

Potential Solutions We're Considering:

  • Generate typed-sql at build time with full schema access, but this still requires an open database connection during builds
  • Some form of schema caching or offline type generation

I've had to rewrite my shitty feedback with Claude sorry for it.

Ketcap avatar Jun 13 '25 09:06 Ketcap

I had the same issue when containerizing our application. Including a temporary db during build wasn't an option for us, so we used a custom output path for the prisma client as @sorenbs mentioned:

generator client {
  provider        = "prisma-client-js"
  output          = "../generated/prisma"
  moduleFormat    = "esm"
  previewFeatures = ["views", "typedSql"]
}

We excluded everything except the TypedSQL-specific files to address the engine size issue mentioned by @afonsomatos (in our case it would have been ~20MB)

# Ignore everything in ./src/generated/prisma
/src/generated/prisma/*
# Allow TypedSQL specific files
!/src/generated/prisma/sql/
!/src/generated/prisma/sql/**

In our Dockerfile build stage, we simply generate the general parts of the client and then copy the pre-generated TypedSQL-specific parts from the codebase:

RUN npm run prisma generate
COPY src/generated/prisma/sql ./src/generated/prisma/sql

This solution is a bit hacky but works for now. hope we see a better one in the future

jonathan-baernthaler avatar Jun 24 '25 18:06 jonathan-baernthaler

Here's my approach to include typedSql generated files in your Docker image without modifying your Prisma generator client configuration:

First, update your .gitignore to specifically include the sql folder within node_modules/.prisma/client/:

/node_modules/*
# Un-ignore .prisma/client/sql folder
!/node_modules/.prisma
/node_modules/.prisma/*
!/node_modules/.prisma/client/
/node_modules/.prisma/client/*
!/node_modules/.prisma/client/sql/

Then, in your Dockerfile, copy the necessary Prisma files and your pre-computed SQL declarations:

# Adapt to your favorite node image
FROM node-alpine
WORKDIR /my-app
COPY ./package.json ./

# Adapt to your package manager
COPY ./yarn.lock ./ 

# Copy schema and migrations
COPY ./prisma ./prisma 
# Copy pre-compute sql declaration files
COPY ./node_modules/.prisma/client/sql ./node_modules/.prisma/client/sql 

RUN yarn install --frozen-lockfile && yarn cache clean
# Update client declaration files to match `prisma` folder (no db connection needed)
RUN npx prisma generate

# Your port
EXPOSE 1234
# Start your server here
CMD ["npm", "run", "start"]

This method maintains Prisma's default paths while seamlessly embedding the required files in your Docker image.

ctjhoa avatar Jun 25 '25 15:06 ctjhoa

We are committing the client/sql folder as we don't want to rely on a PostgreSQL database in our CI to generate typings.

None of these alternatives work when using the new prisma-client generator preview (as of Prisma 6.15.0).

Here are the issues:

it deletes the sql folder

  1. If the client/sql folder exists, running prisma generate (with ou without --sql), it errors:
prisma/client exists and is not empty but doesn't look like a generated Prisma Client. Please check your output path and remove the existing directory if you indeed want to generate the Prisma Client in that location.
  1. If we create a client/client.ts dummy file to bypass this error, it generated everything except it deletes de sql folder (when running prisma generate without --sql arg)

danilofuchs avatar Sep 05 '25 16:09 danilofuchs

@danilofuchs When attempting to migrate from prisma-client-js to the new prisma-client provider, I faced the same issue. The only workaround I found was to have something like this:

generator client {
  provider        = "prisma-client"
  previewFeatures = ["typedSql"]
  binaryTargets   = ["native", "linux-musl-openssl-3.0.x"]
  output          = env("PRISMA_OUTPUT")
}

So I can drive the prisma generate output with an environment variable and generate into 2 different folders. eg. PRISMA_OUTPUT=../src/generated prisma generate PRISMA_OUTPUT=../src/generated-sql prisma generate --sql Then you could completely ignore the src/generated folder and partially ignore the src/generated-sql folder to track the sql part. Not very elegant but it's the only solution I see at the moment.

ctjhoa avatar Sep 19 '25 11:09 ctjhoa

@danilofuchs @ctjhoa Another way is to make output directory empty temporarily before generating prisma client. Content of prisma/schema.prisma file:

generator client {
  provider        = "prisma-client"
  output          = "../generated/prisma"
  previewFeatures = ["typedSql"]
}

datasource db {
  provider = "postgresql"
}

Content of .gitignore file:

/generated/prisma/*
!/generated/prisma/sql*

Content of Dockerfile:

FROM node:20-alpine AS builder

WORKDIR /app

# Copy package files and install dependencies
COPY package.json yarn.lock ./
RUN yarn install --frozen-lockfile

# Copy source code and Prisma configuration
COPY . .

# Move typedSQL to a temp directory
RUN mkdir -p ./temp
RUN mv generated/prisma/sql* ./temp

# Generate Prisma client
RUN npx prisma generate

# Move typedSQL back to the original location
RUN mv ./temp/* ./generated/prisma/
RUN rm -rf ./temp

# Rest of your docker file ...

milad2golnia avatar Nov 26 '25 11:11 milad2golnia