Forráskód Böngészése

Upgrade Prisma ORM from v6 to v7 (#118)

* dev container

* upgraded to prisma v7

* connection pool

* update prisma in docker

* removed eslint file

* env

* config

* config

* env

* DATABASE_URL

* postgresql-client

* init

* dev

* docker

* dotenv
Bernt Christian Egeland 6 hónapja
szülő
commit
354bfce319

+ 0 - 3
.devcontainer/Dockerfile

@@ -21,9 +21,6 @@ RUN if [ "$USER_GID" != "1000" ] || [ "$USER_UID" != "1000" ]; then \
 WORKDIR /workspace
 RUN chown $USERNAME:$USERNAME /workspace
 
-COPY package*.json ./
-RUN npm install
-
 COPY . .
 
 EXPOSE 3000

+ 22 - 12
Dockerfile

@@ -1,11 +1,13 @@
 FROM node:20-alpine AS base
+RUN apk add --no-cache libc6-compat openssl postgresql-client bash ffmpeg
 
 # Install dependencies only when needed
 FROM base AS deps
-RUN apk add --no-cache libc6-compat
 WORKDIR /app
 
 COPY package.json package-lock.json* ./
+COPY prisma ./prisma/
+COPY prisma.config.ts ./prisma.config.ts
 RUN npm ci
 
 # Rebuild the source code only when needed
@@ -33,22 +35,30 @@ ENV NEXT_TELEMETRY_DISABLED=1
 RUN addgroup --system --gid 1001 nodejs && \
     adduser --system --uid 1001 --home /home/nextjs nextjs
 
-# Install runtime dependencies (prisma CLI + serverExternalPackages)
-# Must run before standalone copy to avoid conflicts with bundled node_modules
-COPY --from=builder --chown=nextjs:nodejs /app/package.json ./package.json
-RUN npm install @prisma/client@6.19.2 prisma@6.19.2 @prisma/adapter-pg@6.19.2 pg && \
-    chown -R nextjs:nodejs /app/node_modules
-
-RUN apk add --no-cache bash ffmpeg
-
+# Copy public assets
 COPY --from=builder /app/public ./public
-COPY --from=builder /app/prisma ./prisma
-COPY --from=builder /app/init-db.sh ./init-db.sh
 
-# Automatically leverage output traces to reduce image size
+# Set correct permissions for prerender cache
+RUN mkdir .next && chown nextjs:nodejs .next
+
+# Copy standalone build
 COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
 COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
 
+# Copy prisma schema and config for migrations
+COPY --from=builder --chown=nextjs:nodejs /app/prisma ./prisma
+COPY --from=builder --chown=nextjs:nodejs /app/prisma.config.ts ./prisma.config.ts
+
+# Copy generated Prisma client
+COPY --from=builder --chown=nextjs:nodejs /app/src/generated ./src/generated
+
+# Install runtime packages for Prisma v7
+RUN npm install prisma@7.6.0 @prisma/client@7.6.0 @prisma/adapter-pg@7.6.0 pg dotenv
+
+# Copy init script
+COPY --chown=nextjs:nodejs init-db.sh ./init-db.sh
+RUN chmod +x ./init-db.sh
+
 # Create uploads directories
 RUN mkdir -p /app/data/uploads && chown -R nextjs:nodejs /app/data
 

+ 0 - 18
eslint.config.mjs

@@ -1,18 +0,0 @@
-import { defineConfig, globalIgnores } from "eslint/config";
-import nextVitals from "eslint-config-next/core-web-vitals";
-import nextTs from "eslint-config-next/typescript";
-
-const eslintConfig = defineConfig([
-  ...nextVitals,
-  ...nextTs,
-  // Override default ignores of eslint-config-next.
-  globalIgnores([
-    // Default ignores of eslint-config-next:
-    ".next/**",
-    "out/**",
-    "build/**",
-    "next-env.d.ts",
-  ]),
-]);
-
-export default eslintConfig;

+ 7 - 2
init-db.sh

@@ -5,8 +5,13 @@ trap 'echo "An error occurred. Exiting..."; exit 1' ERR
 
 cmd="$@"
 
+# Construct DATABASE_URL from environment variables if not already set
+if [ -z "$DATABASE_URL" ]; then
+  export DATABASE_URL="postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${POSTGRES_HOST}:${POSTGRES_PORT}/${POSTGRES_DB}"
+fi
+
 echo "Waiting for PostgreSQL to be ready..."
-until npx prisma db execute --schema prisma/schema.prisma --stdin <<< "SELECT 1" 2>/dev/null; do
+until pg_isready -d "$DATABASE_URL" -q; do
   echo "PostgreSQL is unavailable - sleeping 2s..."
   sleep 2
 done
@@ -18,7 +23,7 @@ if ! npx prisma migrate deploy 2>/dev/null; then
   # Create empty migration table so migrate deploy can run.
   # 0_init is fully idempotent — safe on any DB state.
   echo "Existing database detected. Creating migration table..."
-  npx prisma db execute --schema prisma/schema.prisma --stdin <<< 'CREATE TABLE IF NOT EXISTS "_prisma_migrations" ("id" VARCHAR(36) PRIMARY KEY NOT NULL, "checksum" VARCHAR(64) NOT NULL, "finished_at" TIMESTAMPTZ, "migration_name" VARCHAR(255) NOT NULL, "logs" TEXT, "rolled_back_at" TIMESTAMPTZ, "started_at" TIMESTAMPTZ NOT NULL DEFAULT now(), "applied_steps_count" INTEGER NOT NULL DEFAULT 0)'
+  npx prisma db execute --stdin <<< 'CREATE TABLE IF NOT EXISTS "_prisma_migrations" ("id" VARCHAR(36) PRIMARY KEY NOT NULL, "checksum" VARCHAR(64) NOT NULL, "finished_at" TIMESTAMPTZ, "migration_name" VARCHAR(255) NOT NULL, "logs" TEXT, "rolled_back_at" TIMESTAMPTZ, "started_at" TIMESTAMPTZ NOT NULL DEFAULT now(), "applied_steps_count" INTEGER NOT NULL DEFAULT 0)'
   npx prisma migrate deploy
 fi
 echo "Migrations applied successfully!"

+ 539 - 92
package-lock.json

@@ -15,7 +15,8 @@
         "@dnd-kit/sortable": "^10.0.0",
         "@dnd-kit/utilities": "^3.2.2",
         "@hookform/resolvers": "^5.2.2",
-        "@prisma/client": "^6.19.2",
+        "@prisma/adapter-pg": "^7.6.0",
+        "@prisma/client": "^7.6.0",
         "@react-pdf/renderer": "^4.3.2",
         "@sendgrid/mail": "^8.1.6",
         "@tanstack/react-query": "^5.90.21",
@@ -47,7 +48,7 @@
         "pdf-lib": "^1.17.1",
         "posthog-js": "^1.360.2",
         "postmark": "^4.0.5",
-        "prisma": "^6.19.2",
+        "prisma": "^7.6.0",
         "qrcode": "^1.5.4",
         "qrcode.react": "^4.2.0",
         "radix-ui": "^1.4.3",
@@ -1567,6 +1568,27 @@
         "specificity": "bin/cli.js"
       }
     },
+    "node_modules/@clack/core": {
+      "version": "0.5.0",
+      "resolved": "https://registry.npmjs.org/@clack/core/-/core-0.5.0.tgz",
+      "integrity": "sha512-p3y0FIOwaYRUPRcMO7+dlmLh8PSRcrjuTndsiA0WAFbWES0mLZlrjVoBRZ9DzkPFJZG6KGkJmoEAY0ZcVWTkow==",
+      "license": "MIT",
+      "dependencies": {
+        "picocolors": "^1.0.0",
+        "sisteransi": "^1.0.5"
+      }
+    },
+    "node_modules/@clack/prompts": {
+      "version": "0.11.0",
+      "resolved": "https://registry.npmjs.org/@clack/prompts/-/prompts-0.11.0.tgz",
+      "integrity": "sha512-pMN5FcrEw9hUkZA4f+zLlzivQSeQf5dRGJjSUbvVYDLvpKCdQx5OaknvKzgbtXOizhP+SJJJjqEbOe55uKKfAw==",
+      "license": "MIT",
+      "dependencies": {
+        "@clack/core": "0.5.0",
+        "picocolors": "^1.0.0",
+        "sisteransi": "^1.0.5"
+      }
+    },
     "node_modules/@csstools/color-helpers": {
       "version": "6.0.2",
       "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-6.0.2.tgz",
@@ -1978,6 +2000,33 @@
         "@noble/ciphers": "^1.0.0"
       }
     },
+    "node_modules/@electric-sql/pglite": {
+      "version": "0.4.1",
+      "resolved": "https://registry.npmjs.org/@electric-sql/pglite/-/pglite-0.4.1.tgz",
+      "integrity": "sha512-mZ9NzzUSYPOCnxHH1oAHPRzoMFJHY472raDKwXl/+6oPbpdJ7g8LsCN4FSaIIfkiCKHhb3iF/Zqo3NYxaIhU7Q==",
+      "license": "Apache-2.0"
+    },
+    "node_modules/@electric-sql/pglite-socket": {
+      "version": "0.1.1",
+      "resolved": "https://registry.npmjs.org/@electric-sql/pglite-socket/-/pglite-socket-0.1.1.tgz",
+      "integrity": "sha512-p2hoXw3Z3LQHwTeikdZNsFBOvXGqKY2hk51BBw+8NKND8eoH+8LFOtW9Z8CQKmTJ2qqGYu82ipqiyFZOTTXNfw==",
+      "license": "Apache-2.0",
+      "bin": {
+        "pglite-server": "dist/scripts/server.js"
+      },
+      "peerDependencies": {
+        "@electric-sql/pglite": "0.4.1"
+      }
+    },
+    "node_modules/@electric-sql/pglite-tools": {
+      "version": "0.3.1",
+      "resolved": "https://registry.npmjs.org/@electric-sql/pglite-tools/-/pglite-tools-0.3.1.tgz",
+      "integrity": "sha512-C+T3oivmy9bpQvSxVqXA1UDY8cB9Eb9vZHL9zxWwEUfDixbXv4G3r2LjoTdR33LD8aomR3O9ZXEO3XEwr/cUCA==",
+      "license": "Apache-2.0",
+      "peerDependencies": {
+        "@electric-sql/pglite": "0.4.1"
+      }
+    },
     "node_modules/@emnapi/core": {
       "version": "1.8.1",
       "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.8.1.tgz",
@@ -2129,7 +2178,6 @@
       "version": "1.19.11",
       "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.11.tgz",
       "integrity": "sha512-dr8/3zEaB+p0D2n/IUrlPF1HZm586qgJNXK1a9fhg/PzdtkK7Ksd5l312tJX2yBuALqDYBlG20QEbayqPyxn+g==",
-      "dev": true,
       "license": "MIT",
       "engines": {
         "node": ">=18.14.1"
@@ -2812,6 +2860,12 @@
         "@jridgewell/sourcemap-codec": "^1.4.14"
       }
     },
+    "node_modules/@kurkle/color": {
+      "version": "0.3.4",
+      "resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.4.tgz",
+      "integrity": "sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==",
+      "license": "MIT"
+    },
     "node_modules/@levischuck/tiny-cbor": {
       "version": "0.2.11",
       "resolved": "https://registry.npmjs.org/@levischuck/tiny-cbor/-/tiny-cbor-0.2.11.tgz",
@@ -3898,18 +3952,32 @@
       "integrity": "sha512-U48CbtmX5kETZvWjaJVlublSA1aLV99m71TQtgxWksBMXINS/3C7j+KqlMO6wH7SuaEZQnjaxh1KYGH4nRCaaA==",
       "license": "MIT"
     },
+    "node_modules/@prisma/adapter-pg": {
+      "version": "7.6.0",
+      "resolved": "https://registry.npmjs.org/@prisma/adapter-pg/-/adapter-pg-7.6.0.tgz",
+      "integrity": "sha512-BjHNmJqqa42NqJSDPnXUfwUofWo8LJY7Ui2gqxN4DmAOb+H/gGKv+hln2Xq/1kSJXPW5AXMXuNiPDMpywvyIOw==",
+      "license": "Apache-2.0",
+      "dependencies": {
+        "@prisma/driver-adapter-utils": "7.6.0",
+        "@types/pg": "^8.16.0",
+        "pg": "^8.16.3",
+        "postgres-array": "3.0.4"
+      }
+    },
     "node_modules/@prisma/client": {
-      "version": "6.19.2",
-      "resolved": "https://registry.npmjs.org/@prisma/client/-/client-6.19.2.tgz",
-      "integrity": "sha512-gR2EMvfK/aTxsuooaDA32D8v+us/8AAet+C3J1cc04SW35FPdZYgLF+iN4NDLUgAaUGTKdAB0CYenu1TAgGdMg==",
-      "hasInstallScript": true,
+      "version": "7.6.0",
+      "resolved": "https://registry.npmjs.org/@prisma/client/-/client-7.6.0.tgz",
+      "integrity": "sha512-7Pe/1ayh3GgWPEg4mmT4ax77LJ1wC+XlnIFvQ94bLP2DsUnOpnruQQR3Jw7r+Frthk94QqDNxo3FjSg8h9PXeQ==",
       "license": "Apache-2.0",
+      "dependencies": {
+        "@prisma/client-runtime-utils": "7.6.0"
+      },
       "engines": {
-        "node": ">=18.18"
+        "node": "^20.19 || ^22.12 || >=24.0"
       },
       "peerDependencies": {
         "prisma": "*",
-        "typescript": ">=5.1.0"
+        "typescript": ">=5.4.0"
       },
       "peerDependenciesMeta": {
         "prisma": {
@@ -3920,61 +3988,184 @@
         }
       }
     },
+    "node_modules/@prisma/client-runtime-utils": {
+      "version": "7.6.0",
+      "resolved": "https://registry.npmjs.org/@prisma/client-runtime-utils/-/client-runtime-utils-7.6.0.tgz",
+      "integrity": "sha512-fD7jlqubsZvVODKvsp9lOpXVecx2aWGxC2l35Ioz2t+teUJ5CfR0SAMsi7UkU1VvaZmmm+DS6BdujF622nY7tQ==",
+      "license": "Apache-2.0"
+    },
     "node_modules/@prisma/config": {
-      "version": "6.19.2",
-      "resolved": "https://registry.npmjs.org/@prisma/config/-/config-6.19.2.tgz",
-      "integrity": "sha512-kadBGDl+aUswv/zZMk9Mx0C8UZs1kjao8H9/JpI4Wh4SHZaM7zkTwiKn/iFLfRg+XtOAo/Z/c6pAYhijKl0nzQ==",
+      "version": "7.6.0",
+      "resolved": "https://registry.npmjs.org/@prisma/config/-/config-7.6.0.tgz",
+      "integrity": "sha512-MuAz1MK4PeG5/03YzfzX3CnFVHQ6qePGwUpQRzPzX5tT0ffJ3Tzi9zJZbBc+VzEGFCM8ghW/gTVDR85Syjt+Yw==",
       "license": "Apache-2.0",
       "dependencies": {
         "c12": "3.1.0",
         "deepmerge-ts": "7.1.5",
-        "effect": "3.18.4",
+        "effect": "3.20.0",
         "empathic": "2.0.0"
       }
     },
     "node_modules/@prisma/debug": {
-      "version": "6.19.2",
-      "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-6.19.2.tgz",
-      "integrity": "sha512-lFnEZsLdFLmEVCVNdskLDCL8Uup41GDfU0LUfquw+ercJC8ODTuL0WNKgOKmYxCJVvFwf0OuZBzW99DuWmoH2A==",
+      "version": "7.6.0",
+      "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-7.6.0.tgz",
+      "integrity": "sha512-LpHr3qos4lQZ6sxwjStf59YBht7m9/QF7NSQsMH6qGENWZu2w3UkQUGn1h5iRkDjnWRj3VHykOu9qFhps4ADvA==",
       "license": "Apache-2.0"
     },
+    "node_modules/@prisma/dev": {
+      "version": "0.24.3",
+      "resolved": "https://registry.npmjs.org/@prisma/dev/-/dev-0.24.3.tgz",
+      "integrity": "sha512-ffHlQuKXZiaDt9Go0OnCTdJZrHxK0k7omJKNV86/VjpsXu5EIHZLK0T7JSWgvNlJwh56kW9JFu9v0qJciFzepg==",
+      "license": "ISC",
+      "dependencies": {
+        "@electric-sql/pglite": "0.4.1",
+        "@electric-sql/pglite-socket": "0.1.1",
+        "@electric-sql/pglite-tools": "0.3.1",
+        "@hono/node-server": "1.19.11",
+        "@prisma/get-platform": "7.2.0",
+        "@prisma/query-plan-executor": "7.2.0",
+        "@prisma/streams-local": "0.1.2",
+        "foreground-child": "3.3.1",
+        "get-port-please": "3.2.0",
+        "hono": "^4.12.8",
+        "http-status-codes": "2.3.0",
+        "pathe": "2.0.3",
+        "proper-lockfile": "4.1.2",
+        "remeda": "2.33.4",
+        "std-env": "3.10.0",
+        "valibot": "1.2.0",
+        "zeptomatch": "2.1.0"
+      }
+    },
+    "node_modules/@prisma/dev/node_modules/std-env": {
+      "version": "3.10.0",
+      "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz",
+      "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==",
+      "license": "MIT"
+    },
+    "node_modules/@prisma/driver-adapter-utils": {
+      "version": "7.6.0",
+      "resolved": "https://registry.npmjs.org/@prisma/driver-adapter-utils/-/driver-adapter-utils-7.6.0.tgz",
+      "integrity": "sha512-D8j3p0RnhLuufMaRLX6QqtGgPC5Ao3l5oFP6Q5AL0rTHi4vna+NzGEipwCsfvcSvaGFCbsH3lsTMbb4WvY+ovA==",
+      "license": "Apache-2.0",
+      "dependencies": {
+        "@prisma/debug": "7.6.0"
+      }
+    },
     "node_modules/@prisma/engines": {
-      "version": "6.19.2",
-      "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-6.19.2.tgz",
-      "integrity": "sha512-TTkJ8r+uk/uqczX40wb+ODG0E0icVsMgwCTyTHXehaEfb0uo80M9g1aW1tEJrxmFHeOZFXdI2sTA1j1AgcHi4A==",
+      "version": "7.6.0",
+      "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-7.6.0.tgz",
+      "integrity": "sha512-Sn5edRzhHqgRV2M+A0eIbY442B4mReWWf3pKs/LKreYgW7oa/up8JtK/s4iv/EQA097cyboZ08mmkpbLp+tZ3w==",
       "hasInstallScript": true,
       "license": "Apache-2.0",
       "dependencies": {
-        "@prisma/debug": "6.19.2",
-        "@prisma/engines-version": "7.1.1-3.c2990dca591cba766e3b7ef5d9e8a84796e47ab7",
-        "@prisma/fetch-engine": "6.19.2",
-        "@prisma/get-platform": "6.19.2"
+        "@prisma/debug": "7.6.0",
+        "@prisma/engines-version": "7.6.0-1.75cbdc1eb7150937890ad5465d861175c6624711",
+        "@prisma/fetch-engine": "7.6.0",
+        "@prisma/get-platform": "7.6.0"
       }
     },
     "node_modules/@prisma/engines-version": {
-      "version": "7.1.1-3.c2990dca591cba766e3b7ef5d9e8a84796e47ab7",
-      "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-7.1.1-3.c2990dca591cba766e3b7ef5d9e8a84796e47ab7.tgz",
-      "integrity": "sha512-03bgb1VD5gvuumNf+7fVGBzfpJPjmqV423l/WxsWk2cNQ42JD0/SsFBPhN6z8iAvdHs07/7ei77SKu7aZfq8bA==",
+      "version": "7.6.0-1.75cbdc1eb7150937890ad5465d861175c6624711",
+      "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-7.6.0-1.75cbdc1eb7150937890ad5465d861175c6624711.tgz",
+      "integrity": "sha512-r51DLcJ8bDRSrBEJF3J4cinoWyGA7rfP2mG6lD90VqIbGNOkbfcLcXalSVjq5Y6brQS3vcjrq4GbyUb1Cb7vkw==",
       "license": "Apache-2.0"
     },
+    "node_modules/@prisma/engines/node_modules/@prisma/get-platform": {
+      "version": "7.6.0",
+      "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-7.6.0.tgz",
+      "integrity": "sha512-ohZDwXvtmnbzOcutR2D13lDWpZP1wQjmPyztmt0AwXLzQI7q95EE7NYCvS+M6N6SivT+BM0NOqLmTH3wms4L3A==",
+      "license": "Apache-2.0",
+      "dependencies": {
+        "@prisma/debug": "7.6.0"
+      }
+    },
     "node_modules/@prisma/fetch-engine": {
-      "version": "6.19.2",
-      "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-6.19.2.tgz",
-      "integrity": "sha512-h4Ff4Pho+SR1S8XerMCC12X//oY2bG3Iug/fUnudfcXEUnIeRiBdXHFdGlGOgQ3HqKgosTEhkZMvGM9tWtYC+Q==",
+      "version": "7.6.0",
+      "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-7.6.0.tgz",
+      "integrity": "sha512-N575Ni95c3FkduWY/eKTHqNYgNbceZ1tQaSknVtJjpKmiiBXmniESn/GTxsDvICC4ZeiNrXxioGInzQrCdx16w==",
       "license": "Apache-2.0",
       "dependencies": {
-        "@prisma/debug": "6.19.2",
-        "@prisma/engines-version": "7.1.1-3.c2990dca591cba766e3b7ef5d9e8a84796e47ab7",
-        "@prisma/get-platform": "6.19.2"
+        "@prisma/debug": "7.6.0",
+        "@prisma/engines-version": "7.6.0-1.75cbdc1eb7150937890ad5465d861175c6624711",
+        "@prisma/get-platform": "7.6.0"
+      }
+    },
+    "node_modules/@prisma/fetch-engine/node_modules/@prisma/get-platform": {
+      "version": "7.6.0",
+      "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-7.6.0.tgz",
+      "integrity": "sha512-ohZDwXvtmnbzOcutR2D13lDWpZP1wQjmPyztmt0AwXLzQI7q95EE7NYCvS+M6N6SivT+BM0NOqLmTH3wms4L3A==",
+      "license": "Apache-2.0",
+      "dependencies": {
+        "@prisma/debug": "7.6.0"
       }
     },
     "node_modules/@prisma/get-platform": {
-      "version": "6.19.2",
-      "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-6.19.2.tgz",
-      "integrity": "sha512-PGLr06JUSTqIvztJtAzIxOwtWKtJm5WwOG6xpsgD37Rc84FpfUBGLKz65YpJBGtkRQGXTYEFie7pYALocC3MtA==",
+      "version": "7.2.0",
+      "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-7.2.0.tgz",
+      "integrity": "sha512-k1V0l0Td1732EHpAfi2eySTezyllok9dXb6UQanajkJQzPUGi3vO2z7jdkz67SypFTdmbnyGYxvEvYZdZsMAVA==",
+      "license": "Apache-2.0",
+      "dependencies": {
+        "@prisma/debug": "7.2.0"
+      }
+    },
+    "node_modules/@prisma/get-platform/node_modules/@prisma/debug": {
+      "version": "7.2.0",
+      "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-7.2.0.tgz",
+      "integrity": "sha512-YSGTiSlBAVJPzX4ONZmMotL+ozJwQjRmZweQNIq/ER0tQJKJynNkRB3kyvt37eOfsbMCXk3gnLF6J9OJ4QWftw==",
+      "license": "Apache-2.0"
+    },
+    "node_modules/@prisma/query-plan-executor": {
+      "version": "7.2.0",
+      "resolved": "https://registry.npmjs.org/@prisma/query-plan-executor/-/query-plan-executor-7.2.0.tgz",
+      "integrity": "sha512-EOZmNzcV8uJ0mae3DhTsiHgoNCuu1J9mULQpGCh62zN3PxPTd+qI9tJvk5jOst8WHKQNwJWR3b39t0XvfBB0WQ==",
+      "license": "Apache-2.0"
+    },
+    "node_modules/@prisma/streams-local": {
+      "version": "0.1.2",
+      "resolved": "https://registry.npmjs.org/@prisma/streams-local/-/streams-local-0.1.2.tgz",
+      "integrity": "sha512-l49yTxKKF2odFxaAXTmwmkBKL3+bVQ1tFOooGifu4xkdb9NMNLxHj27XAhTylWZod8I+ISGM5erU1xcl/oBCtg==",
+      "license": "Apache-2.0",
+      "dependencies": {
+        "ajv": "^8.12.0",
+        "better-result": "^2.7.0",
+        "env-paths": "^3.0.0",
+        "proper-lockfile": "^4.1.2"
+      },
+      "engines": {
+        "bun": ">=1.3.6",
+        "node": ">=22.0.0"
+      }
+    },
+    "node_modules/@prisma/streams-local/node_modules/env-paths": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-3.0.0.tgz",
+      "integrity": "sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A==",
+      "license": "MIT",
+      "engines": {
+        "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/@prisma/studio-core": {
+      "version": "0.27.3",
+      "resolved": "https://registry.npmjs.org/@prisma/studio-core/-/studio-core-0.27.3.tgz",
+      "integrity": "sha512-AADjNFPdsrglxHQVTmHFqv6DuKQZ5WY4p5/gVFY017twvNrSwpLJ9lqUbYYxEu2W7nbvVxTZA8deJ8LseNALsw==",
       "license": "Apache-2.0",
       "dependencies": {
-        "@prisma/debug": "6.19.2"
+        "@radix-ui/react-toggle": "1.1.10",
+        "chart.js": "4.5.1"
+      },
+      "engines": {
+        "node": "^20.19 || ^22.12 || >=24.0",
+        "pnpm": "8"
+      },
+      "peerDependencies": {
+        "@types/react": "^18.0.0 || ^19.0.0",
+        "react": "^18.0.0 || ^19.0.0",
+        "react-dom": "^18.0.0 || ^19.0.0"
       }
     },
     "node_modules/@protobufjs/aspromise": {
@@ -7995,6 +8186,17 @@
         "@types/node": "*"
       }
     },
+    "node_modules/@types/pg": {
+      "version": "8.20.0",
+      "resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.20.0.tgz",
+      "integrity": "sha512-bEPFOaMAHTEP1EzpvHTbmwR8UsFyHSKsRisLIHVMXnpNefSbGA1bD6CVy+qKjGSqmZqNqBDV2azOBo8TgkcVow==",
+      "license": "MIT",
+      "dependencies": {
+        "@types/node": "*",
+        "pg-protocol": "*",
+        "pg-types": "^2.2.0"
+      }
+    },
     "node_modules/@types/qrcode": {
       "version": "1.5.6",
       "resolved": "https://registry.npmjs.org/@types/qrcode/-/qrcode-1.5.6.tgz",
@@ -8283,7 +8485,6 @@
       "version": "8.18.0",
       "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz",
       "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==",
-      "dev": true,
       "license": "MIT",
       "dependencies": {
         "fast-deep-equal": "^3.1.3",
@@ -8489,8 +8690,6 @@
       "resolved": "https://registry.npmjs.org/aws-ssl-profiles/-/aws-ssl-profiles-1.1.2.tgz",
       "integrity": "sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g==",
       "license": "MIT",
-      "optional": true,
-      "peer": true,
       "engines": {
         "node": ">= 6.0.0"
       }
@@ -8729,6 +8928,18 @@
         }
       }
     },
+    "node_modules/better-result": {
+      "version": "2.7.0",
+      "resolved": "https://registry.npmjs.org/better-result/-/better-result-2.7.0.tgz",
+      "integrity": "sha512-7zrmXjAK8u8Z6SOe4R65XObOR5X+Y2I/VVku3t5cPOGQ8/WsBcfFmfnIPiEl5EBMDOzPHRwbiPbMtQBKYdw7RA==",
+      "license": "MIT",
+      "dependencies": {
+        "@clack/prompts": "^0.11.0"
+      },
+      "bin": {
+        "better-result": "bin/cli.mjs"
+      }
+    },
     "node_modules/bidi-js": {
       "version": "1.0.3",
       "resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.3.tgz",
@@ -9098,6 +9309,18 @@
         "url": "https://github.com/sponsors/wooorm"
       }
     },
+    "node_modules/chart.js": {
+      "version": "4.5.1",
+      "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.5.1.tgz",
+      "integrity": "sha512-GIjfiT9dbmHRiYi6Nl2yFCq7kkwdkp1W/lp2J99rX0yo9tgJGn3lKQATztIjb5tVtevcBtIdICNWqlq5+E8/Pw==",
+      "license": "MIT",
+      "dependencies": {
+        "@kurkle/color": "^0.3.0"
+      },
+      "engines": {
+        "pnpm": ">=8"
+      }
+    },
     "node_modules/chokidar": {
       "version": "4.0.3",
       "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
@@ -9949,8 +10172,6 @@
       "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz",
       "integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==",
       "license": "Apache-2.0",
-      "optional": true,
-      "peer": true,
       "engines": {
         "node": ">=0.10"
       }
@@ -10106,9 +10327,9 @@
       "license": "MIT"
     },
     "node_modules/effect": {
-      "version": "3.18.4",
-      "resolved": "https://registry.npmjs.org/effect/-/effect-3.18.4.tgz",
-      "integrity": "sha512-b1LXQJLe9D11wfnOKAk3PKxuqYshQ0Heez+y5pnkd3jLj1yx9QhM72zZ9uUrOQyNvrs2GZZd/3maL0ZV18YuDA==",
+      "version": "3.20.0",
+      "resolved": "https://registry.npmjs.org/effect/-/effect-3.20.0.tgz",
+      "integrity": "sha512-qMLfDJscrNG8p/aw+IkT9W7fgj50Z4wG5bLBy0Txsxz8iUHjDIkOgO3SV0WZfnQbNG2VJYb0b+rDLMrhM4+Krw==",
       "license": "MIT",
       "dependencies": {
         "@standard-schema/spec": "^1.0.0",
@@ -10552,7 +10773,6 @@
       "version": "3.1.0",
       "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz",
       "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==",
-      "dev": true,
       "funding": [
         {
           "type": "github",
@@ -10899,8 +11119,6 @@
       "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz",
       "integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==",
       "license": "MIT",
-      "optional": true,
-      "peer": true,
       "dependencies": {
         "is-property": "^1.0.2"
       }
@@ -10982,6 +11200,12 @@
         "url": "https://github.com/sponsors/sindresorhus"
       }
     },
+    "node_modules/get-port-please": {
+      "version": "3.2.0",
+      "resolved": "https://registry.npmjs.org/get-port-please/-/get-port-please-3.2.0.tgz",
+      "integrity": "sha512-I9QVvBw5U/hw3RmWpYKRumUeaDgxTPd401x364rLmWBJcOQ753eov1eTgzDqRG9bqFIfDc7gfzcQEWrUri3o1A==",
+      "license": "MIT"
+    },
     "node_modules/get-proto": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
@@ -11092,6 +11316,18 @@
       "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
       "license": "ISC"
     },
+    "node_modules/grammex": {
+      "version": "3.1.12",
+      "resolved": "https://registry.npmjs.org/grammex/-/grammex-3.1.12.tgz",
+      "integrity": "sha512-6ufJOsSA7LcQehIJNCO7HIBykfM7DXQual0Ny780/DEcJIpBlHRvcqEBWGPYd7hrXL2GJ3oJI1MIhaXjWmLQOQ==",
+      "license": "MIT"
+    },
+    "node_modules/graphmatch": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/graphmatch/-/graphmatch-1.1.1.tgz",
+      "integrity": "sha512-5ykVn/EXM1hF0XCaWh05VbYvEiOL2lY1kBxZtaYsyvjp7cmWOU1XsAdfQBwClraEofXDT197lFbXOEVMHpvQOg==",
+      "license": "MIT"
+    },
     "node_modules/graphql": {
       "version": "16.12.0",
       "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.12.0.tgz",
@@ -11199,10 +11435,9 @@
       "license": "MIT"
     },
     "node_modules/hono": {
-      "version": "4.12.7",
-      "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.7.tgz",
-      "integrity": "sha512-jq9l1DM0zVIvsm3lv9Nw9nlJnMNPOcAtsbsgiUhWcFzPE99Gvo6yRTlszSLLYacMeQ6quHD6hMfId8crVHvexw==",
-      "dev": true,
+      "version": "4.12.9",
+      "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.9.tgz",
+      "integrity": "sha512-wy3T8Zm2bsEvxKZM5w21VdHDDcwVS1yUFFY6i8UobSsKfFceT7TOwhbhfKsDyx7tYQlmRM5FLpIuYvNFyjctiA==",
       "license": "MIT",
       "engines": {
         "node": ">=16.9.0"
@@ -11280,6 +11515,12 @@
         "url": "https://opencollective.com/express"
       }
     },
+    "node_modules/http-status-codes": {
+      "version": "2.3.0",
+      "resolved": "https://registry.npmjs.org/http-status-codes/-/http-status-codes-2.3.0.tgz",
+      "integrity": "sha512-RJ8XvFvpPM/Dmc5SV+dC4y5PCeOhT3x1Hq0NU3rjGeg5a/CqlhZ7uudknPwZFz4aeAXDcbAyaeP7GAo9lvngtA==",
+      "license": "MIT"
+    },
     "node_modules/https-proxy-agent": {
       "version": "7.0.6",
       "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz",
@@ -11314,7 +11555,6 @@
       "version": "0.7.2",
       "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz",
       "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==",
-      "devOptional": true,
       "license": "MIT",
       "dependencies": {
         "safer-buffer": ">= 2.1.2 < 3.0.0"
@@ -11690,9 +11930,7 @@
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz",
       "integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==",
-      "license": "MIT",
-      "optional": true,
-      "peer": true
+      "license": "MIT"
     },
     "node_modules/is-regexp": {
       "version": "3.1.0",
@@ -12013,7 +12251,6 @@
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
       "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
-      "dev": true,
       "license": "MIT"
     },
     "node_modules/json-schema-typed": {
@@ -12587,8 +12824,6 @@
       "resolved": "https://registry.npmjs.org/lru.min/-/lru.min-1.1.4.tgz",
       "integrity": "sha512-DqC6n3QQ77zdFpCMASA1a3Jlb64Hv2N2DciFGkO/4L9+q/IpIAuRlKOvCXabtRW6cQf8usbmM6BE/TOPysCdIA==",
       "license": "MIT",
-      "optional": true,
-      "peer": true,
       "engines": {
         "bun": ">=1.0.0",
         "deno": ">=1.30.0",
@@ -12638,19 +12873,6 @@
         "@jridgewell/sourcemap-codec": "^1.5.5"
       }
     },
-    "node_modules/magicast": {
-      "version": "0.3.5",
-      "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.3.5.tgz",
-      "integrity": "sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==",
-      "license": "MIT",
-      "optional": true,
-      "peer": true,
-      "dependencies": {
-        "@babel/parser": "^7.25.4",
-        "@babel/types": "^7.25.4",
-        "source-map-js": "^1.2.0"
-      }
-    },
     "node_modules/mailgun.js": {
       "version": "12.7.0",
       "resolved": "https://registry.npmjs.org/mailgun.js/-/mailgun.js-12.7.0.tgz",
@@ -13814,8 +14036,6 @@
       "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.15.3.tgz",
       "integrity": "sha512-FBrGau0IXmuqg4haEZRBfHNWB5mUARw6hNwPDXXGg0XzVJ50mr/9hb267lvpVMnhZ1FON3qNd4Xfcez1rbFwSg==",
       "license": "MIT",
-      "optional": true,
-      "peer": true,
       "dependencies": {
         "aws-ssl-profiles": "^1.1.1",
         "denque": "^2.1.0",
@@ -13836,8 +14056,6 @@
       "resolved": "https://registry.npmjs.org/named-placeholders/-/named-placeholders-1.1.6.tgz",
       "integrity": "sha512-Tz09sEL2EEuv5fFowm419c1+a/jSMiBjI9gHxVLrVdbUkkNUUfjsVYs9pVZu5oCon/kmRh9TfLEObFtkVxmY0w==",
       "license": "MIT",
-      "optional": true,
-      "peer": true,
       "dependencies": {
         "lru.min": "^1.1.0"
       },
@@ -14235,9 +14453,9 @@
       }
     },
     "node_modules/nypm/node_modules/citty": {
-      "version": "0.2.0",
-      "resolved": "https://registry.npmjs.org/citty/-/citty-0.2.0.tgz",
-      "integrity": "sha512-8csy5IBFI2ex2hTVpaHN2j+LNE199AgiI7y4dMintrr8i0lQiFn+0AWMZrWdHKIgMOer65f8IThysYhoReqjWA==",
+      "version": "0.2.1",
+      "resolved": "https://registry.npmjs.org/citty/-/citty-0.2.1.tgz",
+      "integrity": "sha512-kEV95lFBhQgtogAPlQfJJ0WGVSokvLr/UEoFPiKKOXF7pl98HfUVUD0ejsuTCld/9xH9vogSywZ5KqHzXrZpqg==",
       "license": "MIT"
     },
     "node_modules/object-assign": {
@@ -14679,6 +14897,104 @@
       "integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==",
       "license": "MIT"
     },
+    "node_modules/pg": {
+      "version": "8.20.0",
+      "resolved": "https://registry.npmjs.org/pg/-/pg-8.20.0.tgz",
+      "integrity": "sha512-ldhMxz2r8fl/6QkXnBD3CR9/xg694oT6DZQ2s6c/RI28OjtSOpxnPrUCGOBJ46RCUxcWdx3p6kw/xnDHjKvaRA==",
+      "license": "MIT",
+      "dependencies": {
+        "pg-connection-string": "^2.12.0",
+        "pg-pool": "^3.13.0",
+        "pg-protocol": "^1.13.0",
+        "pg-types": "2.2.0",
+        "pgpass": "1.0.5"
+      },
+      "engines": {
+        "node": ">= 16.0.0"
+      },
+      "optionalDependencies": {
+        "pg-cloudflare": "^1.3.0"
+      },
+      "peerDependencies": {
+        "pg-native": ">=3.0.1"
+      },
+      "peerDependenciesMeta": {
+        "pg-native": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/pg-cloudflare": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.3.0.tgz",
+      "integrity": "sha512-6lswVVSztmHiRtD6I8hw4qP/nDm1EJbKMRhf3HCYaqud7frGysPv7FYJ5noZQdhQtN2xJnimfMtvQq21pdbzyQ==",
+      "license": "MIT",
+      "optional": true
+    },
+    "node_modules/pg-connection-string": {
+      "version": "2.12.0",
+      "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.12.0.tgz",
+      "integrity": "sha512-U7qg+bpswf3Cs5xLzRqbXbQl85ng0mfSV/J0nnA31MCLgvEaAo7CIhmeyrmJpOr7o+zm0rXK+hNnT5l9RHkCkQ==",
+      "license": "MIT"
+    },
+    "node_modules/pg-int8": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz",
+      "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==",
+      "license": "ISC",
+      "engines": {
+        "node": ">=4.0.0"
+      }
+    },
+    "node_modules/pg-pool": {
+      "version": "3.13.0",
+      "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.13.0.tgz",
+      "integrity": "sha512-gB+R+Xud1gLFuRD/QgOIgGOBE2KCQPaPwkzBBGC9oG69pHTkhQeIuejVIk3/cnDyX39av2AxomQiyPT13WKHQA==",
+      "license": "MIT",
+      "peerDependencies": {
+        "pg": ">=8.0"
+      }
+    },
+    "node_modules/pg-protocol": {
+      "version": "1.13.0",
+      "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.13.0.tgz",
+      "integrity": "sha512-zzdvXfS6v89r6v7OcFCHfHlyG/wvry1ALxZo4LqgUoy7W9xhBDMaqOuMiF3qEV45VqsN6rdlcehHrfDtlCPc8w==",
+      "license": "MIT"
+    },
+    "node_modules/pg-types": {
+      "version": "2.2.0",
+      "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz",
+      "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==",
+      "license": "MIT",
+      "dependencies": {
+        "pg-int8": "1.0.1",
+        "postgres-array": "~2.0.0",
+        "postgres-bytea": "~1.0.0",
+        "postgres-date": "~1.0.4",
+        "postgres-interval": "^1.1.0"
+      },
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/pg-types/node_modules/postgres-array": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz",
+      "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/pgpass": {
+      "version": "1.0.5",
+      "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz",
+      "integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==",
+      "license": "MIT",
+      "dependencies": {
+        "split2": "^4.1.0"
+      }
+    },
     "node_modules/picocolors": {
       "version": "1.1.1",
       "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
@@ -14818,6 +15134,58 @@
       "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
       "license": "MIT"
     },
+    "node_modules/postgres": {
+      "version": "3.4.7",
+      "resolved": "https://registry.npmjs.org/postgres/-/postgres-3.4.7.tgz",
+      "integrity": "sha512-Jtc2612XINuBjIl/QTWsV5UvE8UHuNblcO3vVADSrKsrc6RqGX6lOW1cEo3CM2v0XG4Nat8nI+YM7/f26VxXLw==",
+      "license": "Unlicense",
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "type": "individual",
+        "url": "https://github.com/sponsors/porsager"
+      }
+    },
+    "node_modules/postgres-array": {
+      "version": "3.0.4",
+      "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-3.0.4.tgz",
+      "integrity": "sha512-nAUSGfSDGOaOAEGwqsRY27GPOea7CNipJPOA7lPbdEpx5Kg3qzdP0AaWC5MlhTWV9s4hFX39nomVZ+C4tnGOJQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/postgres-bytea": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.1.tgz",
+      "integrity": "sha512-5+5HqXnsZPE65IJZSMkZtURARZelel2oXUEO8rH83VS/hxH5vv1uHquPg5wZs8yMAfdv971IU+kcPUczi7NVBQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/postgres-date": {
+      "version": "1.0.7",
+      "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz",
+      "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/postgres-interval": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz",
+      "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==",
+      "license": "MIT",
+      "dependencies": {
+        "xtend": "^4.0.0"
+      },
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
     "node_modules/posthog-js": {
       "version": "1.360.2",
       "resolved": "https://registry.npmjs.org/posthog-js/-/posthog-js-1.360.2.tgz",
@@ -14937,25 +15305,33 @@
       }
     },
     "node_modules/prisma": {
-      "version": "6.19.2",
-      "resolved": "https://registry.npmjs.org/prisma/-/prisma-6.19.2.tgz",
-      "integrity": "sha512-XTKeKxtQElcq3U9/jHyxSPgiRgeYDKxWTPOf6NkXA0dNj5j40MfEsZkMbyNpwDWCUv7YBFUl7I2VK/6ALbmhEg==",
+      "version": "7.6.0",
+      "resolved": "https://registry.npmjs.org/prisma/-/prisma-7.6.0.tgz",
+      "integrity": "sha512-OKJIPT81K3+F+AayIkY/Y3mkF2NWoFh7lZApaaqPYy7EHILKdO0VsmGkP+hDKYTySHsFSyLWXm/JgcR1B8fY1Q==",
       "hasInstallScript": true,
       "license": "Apache-2.0",
       "dependencies": {
-        "@prisma/config": "6.19.2",
-        "@prisma/engines": "6.19.2"
+        "@prisma/config": "7.6.0",
+        "@prisma/dev": "0.24.3",
+        "@prisma/engines": "7.6.0",
+        "@prisma/studio-core": "0.27.3",
+        "mysql2": "3.15.3",
+        "postgres": "3.4.7"
       },
       "bin": {
         "prisma": "build/index.js"
       },
       "engines": {
-        "node": ">=18.18"
+        "node": "^20.19 || ^22.12 || >=24.0"
       },
       "peerDependencies": {
-        "typescript": ">=5.1.0"
+        "better-sqlite3": ">=9.0.0",
+        "typescript": ">=5.4.0"
       },
       "peerDependenciesMeta": {
+        "better-sqlite3": {
+          "optional": true
+        },
         "typescript": {
           "optional": true
         }
@@ -15011,6 +15387,23 @@
         "react-is": "^16.13.1"
       }
     },
+    "node_modules/proper-lockfile": {
+      "version": "4.1.2",
+      "resolved": "https://registry.npmjs.org/proper-lockfile/-/proper-lockfile-4.1.2.tgz",
+      "integrity": "sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==",
+      "license": "MIT",
+      "dependencies": {
+        "graceful-fs": "^4.2.4",
+        "retry": "^0.12.0",
+        "signal-exit": "^3.0.2"
+      }
+    },
+    "node_modules/proper-lockfile/node_modules/signal-exit": {
+      "version": "3.0.7",
+      "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
+      "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
+      "license": "ISC"
+    },
     "node_modules/property-information": {
       "version": "7.1.0",
       "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.1.0.tgz",
@@ -16042,6 +16435,15 @@
         "url": "https://opencollective.com/unified"
       }
     },
+    "node_modules/remeda": {
+      "version": "2.33.4",
+      "resolved": "https://registry.npmjs.org/remeda/-/remeda-2.33.4.tgz",
+      "integrity": "sha512-ygHswjlc/opg2VrtiYvUOPLjxjtdKvjGz1/plDhkG66hjNjFr1xmfrs2ClNFo/E6TyUFiwYNh53bKV26oBoMGQ==",
+      "license": "MIT",
+      "funding": {
+        "url": "https://github.com/sponsors/remeda"
+      }
+    },
     "node_modules/require-directory": {
       "version": "2.1.1",
       "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
@@ -16126,6 +16528,15 @@
       "integrity": "sha512-gSfoiOEA0VPE6Tukkrr7I0RBdE0s7H1eFCDBk05l1KIQT1UIKNc5JZy6jdyW6eYH3aR3g5b3PuL77rq0hvwtAw==",
       "license": "MIT"
     },
+    "node_modules/retry": {
+      "version": "0.12.0",
+      "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz",
+      "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 4"
+      }
+    },
     "node_modules/rettime": {
       "version": "0.10.1",
       "resolved": "https://registry.npmjs.org/rettime/-/rettime-0.10.1.tgz",
@@ -16279,7 +16690,6 @@
       "version": "2.1.2",
       "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
       "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
-      "devOptional": true,
       "license": "MIT"
     },
     "node_modules/saxes": {
@@ -16340,9 +16750,7 @@
     "node_modules/seq-queue": {
       "version": "0.0.5",
       "resolved": "https://registry.npmjs.org/seq-queue/-/seq-queue-0.0.5.tgz",
-      "integrity": "sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q==",
-      "optional": true,
-      "peer": true
+      "integrity": "sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q=="
     },
     "node_modules/serve-static": {
       "version": "2.2.1",
@@ -16695,7 +17103,6 @@
       "version": "1.0.5",
       "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz",
       "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==",
-      "dev": true,
       "license": "MIT"
     },
     "node_modules/sonner": {
@@ -16746,13 +17153,20 @@
         "url": "https://github.com/sponsors/wooorm"
       }
     },
+    "node_modules/split2": {
+      "version": "4.2.0",
+      "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz",
+      "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==",
+      "license": "ISC",
+      "engines": {
+        "node": ">= 10.x"
+      }
+    },
     "node_modules/sqlstring": {
       "version": "2.3.3",
       "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.3.tgz",
       "integrity": "sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg==",
       "license": "MIT",
-      "optional": true,
-      "peer": true,
       "engines": {
         "node": ">= 0.6"
       }
@@ -17752,6 +18166,20 @@
         "uuid": "dist/bin/uuid"
       }
     },
+    "node_modules/valibot": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/valibot/-/valibot-1.2.0.tgz",
+      "integrity": "sha512-mm1rxUsmOxzrwnX5arGS+U4T25RdvpPjPN4yR0u9pUBov9+zGVtO84tif1eY4r6zWxVxu3KzIyknJy3rxfRZZg==",
+      "license": "MIT",
+      "peerDependencies": {
+        "typescript": ">=5"
+      },
+      "peerDependenciesMeta": {
+        "typescript": {
+          "optional": true
+        }
+      }
+    },
     "node_modules/validate-npm-package-name": {
       "version": "7.0.2",
       "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-7.0.2.tgz",
@@ -18593,6 +19021,15 @@
       "dev": true,
       "license": "MIT"
     },
+    "node_modules/xtend": {
+      "version": "4.0.2",
+      "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
+      "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.4"
+      }
+    },
     "node_modules/y18n": {
       "version": "5.0.8",
       "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
@@ -18715,6 +19152,16 @@
       "integrity": "sha512-0LPOt3AxKqMdFBZA3HBAt/t/8vIKq7VaQYbuA8WxCgung+p9TVyKRYdpvCb80HcdTN2NkbIKbhNwKUfm3tQywQ==",
       "license": "MIT"
     },
+    "node_modules/zeptomatch": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/zeptomatch/-/zeptomatch-2.1.0.tgz",
+      "integrity": "sha512-KiGErG2J0G82LSpniV0CtIzjlJ10E04j02VOudJsPyPwNZgGnRKQy7I1R7GMyg/QswnE4l7ohSGrQbQbjXPPDA==",
+      "license": "MIT",
+      "dependencies": {
+        "grammex": "^3.1.11",
+        "graphmatch": "^1.1.0"
+      }
+    },
     "node_modules/zip-stream": {
       "version": "6.0.1",
       "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-6.0.1.tgz",

+ 5 - 4
package.json

@@ -23,7 +23,8 @@
     "@dnd-kit/sortable": "^10.0.0",
     "@dnd-kit/utilities": "^3.2.2",
     "@hookform/resolvers": "^5.2.2",
-    "@prisma/client": "^6.19.2",
+    "@prisma/adapter-pg": "^7.6.0",
+    "@prisma/client": "^7.6.0",
     "@react-pdf/renderer": "^4.3.2",
     "@sendgrid/mail": "^8.1.6",
     "@tanstack/react-query": "^5.90.21",
@@ -41,6 +42,7 @@
     "cmdk": "^1.1.1",
     "cron": "^4.4.0",
     "date-fns": "^4.1.0",
+    "dotenv": "^17.2.4",
     "form-data": "^4.0.5",
     "html5-qrcode": "^2.3.8",
     "jszip": "^3.10.1",
@@ -55,7 +57,7 @@
     "pdf-lib": "^1.17.1",
     "posthog-js": "^1.360.2",
     "postmark": "^4.0.5",
-    "prisma": "^6.19.2",
+    "prisma": "^7.6.0",
     "qrcode": "^1.5.4",
     "qrcode.react": "^4.2.0",
     "radix-ui": "^1.4.3",
@@ -90,7 +92,6 @@
     "@types/react-dom": "^19",
     "@types/ws": "^8.18.1",
     "@vitest/coverage-v8": "^4.1.2",
-    "dotenv": "^17.2.4",
     "jsdom": "^29.0.0",
     "shadcn": "^4.1.1",
     "tailwindcss": "^4",
@@ -102,4 +103,4 @@
     "fast-xml-parser": ">=5.3.6",
     "qs": ">=6.14.2"
   }
-}
+}

+ 12 - 0
prisma.config.ts

@@ -0,0 +1,12 @@
+import "dotenv/config";
+import { defineConfig } from "prisma/config";
+
+export default defineConfig({
+  schema: "prisma/schema.prisma",
+  migrations: {
+    path: "prisma/migrations",
+  },
+  datasource: {
+    url: process.env.DATABASE_URL ?? "",
+  },
+});

+ 2 - 2
prisma/schema.prisma

@@ -1,10 +1,10 @@
 generator client {
-  provider = "prisma-client-js"
+  provider = "prisma-client"
+  output   = "../src/generated/prisma"
 }
 
 datasource db {
   provider = "postgresql"
-  url      = env("DATABASE_URL")
 }
 
 model User {

+ 1 - 1
src/app/api/protected/backup/import/route.ts

@@ -1,7 +1,7 @@
 import { NextRequest, NextResponse } from "next/server";
 import { getAuthContext } from "@/lib/get-auth-context";
 import { db } from "@/lib/db";
-import { Prisma } from "@prisma/client";
+import { Prisma } from "@/generated/prisma/client";
 import JSZip from "jszip";
 import { mkdir, rm, writeFile } from "fs/promises";
 import path from "path";

+ 1 - 1
src/features/ai/tools/workshop-tools.ts

@@ -1,7 +1,7 @@
 import "server-only";
 import { db } from "@/lib/db";
 import type OpenAI from "openai";
-import { Prisma } from "@prisma/client";
+import { Prisma } from "@/generated/prisma/client";
 
 const MAX_ROWS = 100;
 

+ 1 - 1
src/features/billing/Actions/billingActions.ts

@@ -3,7 +3,7 @@
 import { db } from "@/lib/db";
 import { withAuth } from "@/lib/with-auth";
 import { PermissionAction, PermissionSubject } from "@/lib/permissions";
-import { Prisma } from "@prisma/client";
+import { Prisma } from "@/generated/prisma/client";
 
 interface BillingSummary {
   totalRevenue: number;

+ 1 - 1
src/features/inventory/Actions/inventoryActions.ts

@@ -4,7 +4,7 @@ import { db } from "@/lib/db";
 import { withAuth } from "@/lib/with-auth";
 import { createInventoryPartSchema, updateInventoryPartSchema, adjustStockSchema } from "../Schema/inventorySchema";
 import { revalidatePath } from "next/cache";
-import { Prisma } from "@prisma/client";
+import { Prisma } from "@/generated/prisma/client";
 import { z } from "zod";
 import { unlink } from "fs/promises";
 import { resolveUploadPath } from "@/lib/resolve-upload-path";

+ 1 - 1
src/features/labor-presets/Actions/laborPresetActions.ts

@@ -4,7 +4,7 @@ import { db } from "@/lib/db";
 import { withAuth } from "@/lib/with-auth";
 import { createLaborPresetSchema, updateLaborPresetSchema } from "../Schema/laborPresetSchema";
 import { revalidatePath } from "next/cache";
-import { Prisma } from "@prisma/client";
+import { Prisma } from "@/generated/prisma/client";
 import { PermissionAction, PermissionSubject } from "@/lib/permissions";
 
 export async function getLaborPresetsPaginated(params: {

+ 1 - 1
src/features/search/Actions/searchActions.ts

@@ -1,7 +1,7 @@
 "use server";
 
 import { db } from "@/lib/db";
-import { Prisma } from "@prisma/client";
+import { Prisma } from "@/generated/prisma/client";
 import { withAuth } from "@/lib/with-auth";
 import { PermissionAction, PermissionSubject } from "@/lib/permissions";
 

+ 14 - 5
src/lib/db.ts

@@ -1,14 +1,23 @@
-import { PrismaClient } from "@prisma/client";
+import { PrismaClient } from "@/generated/prisma/client";
+import { PrismaPg } from "@prisma/adapter-pg";
 
 const globalForPrisma = globalThis as unknown as {
   prisma: PrismaClient | undefined;
 };
 
-export const db =
-  globalForPrisma.prisma ??
-  new PrismaClient({
-    datasourceUrl: process.env.DATABASE_URL,
+function createPrismaClient() {
+  const adapter = new PrismaPg({
+    connectionString: process.env.DATABASE_URL,
+    max: 10,
+    connectionTimeoutMillis: 5000,
+    idleTimeoutMillis: 30000,
+  });
+  return new PrismaClient({
+    adapter,
     log: process.env.NODE_ENV === "development" ? ["warn", "error"] : ["error"],
   });
+}
+
+export const db = globalForPrisma.prisma ?? createPrismaClient();
 
 globalForPrisma.prisma = db;