diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index 18399cb..0000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "folder-color.pathColors": [] -} diff --git a/claim-db-worker/app/api/auth/callback/route.ts b/claim-db-worker/app/api/auth/callback/route.ts index 2c2b544..b8ac49f 100644 --- a/claim-db-worker/app/api/auth/callback/route.ts +++ b/claim-db-worker/app/api/auth/callback/route.ts @@ -114,9 +114,10 @@ export async function GET(request: NextRequest) { ); } - // Validate project exists + // Validate project exists and get project data + let projectData; try { - await validateProject(projectID); + projectData = await validateProject(projectID); } catch (error) { const errorMessage = error instanceof Error ? error.message : "Unknown error"; @@ -141,16 +142,57 @@ export async function GET(request: NextRequest) { projectID, tokenData.access_token ); - if (transferResult.success) { + // Fetch project details with user's token to get workspace ID + const projectDetailsRes = await fetch( + `https://api.prisma.io/v1/projects/${projectID}`, + { + headers: { + Authorization: `Bearer ${tokenData.access_token}`, + "Content-Type": "application/json", + }, + } + ); + const projectDetails = (await projectDetailsRes.json()) as { + data?: { workspace?: { id?: string } }; + }; + const workspaceId = (projectDetails.data?.workspace?.id ?? "").replace( + /^wksp_/, + "" + ); + + // Fetch databases to get database ID + const databasesRes = await fetch( + `https://api.prisma.io/v1/projects/${projectID}/databases`, + { + headers: { + Authorization: `Bearer ${tokenData.access_token}`, + "Content-Type": "application/json", + }, + } + ); + const databases = (await databasesRes.json()) as { + data?: Array<{ id?: string }>; + }; + const databaseId = (databases.data?.[0]?.id ?? "").replace(/^db_/, ""); + await sendServerAnalyticsEvent( "create_db:claim_successful", { "project-id": projectID, + "workspace-id": workspaceId, + "database-id": databaseId, }, request ); - return redirectToSuccess(request, projectID); + + const cleanProjectId = projectID.replace(/^proj_/, ""); + return redirectToSuccess( + request, + cleanProjectId, + workspaceId, + databaseId + ); } else { await sendServerAnalyticsEvent( "create_db:claim_failed", diff --git a/claim-db-worker/app/success/page.tsx b/claim-db-worker/app/success/page.tsx index 092d096..57ce09c 100644 --- a/claim-db-worker/app/success/page.tsx +++ b/claim-db-worker/app/success/page.tsx @@ -8,6 +8,13 @@ import { Suspense } from "react"; function SuccessContent() { const searchParams = useSearchParams(); const projectID = searchParams.get("projectID"); + const workspaceId = searchParams.get("workspaceId"); + const databaseId = searchParams.get("databaseId"); + + const consoleUrl = + workspaceId && projectID && databaseId + ? `https://console.prisma.io/${workspaceId}/${projectID}/${databaseId}` + : "https://console.prisma.io/"; return (
@@ -23,7 +30,7 @@ function SuccessContent() {

Import and use Prisma Client in your application, + code: `import { PrismaClient } from "./generated/prisma/client.js"; +import { PrismaPg } from "@prisma/adapter-pg"; -const buildSteps: Record = { - prisma: [ - { - title: "Install Prisma", - content: null, - code: "npm install prisma @prisma/client", - }, - { - title: "Initialize Prisma", - content: null, - code: "npx prisma init", - }, - { - title: "Set connection string in .env", - content: null, - code: 'DATABASE_URL=""', - }, - { - title: "Pull the database schema", - content: null, - code: "npx prisma db pull", - }, - { - title: "Generate Prisma Client", - content: null, - code: "npx prisma generate", - }, - { - title: "Start querying", - content: Import and use Prisma Client in your application, - code: `import { PrismaClient } from "@prisma/client"; +const adapter = new PrismaPg({ + connectionString: process.env.DATABASE_URL, +}); -const prisma = new PrismaClient(); -const users = await prisma.user.findMany(); -console.log(users);`, - }, - ], - direct: [ - { - title: "Install node-postgres", - content: null, - code: "npm install pg", - }, - { - title: "Set connection string in .env", - content: null, - code: 'DATABASE_URL=""', - }, - { - title: "Set up connection", - content: null, - code: `import { Pool } from "pg"; +const prisma = new PrismaClient({ + adapter, +}); -const pool = new Pool({ - connectionString: process.env.DATABASE_URL -});`, - }, - { - title: "Query your database", - content: null, - code: `const { rows } = await pool.query('SELECT * FROM "User"'); - -console.log(rows);`, - }, - ], -}; +export default prisma;`, + }, +]; const StepItem = ({ number, @@ -138,19 +114,18 @@ const StepItem = ({ export default function ConnectPage() { const { dbInfo } = useDatabase(); - const [connectionType, setConnectionType] = - useState("prisma"); - const [copied, setCopied] = useState(false); + const [copiedDirect, setCopiedDirect] = useState(false); + const [copiedAccel, setCopiedAccel] = useState(false); const [showPassword, setShowPassword] = useState(false); - const connectionString = - connectionType === "prisma" - ? dbInfo.connectionString - : dbInfo.directConnectionString; + const connectionString = dbInfo.directConnectionString; - const handleCopyConnectionString = async () => { + const handleCopyConnectionString = async ( + copyString: string, + setCopied: (copied: boolean) => void + ) => { try { - await navigator.clipboard.writeText(connectionString || ""); + await navigator.clipboard.writeText(copyString); setCopied(true); customToast("success", "Connection string copied to clipboard"); setTimeout(() => setCopied(false), 2000); @@ -162,7 +137,7 @@ export default function ConnectPage() { return (
{/* Connection type toggle */} -
+ {/*
{(["prisma", "direct"] as const).map((type) => (
+
*/} {/* Connection string input */}
@@ -209,32 +184,56 @@ export default function ConnectPage() { +

- {connectionType === "prisma" - ? "Connect with Prisma ORM" - : "Connect with node-postgres"} + Connect with Prisma ORM

- {buildSteps[connectionType].map((step, index) => ( + {buildSteps.map((step, index) => ( { +): Promise<{ + success: boolean; + error?: string; + status?: number; + transferResponse: any; +}> { const env = getEnv(); const requestBody = JSON.stringify({ @@ -24,13 +29,26 @@ export async function transferProject( ); if (transferResponse.ok) { - return { success: true }; + const responseText = await transferResponse.text(); + let responseData = null; + + if (responseText) { + try { + responseData = JSON.parse(responseText); + } catch (e) { + console.log("Transfer response (not JSON):", responseText); + responseData = { rawResponse: responseText }; + } + } + + return { success: true, transferResponse: responseData }; } else { const responseText = await transferResponse.text(); return { success: false, error: responseText, status: transferResponse.status, + transferResponse: null, }; } } diff --git a/claim-db-worker/lib/response-utils.ts b/claim-db-worker/lib/response-utils.ts index 2afd953..1c62564 100644 --- a/claim-db-worker/lib/response-utils.ts +++ b/claim-db-worker/lib/response-utils.ts @@ -26,10 +26,14 @@ export function redirectToError( export function redirectToSuccess( request: NextRequest, - projectID: string + projectID: string, + workspaceId: string, + databaseId: string ): Response { const params = new URLSearchParams({ projectID: projectID, + workspaceId: workspaceId, + databaseId: databaseId, }); const baseUrl = getBaseUrl(request); diff --git a/claim-db-worker/package.json b/claim-db-worker/package.json index 321d57b..e7a42aa 100644 --- a/claim-db-worker/package.json +++ b/claim-db-worker/package.json @@ -26,9 +26,9 @@ "next": "15.4.6", "pg": "^8.16.3", "posthog-js": "^1.282.0", - "prisma": "^6.14.0", - "react": "19.1.0", - "react-dom": "19.1.0", + "prisma": "^7.1.0", + "react": "19.1.2", + "react-dom": "19.1.2", "react-hot-toast": "^2.6.0" }, "devDependencies": { diff --git a/create-db-worker/src/index.ts b/create-db-worker/src/index.ts index eb7c60b..9f14415 100644 --- a/create-db-worker/src/index.ts +++ b/create-db-worker/src/index.ts @@ -142,7 +142,7 @@ export default { if (!region || !name) { return new Response('Missing region or name in request body', { status: 400 }); } - console.log('userAgent:', userAgent); + const prismaResponse = await fetch('https://api.prisma.io/v1/projects', { method: 'POST', headers: { diff --git a/create-db/__tests__/create.test.ts b/create-db/__tests__/create.test.ts index 3aa3a3e..33116a6 100644 --- a/create-db/__tests__/create.test.ts +++ b/create-db/__tests__/create.test.ts @@ -8,7 +8,7 @@ const CLI_PATH = path.resolve(__dirname, "../dist/cli.mjs"); const runCli = async ( args: string[] = [], - options: { env?: Record;[key: string]: unknown } = {} + options: { env?: Record; [key: string]: unknown } = {} ) => { const result = await execa("node", [CLI_PATH, ...args], { ...options, diff --git a/create-db/package.json b/create-db/package.json index 2136de8..0a71e02 100644 --- a/create-db/package.json +++ b/create-db/package.json @@ -70,4 +70,4 @@ "typescript": "^5.9.3", "vitest": "^4.0.15" } -} \ No newline at end of file +} diff --git a/create-db/src/analytics.ts b/create-db/src/analytics.ts new file mode 100644 index 0000000..8e191a1 --- /dev/null +++ b/create-db/src/analytics.ts @@ -0,0 +1,39 @@ +const pendingAnalytics: Promise[] = []; + +export async function sendAnalytics( + eventName: string, + properties: Record, + cliRunId: string, + workerUrl: string +): Promise { + const controller = new AbortController(); + const timer = setTimeout(() => controller.abort(), 5000); + + const promise = (async () => { + try { + await fetch(`${workerUrl}/analytics`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + eventName, + properties: { distinct_id: cliRunId, ...properties }, + }), + signal: controller.signal, + }); + } catch { + // Analytics failures should not block CLI + } finally { + clearTimeout(timer); + } + })(); + + pendingAnalytics.push(promise); +} + +export async function flushAnalytics(maxWaitMs = 500): Promise { + if (pendingAnalytics.length === 0) return; + await Promise.race([ + Promise.all(pendingAnalytics), + new Promise((resolve) => setTimeout(resolve, maxWaitMs)), + ]); +} diff --git a/create-db/src/cli.ts b/create-db/src/cli.ts index 45b13e9..8c21feb 100644 --- a/create-db/src/cli.ts +++ b/create-db/src/cli.ts @@ -1,4 +1,3 @@ import { createDbCli } from "./index.js"; createDbCli().run(); - diff --git a/create-db/src/database.ts b/create-db/src/database.ts new file mode 100644 index 0000000..8c21252 --- /dev/null +++ b/create-db/src/database.ts @@ -0,0 +1,135 @@ +import { randomUUID } from "crypto"; +import type { CreateDatabaseResult, ApiResponse } from "./types.js"; +import { sendAnalytics } from "./analytics.js"; + +export function getCommandName(): string { + const executable = process.argv[1] || "create-db"; + if (executable.includes("create-pg")) return "create-pg"; + if (executable.includes("create-postgres")) return "create-postgres"; + return "create-db"; +} + +export async function createDatabaseCore( + region: string, + createDbWorkerUrl: string, + claimDbWorkerUrl: string, + userAgent?: string, + cliRunId?: string +): Promise { + const name = new Date().toISOString(); + const runId = cliRunId ?? randomUUID(); + + const resp = await fetch(`${createDbWorkerUrl}/create`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + region, + name, + utm_source: getCommandName(), + userAgent, + }), + }); + + if (resp.status === 429) { + void sendAnalytics( + "create_db:database_creation_failed", + { region, "error-type": "rate_limit", "status-code": 429 }, + runId, + createDbWorkerUrl + ); + return { + success: false, + error: "rate_limit_exceeded", + message: + "We're experiencing a high volume of requests. Please try again later.", + status: 429, + }; + } + + let result: ApiResponse; + let raw = ""; + try { + raw = await resp.text(); + result = JSON.parse(raw) as ApiResponse; + } catch { + void sendAnalytics( + "create_db:database_creation_failed", + { region, "error-type": "invalid_json", "status-code": resp.status }, + runId, + createDbWorkerUrl + ); + return { + success: false, + error: "invalid_json", + message: "Unexpected response from create service.", + raw, + status: resp.status, + }; + } + + if (result.error) { + void sendAnalytics( + "create_db:database_creation_failed", + { + region, + "error-type": "api_error", + "error-message": result.error.message, + }, + runId, + createDbWorkerUrl + ); + return { + success: false, + error: "api_error", + message: result.error.message || "Unknown error", + details: result.error, + status: result.error.status ?? resp.status, + }; + } + + const database = result.data?.database ?? result.databases?.[0]; + const projectId = result.data?.id ?? result.id ?? ""; + + const apiKeys = database?.apiKeys; + const directConnDetails = result.data + ? apiKeys?.[0]?.directConnection + : result.databases?.[0]?.apiKeys?.[0]?.ppgDirectConnection; + + const directUser = directConnDetails?.user + ? encodeURIComponent(String(directConnDetails.user)) + : ""; + const directPass = directConnDetails?.pass + ? encodeURIComponent(String(directConnDetails.pass)) + : ""; + const directHost = directConnDetails?.host; + const directPort = directConnDetails?.port + ? `:${directConnDetails.port}` + : ""; + const directDbName = directConnDetails?.database || "postgres"; + + const connectionString = + directConnDetails && directHost + ? `postgresql://${directUser}:${directPass}@${directHost}${directPort}/${directDbName}?sslmode=require` + : null; + + const claimUrl = `${claimDbWorkerUrl}/claim?projectID=${projectId}&utm_source=${userAgent || getCommandName()}&utm_medium=cli`; + const expiryDate = new Date(Date.now() + 24 * 60 * 60 * 1000); + + void sendAnalytics( + "create_db:database_created", + { region, utm_source: getCommandName() }, + runId, + createDbWorkerUrl + ); + + return { + success: true, + connectionString, + claimUrl, + deletionDate: expiryDate.toISOString(), + region: database?.region?.id || region, + name: database?.name ?? name, + projectId, + userAgent, + }; +} diff --git a/create-db/src/env-utils.ts b/create-db/src/env-utils.ts new file mode 100644 index 0000000..4a1df83 --- /dev/null +++ b/create-db/src/env-utils.ts @@ -0,0 +1,23 @@ +import fs from "fs"; +import path from "path"; + +export function readUserEnvFile(): Record { + const envPath = path.join(process.cwd(), ".env"); + if (!fs.existsSync(envPath)) return {}; + + const envContent = fs.readFileSync(envPath, "utf8"); + const envVars: Record = {}; + + for (const line of envContent.split("\n")) { + const trimmed = line.trim(); + if (trimmed && !trimmed.startsWith("#")) { + const [key, ...valueParts] = trimmed.split("="); + if (key && valueParts.length > 0) { + const value = valueParts.join("=").replace(/^["']|["']$/g, ""); + envVars[key.trim()] = value.trim(); + } + } + } + + return envVars; +} diff --git a/create-db/src/geolocation.ts b/create-db/src/geolocation.ts new file mode 100644 index 0000000..f358d4d --- /dev/null +++ b/create-db/src/geolocation.ts @@ -0,0 +1,173 @@ +import type { + UserLocation, + RegionId, + RegionCoordinates, + GeoLocationResponse, +} from "./types.js"; + +// Test locations for geolocation testing +// Set TEST_LOCATION to one of these to simulate being in that location +// Set to null to use real IP-based geolocation +const TEST_LOCATIONS = { + singapore: { latitude: 1.3521, longitude: 103.8198 }, // ap-southeast-1 + tokyo: { latitude: 35.6762, longitude: 139.6503 }, // ap-northeast-1 + frankfurt: { latitude: 50.1109, longitude: 8.6821 }, // eu-central-1 + paris: { latitude: 48.8566, longitude: 2.3522 }, // eu-west-3 + virginia: { latitude: 38.9072, longitude: -77.0369 }, // us-east-1 + california: { latitude: 37.7749, longitude: -122.4194 }, // us-west-1 +}; + +// Set this to simulate a location (e.g., TEST_LOCATIONS.tokyo) +// or null for real geolocation +const TEST_LOCATION: { latitude: number; longitude: number } | null = null; + +export const REGION_COORDINATES: Record = { + "ap-southeast-1": { lat: 1.3521, lng: 103.8198 }, + "ap-northeast-1": { lat: 35.6762, lng: 139.6503 }, + "eu-central-1": { lat: 50.1109, lng: 8.6821 }, + "eu-west-3": { lat: 48.8566, lng: 2.3522 }, + "us-east-1": { lat: 38.9072, lng: -77.0369 }, + "us-west-1": { lat: 37.7749, lng: -122.4194 }, +}; + +/** + * Calculate the great-circle distance between two points on Earth using the Haversine formula. + * @param lat1 Latitude of first point in degrees + * @param lng1 Longitude of first point in degrees + * @param lat2 Latitude of second point in degrees + * @param lng2 Longitude of second point in degrees + * @returns Distance in kilometers + */ +export function calculateHaversineDistance( + lat1: number, + lng1: number, + lat2: number, + lng2: number +): number { + const EARTH_RADIUS_KM = 6371; + const toRadians = (degrees: number) => (degrees * Math.PI) / 180; + + const lat1Rad = toRadians(lat1); + const lat2Rad = toRadians(lat2); + const deltaLatRad = toRadians(lat2 - lat1); + const deltaLngRad = toRadians(lng2 - lng1); + + const a = + Math.sin(deltaLatRad / 2) ** 2 + + Math.cos(lat1Rad) * Math.cos(lat2Rad) * Math.sin(deltaLngRad / 2) ** 2; + const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); + + return EARTH_RADIUS_KM * c; +} + +/** + * Detect user's location via IP geolocation API or test location override. + * Returns null if detection fails or times out. + */ +export async function detectUserLocation(): Promise { + // FOR TESTING: Return test location if configured + if (TEST_LOCATION !== null) { + return { + country: "TEST", + continent: "TEST", + city: "Test City", + region: "Test Region", + latitude: TEST_LOCATION.latitude, + longitude: TEST_LOCATION.longitude, + }; + } + + // Real geolocation via IP API + const controller = new AbortController(); + const timeout = setTimeout(() => controller.abort(), 3000); + + try { + const response = await fetch("https://ipapi.co/json/", { + method: "GET", + headers: { "User-Agent": "create-db-cli/1.0" }, + signal: controller.signal, + }); + + if (!response.ok) return null; + + const data = (await response.json()) as GeoLocationResponse; + + // Validate that we have valid coordinates + if ( + typeof data.latitude !== "number" || + typeof data.longitude !== "number" || + !Number.isFinite(data.latitude) || + !Number.isFinite(data.longitude) + ) { + return null; + } + + return { + country: data.country_code, + continent: data.continent_code, + city: data.city, + region: data.region, + latitude: data.latitude, + longitude: data.longitude, + }; + } catch { + // Return null on any error (timeout, network, etc.) + return null; + } finally { + clearTimeout(timeout); + } +} + +/** + * Find the closest AWS region to a given location using Haversine distance. + * Returns null if the location is invalid or missing coordinates. + */ +export function getRegionClosestToLocation( + userLocation: { + latitude?: number | string; + longitude?: number | string; + } | null +): RegionId | null { + // Validate input + if ( + !userLocation || + userLocation.latitude == null || + userLocation.longitude == null + ) { + return null; + } + + // Parse and validate coordinates + const userLat = + typeof userLocation.latitude === "number" + ? userLocation.latitude + : parseFloat(String(userLocation.latitude)); + const userLng = + typeof userLocation.longitude === "number" + ? userLocation.longitude + : parseFloat(String(userLocation.longitude)); + + if (!Number.isFinite(userLat) || !Number.isFinite(userLng)) { + return null; + } + + // Find closest region + let closestRegion: RegionId | null = null; + let minDistance = Infinity; + + for (const [regionId, coords] of Object.entries(REGION_COORDINATES)) { + const distance = calculateHaversineDistance( + userLat, + userLng, + coords.lat, + coords.lng + ); + + if (distance < minDistance) { + minDistance = distance; + closestRegion = regionId as RegionId; + } + } + + return closestRegion; +} diff --git a/create-db/src/index.ts b/create-db/src/index.ts index d4ba97c..ea85b24 100644 --- a/create-db/src/index.ts +++ b/create-db/src/index.ts @@ -1,594 +1,359 @@ -import { intro, outro, cancel, select, spinner, log, isCancel } from "@clack/prompts"; +import { + intro, + outro, + cancel, + select, + spinner, + log, + isCancel, +} from "@clack/prompts"; import { createRouterClient, os } from "@orpc/server"; import { randomUUID } from "crypto"; import dotenv from "dotenv"; import fs from "fs"; -import path from "path"; import pc from "picocolors"; import terminalLink from "terminal-link"; import { createCli } from "trpc-cli"; import { z } from "zod"; import { - type UserLocation, - type Region, - type RegionCoordinates, - type CreateDatabaseResult, - type DatabaseResult, - type ApiResponse, - type GeoLocationResponse, - type RegionsResponse, - type ProgrammaticCreateOptions, - type RegionId, - RegionSchema, + type Region, + type CreateDatabaseResult, + type DatabaseResult, + type ProgrammaticCreateOptions, + type RegionId, + RegionSchema, } from "./types.js"; +import { sendAnalytics, flushAnalytics } from "./analytics.js"; +import { createDatabaseCore, getCommandName } from "./database.js"; +import { readUserEnvFile } from "./env-utils.js"; +import { + detectUserLocation, + getRegionClosestToLocation, +} from "./geolocation.js"; +import { checkOnline, getRegions, validateRegion } from "./regions.js"; export type { - Region, - RegionId, - CreateDatabaseResult, - DatabaseResult, - DatabaseError, - ProgrammaticCreateOptions, + Region, + RegionId, + CreateDatabaseResult, + DatabaseResult, + DatabaseError, + ProgrammaticCreateOptions, } from "./types.js"; export { isDatabaseError, isDatabaseSuccess, RegionSchema } from "./types.js"; dotenv.config({ - quiet: true + quiet: true, }); const CREATE_DB_WORKER_URL = - process.env.CREATE_DB_WORKER_URL || "https://create-db-temp.prisma.io"; + process.env.CREATE_DB_WORKER_URL || "https://create-db-temp.prisma.io"; const CLAIM_DB_WORKER_URL = - process.env.CLAIM_DB_WORKER_URL || "https://create-db.prisma.io"; - -const REGION_COORDINATES: Record = { - "ap-southeast-1": { lat: 1.3521, lng: 103.8198 }, - "ap-northeast-1": { lat: 35.6762, lng: 139.6503 }, - "eu-central-1": { lat: 50.1109, lng: 8.6821 }, - "eu-west-3": { lat: 48.8566, lng: 2.3522 }, - "us-east-1": { lat: 38.9072, lng: -77.0369 }, - "us-west-1": { lat: 37.7749, lng: -122.4194 }, + process.env.CLAIM_DB_WORKER_URL || "https://create-db.prisma.io"; + +// Wrapper functions that include worker URLs +const sendAnalyticsWithUrl = ( + eventName: string, + properties: Record, + cliRunId: string +) => sendAnalytics(eventName, properties, cliRunId, CREATE_DB_WORKER_URL); + +const checkOnlineWithUrl = async () => { + try { + await checkOnline(CREATE_DB_WORKER_URL); + } catch { + await flushAnalytics(); + process.exit(1); + } }; -const pendingAnalytics: Promise[] = []; - -async function sendAnalytics( - eventName: string, - properties: Record, - cliRunId: string -): Promise { - const controller = new AbortController(); - const timer = setTimeout(() => controller.abort(), 5000); - - const promise = (async () => { - try { - await fetch(`${CREATE_DB_WORKER_URL}/analytics`, { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ - eventName, - properties: { distinct_id: cliRunId, ...properties }, - }), - signal: controller.signal, - }); - } catch { - // Analytics failures should not block CLI - } finally { - clearTimeout(timer); - } - })(); - - pendingAnalytics.push(promise); -} - -async function flushAnalytics(maxWaitMs = 500): Promise { - if (pendingAnalytics.length === 0) return; - await Promise.race([ - Promise.all(pendingAnalytics), - new Promise((resolve) => setTimeout(resolve, maxWaitMs)), - ]); -} - -function getCommandName(): string { - const executable = process.argv[1] || "create-db"; - if (executable.includes("create-pg")) return "create-pg"; - if (executable.includes("create-postgres")) return "create-postgres"; - return "create-db"; -} - -async function detectUserLocation(): Promise { - try { - const response = await fetch("https://ipapi.co/json/", { - method: "GET", - headers: { "User-Agent": "create-db-cli/1.0" }, - }); - - if (!response.ok) return null; - - const data = (await response.json()) as GeoLocationResponse; - return { - country: data.country_code, - continent: data.continent_code, - city: data.city, - region: data.region, - latitude: data.latitude, - longitude: data.longitude, - }; - } catch { - return null; - } -} - -function getRegionClosestToLocation( - userLocation: { latitude?: number | string; longitude?: number | string } | null -): RegionId | null { - if (!userLocation) return null; - - const userLat = parseFloat(String(userLocation.latitude)); - const userLng = parseFloat(String(userLocation.longitude)); +const getRegionsWithUrl = () => getRegions(CREATE_DB_WORKER_URL); - if (isNaN(userLat) || isNaN(userLng)) return null; +const validateRegionWithUrl = (region: string) => + validateRegion(region, CREATE_DB_WORKER_URL); - let closestRegion: RegionId | null = null; - let minDistance = Infinity; +const createDatabaseCoreWithUrl = ( + region: string, + userAgent?: string, + cliRunId?: string +) => + createDatabaseCore( + region, + CREATE_DB_WORKER_URL, + CLAIM_DB_WORKER_URL, + userAgent, + cliRunId + ); - for (const [region, coordinates] of Object.entries(REGION_COORDINATES)) { - const latDiff = ((userLat - coordinates.lat) * Math.PI) / 180; - const lngDiff = ((userLng - coordinates.lng) * Math.PI) / 180; - const a = - Math.sin(latDiff / 2) ** 2 + - Math.cos((userLat * Math.PI) / 180) * - Math.cos((coordinates.lat * Math.PI) / 180) * - Math.sin(lngDiff / 2) ** 2; - const distance = 6371 * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); - - if (distance < minDistance) { - minDistance = distance; - closestRegion = region as RegionId; +const router = os.router({ + create: os + .meta({ + description: "Create a new Prisma Postgres database", + default: true, + }) + .input( + z.object({ + region: RegionSchema.optional() + .describe("AWS region for the database") + .meta({ alias: "r" }), + interactive: z + .boolean() + .optional() + .default(false) + .describe("Run in interactive mode to select a region") + .meta({ alias: "i" }), + json: z + .boolean() + .optional() + .default(false) + .describe("Output machine-readable JSON") + .meta({ alias: "j" }), + env: z + .string() + .optional() + .describe( + "Write DATABASE_URL and CLAIM_URL to the specified .env file" + ) + .meta({ alias: "e" }), + }) + ) + .handler(async ({ input }) => { + const cliRunId = randomUUID(); + const CLI_NAME = getCommandName(); + + let userAgent: string | undefined; + const userEnvVars = readUserEnvFile(); + if (userEnvVars.PRISMA_ACTOR_NAME && userEnvVars.PRISMA_ACTOR_PROJECT) { + userAgent = `${userEnvVars.PRISMA_ACTOR_NAME}/${userEnvVars.PRISMA_ACTOR_PROJECT}`; + } + + void sendAnalyticsWithUrl( + "create_db:cli_command_ran", + { + command: CLI_NAME, + "has-region-flag": !!input.region, + "has-interactive-flag": input.interactive, + "has-json-flag": input.json, + "has-env-flag": !!input.env, + "has-user-agent-from-env": !!userAgent, + "node-version": process.version, + platform: process.platform, + arch: process.arch, + }, + cliRunId + ); + + let region: RegionId = input.region ?? "us-east-1"; + + if (!input.region) { + const userLocation = await detectUserLocation(); + region = getRegionClosestToLocation(userLocation) ?? region; + } + + const envPath = input.env; + const envEnabled = + typeof envPath === "string" && envPath.trim().length > 0; + + if (input.json || envEnabled) { + if (input.interactive) { + await checkOnlineWithUrl(); + const regions = await getRegionsWithUrl(); + + const selectedRegion = await select({ + message: "Choose a region:", + options: regions.map((r) => ({ + value: r.id, + label: r.name || r.id, + })), + initialValue: + regions.find((r) => r.id === region)?.id || regions[0]?.id, + }); + + if (isCancel(selectedRegion)) { + cancel(pc.red("Operation cancelled.")); + await flushAnalytics(); + process.exit(0); + } + + region = selectedRegion as RegionId; + void sendAnalyticsWithUrl( + "create_db:region_selected", + { region, "selection-method": "interactive" }, + cliRunId + ); + } else if (input.region) { + await validateRegionWithUrl(region); + void sendAnalyticsWithUrl( + "create_db:region_selected", + { region, "selection-method": "flag" }, + cliRunId + ); } - } - return closestRegion; -} + await checkOnlineWithUrl(); + const result = await createDatabaseCoreWithUrl( + region, + userAgent, + cliRunId + ); + await flushAnalytics(); -function readUserEnvFile(): Record { - const envPath = path.join(process.cwd(), ".env"); - if (!fs.existsSync(envPath)) return {}; + if (input.json) { + console.log(JSON.stringify(result, null, 2)); + return; + } - const envContent = fs.readFileSync(envPath, "utf8"); - const envVars: Record = {}; + if (!result.success) { + console.error(result.message); + process.exit(1); + } - for (const line of envContent.split("\n")) { - const trimmed = line.trim(); - if (trimmed && !trimmed.startsWith("#")) { - const [key, ...valueParts] = trimmed.split("="); - if (key && valueParts.length > 0) { - const value = valueParts.join("=").replace(/^["']|["']$/g, ""); - envVars[key.trim()] = value.trim(); + try { + const targetEnvPath = envPath!; + const lines = [ + `DATABASE_URL="${result.connectionString ?? ""}"`, + `CLAIM_URL="${result.claimUrl}"`, + "", + ]; + + let prefix = ""; + if (fs.existsSync(targetEnvPath)) { + const existing = fs.readFileSync(targetEnvPath, "utf8"); + if (existing.length > 0 && !existing.endsWith("\n")) { + prefix = "\n"; } + } + + fs.appendFileSync(targetEnvPath, prefix + lines.join("\n"), { + encoding: "utf8", + }); + + console.log( + pc.green(`Wrote DATABASE_URL and CLAIM_URL to ${targetEnvPath}`) + ); + } catch (err) { + console.error( + pc.red( + `Failed to write environment variables to ${envPath}: ${ + err instanceof Error ? err.message : String(err) + }` + ) + ); + process.exit(1); } - } - return envVars; -} + return; + } -async function checkOnline(): Promise { - try { - const res = await fetch(`${CREATE_DB_WORKER_URL}/health`); - if (!res.ok) throw new Error("API not available"); - } catch { - console.error( - pc.bold(pc.red("\nāœ– Error: Cannot reach Prisma Postgres API server.\n")) - ); - console.error( - pc.dim( - `Check your internet connection or visit ${pc.green("https://www.prisma-status.com/")}\n` - ) - ); - await flushAnalytics(); - process.exit(1); - } -} + await checkOnlineWithUrl(); -async function getRegions(): Promise { - const res = await fetch(`${CREATE_DB_WORKER_URL}/regions`); + intro(pc.bold(pc.cyan("šŸš€ Creating a Prisma Postgres database"))); - if (!res.ok) { - throw new Error( - `Failed to fetch regions. Status: ${res.status} ${res.statusText}` - ); - } + if (input.interactive) { + const regions = await getRegionsWithUrl(); - const data = (await res.json()) as RegionsResponse; - const regions: Region[] = Array.isArray(data) ? data : (data.data ?? []); - return regions.filter((region) => region.status === "available"); -} + const selectedRegion = await select({ + message: "Choose a region:", + options: regions.map((r) => ({ value: r.id, label: r.name || r.id })), + initialValue: + regions.find((r) => r.id === region)?.id || regions[0]?.id, + }); -async function validateRegion(region: string): Promise { - const regions = await getRegions(); - const regionIds = regions.map((r) => r.id); + if (isCancel(selectedRegion)) { + cancel(pc.red("Operation cancelled.")); + await flushAnalytics(); + process.exit(0); + } - if (!regionIds.includes(region)) { - throw new Error( - `Invalid region: ${region}. Available regions: ${regionIds.join(", ")}` + region = selectedRegion as RegionId; + void sendAnalyticsWithUrl( + "create_db:region_selected", + { region, "selection-method": "interactive" }, + cliRunId + ); + } else if (input.region) { + await validateRegionWithUrl(region); + void sendAnalyticsWithUrl( + "create_db:region_selected", + { region, "selection-method": "flag" }, + cliRunId ); - } + } - return region; -} + const s = spinner(); + s.start(`Creating database in ${pc.cyan(region)}...`); -async function createDatabaseCore( - region: string, - userAgent?: string, - cliRunId?: string -): Promise { - const name = new Date().toISOString(); - const runId = cliRunId ?? randomUUID(); - - const resp = await fetch(`${CREATE_DB_WORKER_URL}/create`, { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ - region, - name, - utm_source: getCommandName(), - userAgent, - }), - }); - - if (resp.status === 429) { - void sendAnalytics( - "create_db:database_creation_failed", - { region, "error-type": "rate_limit", "status-code": 429 }, - runId - ); - return { - success: false, - error: "rate_limit_exceeded", - message: - "We're experiencing a high volume of requests. Please try again later.", - status: 429, - }; - } - - let result: ApiResponse; - let raw = ""; - try { - raw = await resp.text(); - result = JSON.parse(raw) as ApiResponse; - } catch { - void sendAnalytics( - "create_db:database_creation_failed", - { region, "error-type": "invalid_json", "status-code": resp.status }, - runId - ); - return { - success: false, - error: "invalid_json", - message: "Unexpected response from create service.", - raw, - status: resp.status, - }; - } - - if (result.error) { - void sendAnalytics( - "create_db:database_creation_failed", - { - region, - "error-type": "api_error", - "error-message": result.error.message, - }, - runId - ); - return { - success: false, - error: "api_error", - message: result.error.message || "Unknown error", - details: result.error, - status: result.error.status ?? resp.status, - }; - } - - const database = result.data?.database ?? result.databases?.[0]; - const projectId = result.data?.id ?? result.id ?? ""; - - const apiKeys = database?.apiKeys; - const directConnDetails = result.data - ? apiKeys?.[0]?.directConnection - : result.databases?.[0]?.apiKeys?.[0]?.ppgDirectConnection; - - const directUser = directConnDetails?.user - ? encodeURIComponent(String(directConnDetails.user)) - : ""; - const directPass = directConnDetails?.pass - ? encodeURIComponent(String(directConnDetails.pass)) - : ""; - const directHost = directConnDetails?.host; - const directPort = directConnDetails?.port - ? `:${directConnDetails.port}` - : ""; - const directDbName = directConnDetails?.database || "postgres"; - - const connectionString = - directConnDetails && directHost - ? `postgresql://${directUser}:${directPass}@${directHost}${directPort}/${directDbName}?sslmode=require` - : null; - - const claimUrl = `${CLAIM_DB_WORKER_URL}/claim?projectID=${projectId}&utm_source=${userAgent || getCommandName()}&utm_medium=cli`; - const expiryDate = new Date(Date.now() + 24 * 60 * 60 * 1000); - - void sendAnalytics( - "create_db:database_created", - { region, utm_source: getCommandName() }, - runId - ); - - return { - success: true, - connectionString, - claimUrl, - deletionDate: expiryDate.toISOString(), - region: database?.region?.id || region, - name: database?.name ?? name, - projectId, + const result = await createDatabaseCoreWithUrl( + region, userAgent, - }; -} + cliRunId + ); -const router = os.router({ - create: os - .meta({ - description: "Create a new Prisma Postgres database", - default: true, - }) - .input( - z.object({ - region: RegionSchema.optional() - .describe("AWS region for the database") - .meta({ alias: "r" }), - interactive: z - .boolean() - .optional() - .default(false) - .describe("Run in interactive mode to select a region") - .meta({ alias: "i" }), - json: z - .boolean() - .optional() - .default(false) - .describe("Output machine-readable JSON") - .meta({ alias: "j" }), - env: z - .string() - .optional() - .describe( - "Write DATABASE_URL and CLAIM_URL to the specified .env file" - ) - .meta({ alias: "e" }), - }) + if (!result.success) { + s.stop(pc.red(`Error: ${result.message}`)); + await flushAnalytics(); + process.exit(1); + } + + s.stop(pc.green("Database created successfully!")); + + const expiryFormatted = new Date(result.deletionDate).toLocaleString(); + const clickableUrl = terminalLink(result.claimUrl, result.claimUrl, { + fallback: false, + }); + + log.message(""); + log.info(pc.bold("Database Connection")); + log.message(""); + + if (result.connectionString) { + log.message(pc.cyan(" Connection String:")); + log.message(" " + pc.yellow(result.connectionString)); + log.message(""); + } else { + log.warning(pc.yellow(" Connection details are not available.")); + log.message(""); + } + + log.success(pc.bold("Claim Your Database")); + log.message(pc.cyan(" Keep your database for free:")); + log.message(" " + pc.yellow(clickableUrl)); + log.message( + pc.italic( + pc.dim( + ` Database will be deleted on ${expiryFormatted} if not claimed.` + ) ) - .handler(async ({ input }) => { - const cliRunId = randomUUID(); - const CLI_NAME = getCommandName(); - - let userAgent: string | undefined; - const userEnvVars = readUserEnvFile(); - if (userEnvVars.PRISMA_ACTOR_NAME && userEnvVars.PRISMA_ACTOR_PROJECT) { - userAgent = `${userEnvVars.PRISMA_ACTOR_NAME}/${userEnvVars.PRISMA_ACTOR_PROJECT}`; - } - - void sendAnalytics( - "create_db:cli_command_ran", - { - command: CLI_NAME, - "has-region-flag": !!input.region, - "has-interactive-flag": input.interactive, - "has-json-flag": input.json, - "has-env-flag": !!input.env, - "has-user-agent-from-env": !!userAgent, - "node-version": process.version, - platform: process.platform, - arch: process.arch, - }, - cliRunId - ); - - let region: RegionId = input.region ?? "us-east-1"; - - if (!input.region) { - const userLocation = await detectUserLocation(); - region = getRegionClosestToLocation(userLocation) ?? region; - } - - const envPath = input.env; - const envEnabled = - typeof envPath === "string" && envPath.trim().length > 0; - - if (input.json || envEnabled) { - if (input.interactive) { - await checkOnline(); - const regions = await getRegions(); - - const selectedRegion = await select({ - message: "Choose a region:", - options: regions.map((r) => ({ value: r.id, label: r.name || r.id })), - initialValue: - regions.find((r) => r.id === region)?.id || regions[0]?.id, - }); - - if (isCancel(selectedRegion)) { - cancel(pc.red("Operation cancelled.")); - await flushAnalytics(); - process.exit(0); - } - - region = selectedRegion as RegionId; - void sendAnalytics( - "create_db:region_selected", - { region, "selection-method": "interactive" }, - cliRunId - ); - } else if (input.region) { - await validateRegion(region); - void sendAnalytics( - "create_db:region_selected", - { region, "selection-method": "flag" }, - cliRunId - ); - } - - await checkOnline(); - const result = await createDatabaseCore(region, userAgent, cliRunId); - await flushAnalytics(); - - if (input.json) { - console.log(JSON.stringify(result, null, 2)); - return; - } - - if (!result.success) { - console.error(result.message); - process.exit(1); - } - - try { - const targetEnvPath = envPath!; - const lines = [ - `DATABASE_URL="${result.connectionString ?? ""}"`, - `CLAIM_URL="${result.claimUrl}"`, - "", - ]; - - let prefix = ""; - if (fs.existsSync(targetEnvPath)) { - const existing = fs.readFileSync(targetEnvPath, "utf8"); - if (existing.length > 0 && !existing.endsWith("\n")) { - prefix = "\n"; - } - } - - fs.appendFileSync(targetEnvPath, prefix + lines.join("\n"), { - encoding: "utf8", - }); - - console.log( - pc.green(`Wrote DATABASE_URL and CLAIM_URL to ${targetEnvPath}`) - ); - } catch (err) { - console.error( - pc.red( - `Failed to write environment variables to ${envPath}: ${err instanceof Error ? err.message : String(err) - }` - ) - ); - process.exit(1); - } - - return; - } - - await checkOnline(); - - intro(pc.bold(pc.cyan("šŸš€ Creating a Prisma Postgres database"))); - - if (input.interactive) { - const regions = await getRegions(); - - const selectedRegion = await select({ - message: "Choose a region:", - options: regions.map((r) => ({ value: r.id, label: r.name || r.id })), - initialValue: - regions.find((r) => r.id === region)?.id || regions[0]?.id, - }); - - if (isCancel(selectedRegion)) { - cancel(pc.red("Operation cancelled.")); - await flushAnalytics(); - process.exit(0); - } - - region = selectedRegion as RegionId; - void sendAnalytics( - "create_db:region_selected", - { region, "selection-method": "interactive" }, - cliRunId - ); - } else if (input.region) { - await validateRegion(region); - void sendAnalytics( - "create_db:region_selected", - { region, "selection-method": "flag" }, - cliRunId - ); - } - - const s = spinner(); - s.start(`Creating database in ${pc.cyan(region)}...`); - - const result = await createDatabaseCore(region, userAgent, cliRunId); - - if (!result.success) { - s.stop(pc.red(`Error: ${result.message}`)); - await flushAnalytics(); - process.exit(1); - } - - s.stop(pc.green("Database created successfully!")); - - const expiryFormatted = new Date(result.deletionDate).toLocaleString(); - const clickableUrl = terminalLink(result.claimUrl, result.claimUrl, { - fallback: false, - }); - - log.message(""); - log.info(pc.bold("Database Connection")); - log.message(""); - - if (result.connectionString) { - log.message(pc.cyan(" Connection String:")); - log.message(" " + pc.yellow(result.connectionString)); - log.message(""); - } else { - log.warning(pc.yellow(" Connection details are not available.")); - log.message(""); - } - - log.success(pc.bold("Claim Your Database")); - log.message(pc.cyan(" Keep your database for free:")); - log.message(" " + pc.yellow(clickableUrl)); - log.message( - pc.italic(pc.dim(` Database will be deleted on ${expiryFormatted} if not claimed.`)) - ); - - outro(pc.dim("Done!")); - await flushAnalytics(); - }), - - regions: os - .meta({ description: "List available Prisma Postgres regions" }) - .handler(async (): Promise => { - const regions = await getRegions(); - - log.message(""); - log.info(pc.bold(pc.cyan("Available Prisma Postgres regions:"))); - log.message(""); - for (const r of regions) { - log.message(` ${pc.green(r.id)} - ${r.name || r.id}`); - } - log.message(""); - }), + ); + + outro(pc.dim("Done!")); + await flushAnalytics(); + }), + + regions: os + .meta({ description: "List available Prisma Postgres regions" }) + .handler(async (): Promise => { + const regions = await getRegionsWithUrl(); + + log.message(""); + log.info(pc.bold(pc.cyan("Available Prisma Postgres regions:"))); + log.message(""); + for (const r of regions) { + log.message(` ${pc.green(r.id)} - ${r.name || r.id}`); + } + log.message(""); + }), }); export function createDbCli() { - return createCli({ - router, - name: getCommandName(), - version: "1.1.0", - description: "Instantly create a temporary Prisma Postgres database", - }); + return createCli({ + router, + name: getCommandName(), + version: "1.1.0", + description: "Instantly create a temporary Prisma Postgres database", + }); } const caller = createRouterClient(router, { context: {} }); @@ -616,9 +381,12 @@ const caller = createRouterClient(router, { context: {} }); * ``` */ export async function create( - options?: ProgrammaticCreateOptions + options?: ProgrammaticCreateOptions ): Promise { - return createDatabaseCore(options?.region || "us-east-1", options?.userAgent); + return createDatabaseCoreWithUrl( + options?.region || "us-east-1", + options?.userAgent + ); } /** @@ -633,5 +401,5 @@ export async function create( * ``` */ export async function regions(): Promise { - return getRegions(); + return getRegionsWithUrl(); } diff --git a/create-db/src/regions.ts b/create-db/src/regions.ts new file mode 100644 index 0000000..d781a86 --- /dev/null +++ b/create-db/src/regions.ts @@ -0,0 +1,49 @@ +import pc from "picocolors"; +import type { Region, RegionsResponse } from "./types.js"; + +export async function checkOnline(workerUrl: string): Promise { + try { + const res = await fetch(`${workerUrl}/health`); + if (!res.ok) throw new Error("API not available"); + } catch { + console.error( + pc.bold(pc.red("\nāœ– Error: Cannot reach Prisma Postgres API server.\n")) + ); + console.error( + pc.dim( + `Check your internet connection or visit ${pc.green("https://www.prisma-status.com/")}\n` + ) + ); + throw new Error("Cannot reach API server"); + } +} + +export async function getRegions(workerUrl: string): Promise { + const res = await fetch(`${workerUrl}/regions`); + + if (!res.ok) { + throw new Error( + `Failed to fetch regions. Status: ${res.status} ${res.statusText}` + ); + } + + const data = (await res.json()) as RegionsResponse; + const regions: Region[] = Array.isArray(data) ? data : (data.data ?? []); + return regions.filter((region) => region.status === "available"); +} + +export async function validateRegion( + region: string, + workerUrl: string +): Promise { + const regions = await getRegions(workerUrl); + const regionIds = regions.map((r) => r.id); + + if (!regionIds.includes(region)) { + throw new Error( + `Invalid region: ${region}. Available regions: ${regionIds.join(", ")}` + ); + } + + return region; +} diff --git a/create-db/src/types.ts b/create-db/src/types.ts index 54f8b34..3654c0f 100644 --- a/create-db/src/types.ts +++ b/create-db/src/types.ts @@ -1,129 +1,129 @@ import z from "zod"; export const RegionSchema = z.enum([ - "ap-southeast-1", - "ap-northeast-1", - "eu-central-1", - "eu-west-3", - "us-east-1", - "us-west-1", + "ap-southeast-1", + "ap-northeast-1", + "eu-central-1", + "eu-west-3", + "us-east-1", + "us-west-1", ]); export type RegionId = z.infer; export interface UserLocation { - country: string; - continent: string; - city: string; - region: string; - latitude: number; - longitude: number; + country: string; + continent: string; + city: string; + region: string; + latitude: number; + longitude: number; } export interface PartialUserLocation { - latitude?: number | string; - longitude?: number | string; + latitude?: number | string; + longitude?: number | string; } export interface RegionCoordinates { - lat: number; - lng: number; + lat: number; + lng: number; } export interface Region { - id: string; - name?: string; - status: string; + id: string; + name?: string; + status: string; } export interface DatabaseResult { - success: true; - connectionString: string | null; - claimUrl: string; - deletionDate: string; - region: string; - name: string; - projectId: string; - userAgent?: string; + success: true; + connectionString: string | null; + claimUrl: string; + deletionDate: string; + region: string; + name: string; + projectId: string; + userAgent?: string; } export interface DatabaseError { - success: false; - error: string; - message: string; - raw?: string; - details?: unknown; - status?: number; + success: false; + error: string; + message: string; + raw?: string; + details?: unknown; + status?: number; } export type CreateDatabaseResult = DatabaseResult | DatabaseError; export function isDatabaseError( - result: CreateDatabaseResult + result: CreateDatabaseResult ): result is DatabaseError { - return !result.success; + return !result.success; } export function isDatabaseSuccess( - result: CreateDatabaseResult + result: CreateDatabaseResult ): result is DatabaseResult { - return result.success; + return result.success; } export interface ApiResponseData { - id?: string; - database?: DatabaseRecord; + id?: string; + database?: DatabaseRecord; } export interface DatabaseRecord { - name?: string; - region?: { - id?: string; - }; - apiKeys?: ApiKey[]; + name?: string; + region?: { + id?: string; + }; + apiKeys?: ApiKey[]; } export interface ApiKey { - directConnection?: ConnectionDetails; - ppgDirectConnection?: ConnectionDetails; + directConnection?: ConnectionDetails; + ppgDirectConnection?: ConnectionDetails; } export interface ConnectionDetails { - user?: string; - pass?: string; - host?: string; - port?: number | string; - database?: string; + user?: string; + pass?: string; + host?: string; + port?: number | string; + database?: string; } export interface ApiResponse { - data?: ApiResponseData; - databases?: DatabaseRecord[]; - id?: string; - error?: ApiErrorInfo; + data?: ApiResponseData; + databases?: DatabaseRecord[]; + id?: string; + error?: ApiErrorInfo; } export interface ApiErrorInfo { - message?: string; - status?: number; + message?: string; + status?: number; } export interface GeoLocationResponse { - country_code: string; - continent_code: string; - city: string; - region: string; - latitude: number; - longitude: number; + country_code: string; + continent_code: string; + city: string; + region: string; + latitude: number; + longitude: number; } export interface RegionsApiResponse { - data?: Region[]; + data?: Region[]; } export type RegionsResponse = Region[] | RegionsApiResponse; export interface ProgrammaticCreateOptions { - region?: RegionId; - userAgent?: string; + region?: RegionId; + userAgent?: string; } diff --git a/create-db/vitest.config.ts b/create-db/vitest.config.ts index 739d065..9243ae4 100644 --- a/create-db/vitest.config.ts +++ b/create-db/vitest.config.ts @@ -5,4 +5,3 @@ export default defineConfig({ include: ["__tests__/**/*.test.ts"], }, }); - diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 47cc60d..dc208e5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -31,22 +31,22 @@ importers: dependencies: '@monaco-editor/react': specifier: ^4.7.0 - version: 4.7.0(monaco-editor@0.52.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + version: 4.7.0(monaco-editor@0.52.2)(react-dom@19.1.2(react@19.1.2))(react@19.1.2) '@opennextjs/cloudflare': specifier: ^1.6.4 version: 1.6.4(wrangler@4.30.0) '@prisma/studio-core': specifier: ^0.5.2 - version: 0.5.2(@prisma/client@6.14.0(prisma@6.14.0(typescript@5.8.3))(typescript@5.8.3))(@types/react@19.1.10)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + version: 0.5.2(@prisma/client@6.14.0(prisma@7.1.0(@types/react@19.1.10)(react-dom@19.1.2(react@19.1.2))(react@19.1.2)(typescript@5.8.3))(typescript@5.8.3))(@types/react@19.1.10)(react-dom@19.1.2(react@19.1.2))(react@19.1.2) '@types/pg': specifier: ^8.15.5 version: 8.15.5 lucide-react: specifier: ^0.541.0 - version: 0.541.0(react@19.1.0) + version: 0.541.0(react@19.1.2) next: specifier: 15.4.6 - version: 15.4.6(@babel/core@7.28.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + version: 15.4.6(@babel/core@7.28.4)(react-dom@19.1.2(react@19.1.2))(react@19.1.2) pg: specifier: ^8.16.3 version: 8.16.3 @@ -54,17 +54,17 @@ importers: specifier: ^1.282.0 version: 1.282.0 prisma: - specifier: ^6.14.0 - version: 6.14.0(typescript@5.8.3) + specifier: ^7.1.0 + version: 7.1.0(@types/react@19.1.10)(react-dom@19.1.2(react@19.1.2))(react@19.1.2)(typescript@5.8.3) react: - specifier: 19.1.0 - version: 19.1.0 + specifier: 19.1.2 + version: 19.1.2 react-dom: - specifier: 19.1.0 - version: 19.1.0(react@19.1.0) + specifier: 19.1.2 + version: 19.1.2(react@19.1.2) react-hot-toast: specifier: ^2.6.0 - version: 2.6.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + version: 2.6.0(react-dom@19.1.2(react@19.1.2))(react@19.1.2) devDependencies: '@tailwindcss/postcss': specifier: ^4 @@ -77,7 +77,7 @@ importers: version: 6.8.0 '@testing-library/react': specifier: ^16.3.0 - version: 16.3.0(@testing-library/dom@10.4.1)(@types/react-dom@19.1.7(@types/react@19.1.10))(@types/react@19.1.10)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + version: 16.3.0(@testing-library/dom@10.4.1)(@types/react-dom@19.1.7(@types/react@19.1.10))(@types/react@19.1.10)(react-dom@19.1.2(react@19.1.2))(react@19.1.2) '@types/node': specifier: ^20.19.10 version: 20.19.11 @@ -134,7 +134,7 @@ importers: version: 5.0.0 trpc-cli: specifier: ^0.12.1 - version: 0.12.1(@orpc/server@1.12.2(ws@8.18.3))(effect@3.16.12)(zod@4.1.13) + version: 0.12.1(@orpc/server@1.12.2(ws@8.18.3))(effect@3.18.4)(valibot@1.2.0(typescript@5.9.3))(zod@4.1.13) zod: specifier: ^4.1.13 version: 4.1.13 @@ -720,6 +720,18 @@ packages: '@changesets/write@0.4.0': resolution: {integrity: sha512-CdTLvIOPiCNuH71pyDu3rA+Q0n65cmAbXnwWH84rKGiFumFzkmHNT8KHTMEchcxN+Kl8I54xGUhJ7l3E7X396Q==} + '@chevrotain/cst-dts-gen@10.5.0': + resolution: {integrity: sha512-lhmC/FyqQ2o7pGK4Om+hzuDrm9rhFYIJ/AXoQBeongmn870Xeb0L6oGEiuR8nohFNL5sMaQEJWCxr1oIVIVXrw==} + + '@chevrotain/gast@10.5.0': + resolution: {integrity: sha512-pXdMJ9XeDAbgOWKuD1Fldz4ieCs6+nLNmyVhe2gZVqoO7v8HXuHYs5OV2EzUtbuai37TlOAQHrTDvxMnvMJz3A==} + + '@chevrotain/types@10.5.0': + resolution: {integrity: sha512-f1MAia0x/pAVPWH/T73BJVyO2XU5tI4/iE7cnxb7tqdNTNhQI3Uq3XkqcoteTmD4t1aM0LbHCJOhgIDn07kl2A==} + + '@chevrotain/utils@10.5.0': + resolution: {integrity: sha512-hBzuU5+JjB2cqNZyszkDHZgOSrUUT8V3dhgRl8Q9Gp6dAj/H5+KILGjbhDpc3Iy9qmqlm/akuOI2ut9VUtzJxQ==} + '@clack/core@0.5.0': resolution: {integrity: sha512-p3y0FIOwaYRUPRcMO7+dlmLh8PSRcrjuTndsiA0WAFbWES0mLZlrjVoBRZ9DzkPFJZG6KGkJmoEAY0ZcVWTkow==} @@ -952,6 +964,20 @@ packages: resolution: {integrity: sha512-0dEVyRLM/lG4gp1R/Ik5bfPl/1wX00xFwd5KcNH602tzBa09oF7pbTKETEhR1GjZ75K6OJnYFu8II2dyMhONMw==} engines: {node: '>=16'} + '@electric-sql/pglite-socket@0.0.6': + resolution: {integrity: sha512-6RjmgzphIHIBA4NrMGJsjNWK4pu+bCWJlEWlwcxFTVY3WT86dFpKwbZaGWZV6C5Rd7sCk1Z0CI76QEfukLAUXw==} + hasBin: true + peerDependencies: + '@electric-sql/pglite': 0.3.2 + + '@electric-sql/pglite-tools@0.2.7': + resolution: {integrity: sha512-9dAccClqxx4cZB+Ar9B+FZ5WgxDc/Xvl9DPrTWv+dYTf0YNubLzi4wHHRGRGhrJv15XwnyKcGOZAP1VXSneSUg==} + peerDependencies: + '@electric-sql/pglite': 0.3.2 + + '@electric-sql/pglite@0.3.2': + resolution: {integrity: sha512-zfWWa+V2ViDCY/cmUfRqeWY1yLto+EpxjXnZzenB1TyxsTiXaTWeZFIZw6mac52BsuQm0RjCnisjBtdBaXOI6w==} + '@emnapi/core@1.7.1': resolution: {integrity: sha512-o1uhUASyo921r2XtHYOHy7gdkGLge8ghBEQHMWmyJFoXlpU58kIrhhN3w26lpQb6dspetweapMn2CSNwQ8I4wg==} @@ -1430,6 +1456,12 @@ packages: peerDependencies: hono: ^4 + '@hono/node-server@1.19.6': + resolution: {integrity: sha512-Shz/KjlIeAhfiuE93NDKVdZ7HdBVLQAfdbaXEaoAVO3ic9ibRSLGIQGkcBbFyuLr+7/1D5ZCINM8B+6IvXeMtw==} + engines: {node: '>=18.14.1'} + peerDependencies: + hono: ^4 + '@img/sharp-darwin-arm64@0.33.5': resolution: {integrity: sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} @@ -1722,6 +1754,10 @@ packages: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + '@mrleebo/prisma-ast@0.12.1': + resolution: {integrity: sha512-JwqeCQ1U3fvccttHZq7Tk0m/TMC6WcFAQZdukypW3AzlJYKYTGNVd1ANU2GuhKnv4UQuOFj3oAl0LLG/gxFN1w==} + engines: {node: '>=16'} + '@napi-rs/wasm-runtime@1.1.0': resolution: {integrity: sha512-Fq6DJW+Bb5jaWE69/qOE0D1TUN9+6uWhCeZpdnSBk14pjLcCWR7Q8n49PTSPHazM37JqrsdpEthXy2xn6jWWiA==} @@ -1911,38 +1947,50 @@ packages: typescript: optional: true - '@prisma/config@6.14.0': - resolution: {integrity: sha512-IwC7o5KNNGhmblLs23swnfBjADkacBb7wvyDXUWLwuvUQciKJZqyecU0jw0d7JRkswrj+XTL8fdr0y2/VerKQQ==} + '@prisma/config@7.1.0': + resolution: {integrity: sha512-Uz+I43Wn1RYNHtuYtOhOnUcNMWp2Pd3GUDDKs37xlHptCGpzEG3MRR9L+8Y2ISMsMI24z/Ni+ww6OB/OO8M0sQ==} '@prisma/debug@5.22.0': resolution: {integrity: sha512-AUt44v3YJeggO2ZU5BkXI7M4hu9BF2zzH2iF2V5pyXT/lRTyWiElZ7It+bRH1EshoMRxHgpYg4VB6rCM+mG5jQ==} - '@prisma/debug@6.14.0': - resolution: {integrity: sha512-j4Lf+y+5QIJgQD4sJWSbkOD7geKx9CakaLp/TyTy/UDu9Wo0awvWCBH/BAxTHUaCpIl9USA5VS/KJhDqKJSwug==} + '@prisma/debug@6.8.2': + resolution: {integrity: sha512-4muBSSUwJJ9BYth5N8tqts8JtiLT8QI/RSAzEogwEfpbYGFo9mYsInsVo8dqXdPO2+Rm5OG5q0qWDDE3nyUbVg==} + + '@prisma/debug@7.1.0': + resolution: {integrity: sha512-pPAckG6etgAsEBusmZiFwM9bldLSNkn++YuC4jCTJACdK5hLOVnOzX7eSL2FgaU6Gomd6wIw21snUX2dYroMZQ==} + + '@prisma/dev@0.15.0': + resolution: {integrity: sha512-KhWaipnFlS/fWEs6I6Oqjcy2S08vKGmxJ5LexqUl/3Ve0EgLUsZwdKF0MvqPM5F5ttw8GtfZarjM5y7VLwv9Ow==} '@prisma/engines-version@5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2': resolution: {integrity: sha512-2PTmxFR2yHW/eB3uqWtcgRcgAbG1rwG9ZriSvQw+nnb7c4uCr3RAcGMb6/zfE88SKlC1Nj2ziUvc96Z379mHgQ==} - '@prisma/engines-version@6.14.0-25.717184b7b35ea05dfa71a3236b7af656013e1e49': - resolution: {integrity: sha512-EgN9ODJpiX45yvwcngoStp3uQPJ3l+AEVoQ6dMMO2QvmwIlnxfApzKmJQExzdo7/hqQANrz5txHJdGYHzOnGHA==} + '@prisma/engines-version@7.1.0-6.ab635e6b9d606fa5c8fb8b1a7f909c3c3c1c98ba': + resolution: {integrity: sha512-qZUevUh+yPhGT28rDQnV8V2kLnFjirzhVD67elRPIJHRsUV/mkII10HSrJrhK/U2GYgAxXR2VEREtq7AsfS8qw==} '@prisma/engines@5.22.0': resolution: {integrity: sha512-UNjfslWhAt06kVL3CjkuYpHAWSO6L4kDCVPegV6itt7nD1kSJavd3vhgAEhjglLJJKEdJ7oIqDJ+yHk6qO8gPA==} - '@prisma/engines@6.14.0': - resolution: {integrity: sha512-LhJjqsALFEcoAtF07nSaOkVguaxw/ZsgfROIYZ8bAZDobe7y8Wy+PkYQaPOK1iLSsFgV2MhCO/eNrI1gdSOj6w==} + '@prisma/engines@7.1.0': + resolution: {integrity: sha512-KQlraOybdHAzVv45KWKJzpR9mJLkib7/TyApQpqrsL7FUHfgjIcy8jrVGt3iNfG6/GDDl+LNlJ84JSQwIfdzxA==} '@prisma/fetch-engine@5.22.0': resolution: {integrity: sha512-bkrD/Mc2fSvkQBV5EpoFcZ87AvOgDxbG99488a5cexp5Ccny+UM6MAe/UFkUC0wLYD9+9befNOqGiIJhhq+HbA==} - '@prisma/fetch-engine@6.14.0': - resolution: {integrity: sha512-MPzYPOKMENYOaY3AcAbaKrfvXVlvTc6iHmTXsp9RiwCX+bPyfDMqMFVUSVXPYrXnrvEzhGHfyiFy0PRLHPysNg==} + '@prisma/fetch-engine@7.1.0': + resolution: {integrity: sha512-GZYF5Q8kweXWGfn87hTu17kw7x1DgnehgKoE4Zg1BmHYF3y1Uu0QRY/qtSE4veH3g+LW8f9HKqA0tARG66bxxQ==} '@prisma/get-platform@5.22.0': resolution: {integrity: sha512-pHhpQdr1UPFpt+zFfnPazhulaZYCUqeIcPpJViYoq9R+D/yw4fjE+CtnsnKzPYm0ddUbeXUzjGVGIRVgPDCk4Q==} - '@prisma/get-platform@6.14.0': - resolution: {integrity: sha512-7VjuxKNwjnBhKfqPpMeWiHEa2sVjYzmHdl1slW6STuUCe9QnOY0OY1ljGSvz6wpG4U8DfbDqkG1yofd/1GINww==} + '@prisma/get-platform@6.8.2': + resolution: {integrity: sha512-vXSxyUgX3vm1Q70QwzwkjeYfRryIvKno1SXbIqwSptKwqKzskINnDUcx85oX+ys6ooN2ATGSD0xN2UTfg6Zcow==} + + '@prisma/get-platform@7.1.0': + resolution: {integrity: sha512-lq8hMdjKiZftuT5SssYB3EtQj8+YjL24/ZTLflQqzFquArKxBcyp6Xrblto+4lzIKJqnpOjfMiBjMvl7YuD7+Q==} + + '@prisma/query-plan-executor@6.18.0': + resolution: {integrity: sha512-jZ8cfzFgL0jReE1R10gT8JLHtQxjWYLiQ//wHmVYZ2rVkFHoh0DT8IXsxcKcFlfKN7ak7k6j0XMNn2xVNyr5cA==} '@prisma/studio-core@0.5.2': resolution: {integrity: sha512-F/LOafCIfNkSlCdWvg/1JW4tj+RAePP7/WaSLFWDR8TFXVRy0AhjRhzTmcc+sB+pG6B2LEH04yagVSyjXQ31yg==} @@ -1952,6 +2000,13 @@ packages: react: ^18.0.0 || ^19.0.0 react-dom: ^18.0.0 || ^19.0.0 + '@prisma/studio-core@0.8.2': + resolution: {integrity: sha512-/iAEWEUpTja+7gVMu1LtR2pPlvDmveAwMHdTWbDeGlT7yiv0ZTCPpmeAGdq/Y9aJ9Zj1cEGBXGRbmmNPj022PQ==} + peerDependencies: + '@types/react': ^18.0.0 || ^19.0.0 + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + '@quansync/fs@0.1.5': resolution: {integrity: sha512-lNS9hL2aS2NZgNW7BBj+6EBl4rOf8l+tQ0eRY6JWCI8jI2kc53gSoqbjojU0OnAWhzoXiOjFyGsHcDGePB3lhA==} @@ -3105,6 +3160,10 @@ packages: asynckit@0.4.0: resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + aws-ssl-profiles@1.1.2: + resolution: {integrity: sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g==} + engines: {node: '>= 6.0.0'} + aws4fetch@1.0.20: resolution: {integrity: sha512-/djoAN709iY65ETD6LKCtyyEI04XIBP5xVvfmNxsEP0uJB5tyaGBztSryRr4HqMStr9R06PisQE7m9zDTXKu6g==} @@ -3219,6 +3278,9 @@ packages: resolution: {integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==} engines: {node: '>= 16'} + chevrotain@10.5.0: + resolution: {integrity: sha512-Pkv5rBY3+CsHOYfV5g/Vs5JY9WTHHDEKOlohI2XeygaZhUeqhAlldZ8Hz9cRmxu709bvS08YzxHdTPHhffc13A==} + chokidar@4.0.0: resolution: {integrity: sha512-mxIojEAQcuEvT/lyXq+jf/3cO/KoA6z4CeNDGGevTybECPOMFCnQy3OPahluUkbqgPNGw5Bi78UC7Po6Lhy+NA==} engines: {node: '>= 14.16.0'} @@ -3452,6 +3514,10 @@ packages: resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} engines: {node: '>=0.4.0'} + denque@2.1.0: + resolution: {integrity: sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==} + engines: {node: '>=0.10'} + depd@1.1.2: resolution: {integrity: sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==} engines: {node: '>= 0.6'} @@ -3535,8 +3601,8 @@ packages: ee-first@1.1.1: resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} - effect@3.16.12: - resolution: {integrity: sha512-N39iBk0K71F9nb442TLbTkjl24FLUzuvx2i1I2RsEAQsdAdUTuUoW0vlfUXgkMTUOnYqKnWcFfqw4hK4Pw27hg==} + effect@3.18.4: + resolution: {integrity: sha512-b1LXQJLe9D11wfnOKAk3PKxuqYshQ0Heez+y5pnkd3jLj1yx9QhM72zZ9uUrOQyNvrs2GZZd/3maL0ZV18YuDA==} electron-to-chromium@1.5.218: resolution: {integrity: sha512-uwwdN0TUHs8u6iRgN8vKeWZMRll4gBkz+QMqdS7DDe49uiK68/UX92lFb61oiFPrpYZNeZIqa4bA7O6Aiasnzg==} @@ -3935,6 +4001,9 @@ packages: function-bind@1.1.2: resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + generate-function@2.3.1: + resolution: {integrity: sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==} + generic-pool@3.4.2: resolution: {integrity: sha512-H7cUpwCQSiJmAHM4c/aFu6fUfrhWXW1ncyh8ftxEPMu6AiYkHw9K8br720TGPZJbk5eOH2bynjZD1yPvdDAmag==} engines: {node: '>= 4'} @@ -3955,6 +4024,9 @@ packages: resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} engines: {node: '>= 0.4'} + get-port-please@3.1.2: + resolution: {integrity: sha512-Gxc29eLs1fbn6LQ4jSU4vXjlwyZhF5HsGuMAa7gqBP4Rw4yxxltyDUuF5MBclFzDTXO+ACchGQoeela4DSfzdQ==} + get-proto@1.0.1: resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} engines: {node: '>= 0.4'} @@ -4028,6 +4100,9 @@ packages: graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + grammex@3.1.12: + resolution: {integrity: sha512-6ufJOsSA7LcQehIJNCO7HIBykfM7DXQual0Ny780/DEcJIpBlHRvcqEBWGPYd7hrXL2GJ3oJI1MIhaXjWmLQOQ==} + gzip-size@6.0.0: resolution: {integrity: sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q==} engines: {node: '>=10'} @@ -4048,6 +4123,10 @@ packages: resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} engines: {node: '>= 0.4'} + hono@4.10.6: + resolution: {integrity: sha512-BIdolzGpDO9MQ4nu3AUuDwHZZ+KViNm+EZ75Ae55eMXMqLVhDFqEMXxtUe9Qh8hjL+pIna/frs2j6Y2yD5Ua/g==} + engines: {node: '>=16.9.0'} + hono@4.9.4: resolution: {integrity: sha512-61hl6MF6ojTl/8QSRu5ran6GXt+6zsngIUN95KzF5v5UjiX/xnrLR358BNRawwIRO49JwUqJqQe3Rb2v559R8Q==} engines: {node: '>=16.9.0'} @@ -4075,6 +4154,9 @@ packages: resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} engines: {node: '>= 14'} + http-status-codes@2.3.0: + resolution: {integrity: sha512-RJ8XvFvpPM/Dmc5SV+dC4y5PCeOhT3x1Hq0NU3rjGeg5a/CqlhZ7uudknPwZFz4aeAXDcbAyaeP7GAo9lvngtA==} + https-proxy-agent@7.0.6: resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} engines: {node: '>= 14'} @@ -4107,6 +4189,10 @@ packages: resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} engines: {node: '>=0.10.0'} + iconv-lite@0.7.0: + resolution: {integrity: sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==} + engines: {node: '>=0.10.0'} + ignore@5.3.2: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} @@ -4183,6 +4269,9 @@ packages: is-promise@4.0.0: resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==} + is-property@1.0.2: + resolution: {integrity: sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==} + is-stream@2.0.1: resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} engines: {node: '>=8'} @@ -4359,6 +4448,10 @@ packages: resolution: {integrity: sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg==} engines: {node: '>= 12.0.0'} + lilconfig@2.1.0: + resolution: {integrity: sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==} + engines: {node: '>=10'} + lines-and-columns@1.2.4: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} @@ -4397,6 +4490,12 @@ packages: lodash.upperfirst@4.3.1: resolution: {integrity: sha512-sReKOYJIJf74dhJONhU4e0/shzi1trVbSWDOhKYE5XV2O+H7Sb2Dihwuc7xWxVl+DgFPyTqIN3zMfT9cq5iWDg==} + lodash@4.17.21: + resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} + + long@5.3.2: + resolution: {integrity: sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==} + loupe@3.1.4: resolution: {integrity: sha512-wJzkKwJrheKtknCOKNEtDK4iqg/MxmZheEMtSTYvnzRdEYaZzmgH976nenp8WdJRdx5Vc1X/9MO0Oszl6ezeXg==} @@ -4414,6 +4513,10 @@ packages: resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} engines: {node: '>=10'} + lru.min@1.1.3: + resolution: {integrity: sha512-Lkk/vx6ak3rYkRR0Nhu4lFUT2VDnQSxBe8Hbl7f36358p6ow8Bnvr8lrLt98H8J1aGxfhbX4Fs5tYg2+FTwr5Q==} + engines: {bun: '>=1.0.0', deno: '>=1.30.0', node: '>=8.0.0'} + lucide-react@0.541.0: resolution: {integrity: sha512-s0Vircsu5WaGv2KoJZ5+SoxiAJ3UXV5KqEM3eIFDHaHkcLIFdIWgXtZ412+Gh02UsdS7Was+jvEpBvPCWQISlg==} peerDependencies: @@ -4589,6 +4692,14 @@ packages: resolution: {integrity: sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==} hasBin: true + mysql2@3.15.3: + resolution: {integrity: sha512-FBrGau0IXmuqg4haEZRBfHNWB5mUARw6hNwPDXXGg0XzVJ50mr/9hb267lvpVMnhZ1FON3qNd4Xfcez1rbFwSg==} + engines: {node: '>= 8.0'} + + named-placeholders@1.1.4: + resolution: {integrity: sha512-/qfG0Kk/bLJIvej4FcPQ2KYUJP8iQdU1CTxysNb/U2wUNb+/4K485yeio8iNoiwfqJnsTInXoRPTza0dZWHVJQ==} + engines: {node: '>=8.0.0'} + nanoid@3.3.11: resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} @@ -4925,6 +5036,10 @@ packages: resolution: {integrity: sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==} engines: {node: '>=0.10.0'} + postgres@3.4.7: + resolution: {integrity: sha512-Jtc2612XINuBjIl/QTWsV5UvE8UHuNblcO3vVADSrKsrc6RqGX6lOW1cEo3CM2v0XG4Nat8nI+YM7/f26VxXLw==} + engines: {node: '>=12'} + posthog-js@1.282.0: resolution: {integrity: sha512-kx7GyVILxR+Ty4SDA5yRpiIZ73yBZht7ZecnvqqOspqiMnzyoTgdBPn2NELMR0OrhjnfU2KhLkXwooUHmD0MAA==} @@ -4961,19 +5076,25 @@ packages: engines: {node: '>=16.13'} hasBin: true - prisma@6.14.0: - resolution: {integrity: sha512-QEuCwxu+Uq9BffFw7in8In+WfbSUN0ewnaSUKloLkbJd42w6EyFckux4M0f7VwwHlM3A8ssaz4OyniCXlsn0WA==} - engines: {node: '>=18.18'} + prisma@7.1.0: + resolution: {integrity: sha512-dy/3urE4JjhdiW5b09pGjVhGI7kPESK2VlCDrCqeYK5m5SslAtG5FCGnZWP7E8Sdg+Ow1wV2mhJH5RTFL5gEsw==} + engines: {node: ^20.19 || ^22.12 || >=24.0} hasBin: true peerDependencies: - typescript: '>=5.1.0' + better-sqlite3: '>=9.0.0' + typescript: '>=5.4.0' peerDependenciesMeta: + better-sqlite3: + optional: true typescript: optional: true promisepipe@3.0.0: resolution: {integrity: sha512-V6TbZDJ/ZswevgkDNpGt/YqNCiZP9ASfgU+p83uJE6NrGtvSGoOcHLiDCqkMs2+yg7F5qHdLV8d0aS8O26G/KA==} + proper-lockfile@4.1.2: + resolution: {integrity: sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==} + proxy-addr@2.0.7: resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} engines: {node: '>= 0.10'} @@ -5021,10 +5142,10 @@ packages: rc9@2.1.2: resolution: {integrity: sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg==} - react-dom@19.1.0: - resolution: {integrity: sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==} + react-dom@19.1.2: + resolution: {integrity: sha512-dEoydsCp50i7kS1xHOmPXq4zQYoGWedUsvqv9H6zdif2r7yLHygyfP9qou71TulRN0d6ng9EbRVsQhSqfUc19g==} peerDependencies: - react: ^19.1.0 + react: ^19.1.2 react-hot-toast@2.6.0: resolution: {integrity: sha512-bH+2EBMZ4sdyou/DPrfgIouFpcRLCJ+HoCA32UoAYHn6T3Ur5yfcDCeSr5mwldl6pFOsiocmrXMuoCJ1vV8bWg==} @@ -5040,8 +5161,8 @@ packages: resolution: {integrity: sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==} engines: {node: '>=0.10.0'} - react@19.1.0: - resolution: {integrity: sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==} + react@19.1.2: + resolution: {integrity: sha512-MdWVitvLbQULD+4DP8GYjZUrepGW7d+GQkNVqJEzNxE+e9WIa4egVFE/RDfVb1u9u/Jw7dNMmPB4IqxzbFYJ0w==} engines: {node: '>=0.10.0'} read-yaml-file@1.1.0: @@ -5056,6 +5177,12 @@ packages: resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==} engines: {node: '>=8'} + regexp-to-ast@0.5.0: + resolution: {integrity: sha512-tlbJqcMHnPKI9zSrystikWKwHkBqu2a/Sgw01h3zFjvYrMxEDYHzzoMZnUrbIfpTFEsoRnnviOXNCzFiSc54Qw==} + + remeda@2.21.3: + resolution: {integrity: sha512-XXrZdLA10oEOQhLLzEJEiFFSKi21REGAkHdImIb4rt/XXy8ORGXh5HCcpUOsElfPNDb+X6TA/+wkh+p2KffYmg==} + require-directory@2.1.1: resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} engines: {node: '>=0.10.0'} @@ -5075,6 +5202,10 @@ packages: resolve-pkg-maps@1.0.0: resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + retry@0.12.0: + resolution: {integrity: sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==} + engines: {node: '>= 4'} + retry@0.13.1: resolution: {integrity: sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==} engines: {node: '>= 4'} @@ -5163,6 +5294,9 @@ packages: resolution: {integrity: sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==} engines: {node: '>= 18'} + seq-queue@0.0.5: + resolution: {integrity: sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q==} + serve-static@2.2.0: resolution: {integrity: sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==} engines: {node: '>= 18'} @@ -5254,6 +5388,10 @@ packages: sprintf-js@1.0.3: resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} + sqlstring@2.3.3: + resolution: {integrity: sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg==} + engines: {node: '>= 0.6'} + stackback@0.0.2: resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} @@ -5579,6 +5717,10 @@ packages: engines: {node: '>=18.0.0'} hasBin: true + type-fest@4.41.0: + resolution: {integrity: sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==} + engines: {node: '>=16'} + type-fest@5.3.0: resolution: {integrity: sha512-d9CwU93nN0IA1QL+GSNDdwLAu1Ew5ZjTwupvedwg3WdfoH6pIDvYQ2hV0Uc2nKBLPq7NB5apCx57MLS5qlmO5g==} engines: {node: '>=20'} @@ -5691,6 +5833,14 @@ packages: v8-compile-cache-lib@3.0.1: resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==} + valibot@1.2.0: + resolution: {integrity: sha512-mm1rxUsmOxzrwnX5arGS+U4T25RdvpPjPN4yR0u9pUBov9+zGVtO84tif1eY4r6zWxVxu3KzIyknJy3rxfRZZg==} + peerDependencies: + typescript: '>=5' + peerDependenciesMeta: + typescript: + optional: true + vary@1.1.2: resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} engines: {node: '>= 0.8'} @@ -6061,6 +6211,9 @@ packages: youch@4.1.0-beta.10: resolution: {integrity: sha512-rLfVLB4FgQneDr0dv1oddCVZmKjcJ6yX6mS4pU82Mq/Dt9a3cLZQ62pDBL4AUO+uVrCvtWz3ZFUL2HFAFJ/BXQ==} + zeptomatch@2.0.2: + resolution: {integrity: sha512-H33jtSKf8Ijtb5BW6wua3G5DhnFjbFML36eFu+VdOoVY4HD9e7ggjqdM6639B+L87rjnR6Y+XeRzBXZdy52B/g==} + zod@3.22.3: resolution: {integrity: sha512-EjIevzuJRiRPbVH4mGc8nApb/lVLKVpmUhAaR5R5doKGfAnGJ6Gr3CViAVjP+4FWSxCsybeWQdcgCtbX+7oZug==} @@ -7377,6 +7530,21 @@ snapshots: human-id: 4.1.1 prettier: 2.8.8 + '@chevrotain/cst-dts-gen@10.5.0': + dependencies: + '@chevrotain/gast': 10.5.0 + '@chevrotain/types': 10.5.0 + lodash: 4.17.21 + + '@chevrotain/gast@10.5.0': + dependencies: + '@chevrotain/types': 10.5.0 + lodash: 4.17.21 + + '@chevrotain/types@10.5.0': {} + + '@chevrotain/utils@10.5.0': {} + '@clack/core@0.5.0': dependencies: picocolors: 1.1.1 @@ -7617,6 +7785,16 @@ snapshots: dependencies: '@edge-runtime/primitives': 4.1.0 + '@electric-sql/pglite-socket@0.0.6(@electric-sql/pglite@0.3.2)': + dependencies: + '@electric-sql/pglite': 0.3.2 + + '@electric-sql/pglite-tools@0.2.7(@electric-sql/pglite@0.3.2)': + dependencies: + '@electric-sql/pglite': 0.3.2 + + '@electric-sql/pglite@0.3.2': {} + '@emnapi/core@1.7.1': dependencies: '@emnapi/wasi-threads': 1.1.0 @@ -7872,6 +8050,10 @@ snapshots: dependencies: hono: 4.9.4 + '@hono/node-server@1.19.6(hono@4.10.6)': + dependencies: + hono: 4.10.6 + '@img/sharp-darwin-arm64@0.33.5': optionalDependencies: '@img/sharp-libvips-darwin-arm64': 1.0.4 @@ -8121,12 +8303,17 @@ snapshots: dependencies: state-local: 1.0.7 - '@monaco-editor/react@4.7.0(monaco-editor@0.52.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + '@monaco-editor/react@4.7.0(monaco-editor@0.52.2)(react-dom@19.1.2(react@19.1.2))(react@19.1.2)': dependencies: '@monaco-editor/loader': 1.5.0 monaco-editor: 0.52.2 - react: 19.1.0 - react-dom: 19.1.0(react@19.1.0) + react: 19.1.2 + react-dom: 19.1.2(react@19.1.2) + + '@mrleebo/prisma-ast@0.12.1': + dependencies: + chevrotain: 10.5.0 + lilconfig: 2.1.0 '@napi-rs/wasm-runtime@1.1.0': dependencies: @@ -8346,27 +8533,51 @@ snapshots: '@posthog/core@1.4.0': {} - '@prisma/client@6.14.0(prisma@6.14.0(typescript@5.8.3))(typescript@5.8.3)': + '@prisma/client@6.14.0(prisma@7.1.0(@types/react@19.1.10)(react-dom@19.1.2(react@19.1.2))(react@19.1.2)(typescript@5.8.3))(typescript@5.8.3)': optionalDependencies: - prisma: 6.14.0(typescript@5.8.3) + prisma: 7.1.0(@types/react@19.1.10)(react-dom@19.1.2(react@19.1.2))(react@19.1.2)(typescript@5.8.3) typescript: 5.8.3 - '@prisma/config@6.14.0': + '@prisma/config@7.1.0': dependencies: c12: 3.1.0 deepmerge-ts: 7.1.5 - effect: 3.16.12 + effect: 3.18.4 empathic: 2.0.0 transitivePeerDependencies: - magicast '@prisma/debug@5.22.0': {} - '@prisma/debug@6.14.0': {} + '@prisma/debug@6.8.2': {} + + '@prisma/debug@7.1.0': {} + + '@prisma/dev@0.15.0(typescript@5.8.3)': + dependencies: + '@electric-sql/pglite': 0.3.2 + '@electric-sql/pglite-socket': 0.0.6(@electric-sql/pglite@0.3.2) + '@electric-sql/pglite-tools': 0.2.7(@electric-sql/pglite@0.3.2) + '@hono/node-server': 1.19.6(hono@4.10.6) + '@mrleebo/prisma-ast': 0.12.1 + '@prisma/get-platform': 6.8.2 + '@prisma/query-plan-executor': 6.18.0 + foreground-child: 3.3.1 + get-port-please: 3.1.2 + hono: 4.10.6 + http-status-codes: 2.3.0 + pathe: 2.0.3 + proper-lockfile: 4.1.2 + remeda: 2.21.3 + std-env: 3.9.0 + valibot: 1.2.0(typescript@5.8.3) + zeptomatch: 2.0.2 + transitivePeerDependencies: + - typescript '@prisma/engines-version@5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2': {} - '@prisma/engines-version@6.14.0-25.717184b7b35ea05dfa71a3236b7af656013e1e49': {} + '@prisma/engines-version@7.1.0-6.ab635e6b9d606fa5c8fb8b1a7f909c3c3c1c98ba': {} '@prisma/engines@5.22.0': dependencies: @@ -8375,12 +8586,12 @@ snapshots: '@prisma/fetch-engine': 5.22.0 '@prisma/get-platform': 5.22.0 - '@prisma/engines@6.14.0': + '@prisma/engines@7.1.0': dependencies: - '@prisma/debug': 6.14.0 - '@prisma/engines-version': 6.14.0-25.717184b7b35ea05dfa71a3236b7af656013e1e49 - '@prisma/fetch-engine': 6.14.0 - '@prisma/get-platform': 6.14.0 + '@prisma/debug': 7.1.0 + '@prisma/engines-version': 7.1.0-6.ab635e6b9d606fa5c8fb8b1a7f909c3c3c1c98ba + '@prisma/fetch-engine': 7.1.0 + '@prisma/get-platform': 7.1.0 '@prisma/fetch-engine@5.22.0': dependencies: @@ -8388,26 +8599,38 @@ snapshots: '@prisma/engines-version': 5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2 '@prisma/get-platform': 5.22.0 - '@prisma/fetch-engine@6.14.0': + '@prisma/fetch-engine@7.1.0': dependencies: - '@prisma/debug': 6.14.0 - '@prisma/engines-version': 6.14.0-25.717184b7b35ea05dfa71a3236b7af656013e1e49 - '@prisma/get-platform': 6.14.0 + '@prisma/debug': 7.1.0 + '@prisma/engines-version': 7.1.0-6.ab635e6b9d606fa5c8fb8b1a7f909c3c3c1c98ba + '@prisma/get-platform': 7.1.0 '@prisma/get-platform@5.22.0': dependencies: '@prisma/debug': 5.22.0 - '@prisma/get-platform@6.14.0': + '@prisma/get-platform@6.8.2': dependencies: - '@prisma/debug': 6.14.0 + '@prisma/debug': 6.8.2 - '@prisma/studio-core@0.5.2(@prisma/client@6.14.0(prisma@6.14.0(typescript@5.8.3))(typescript@5.8.3))(@types/react@19.1.10)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + '@prisma/get-platform@7.1.0': dependencies: - '@prisma/client': 6.14.0(prisma@6.14.0(typescript@5.8.3))(typescript@5.8.3) + '@prisma/debug': 7.1.0 + + '@prisma/query-plan-executor@6.18.0': {} + + '@prisma/studio-core@0.5.2(@prisma/client@6.14.0(prisma@7.1.0(@types/react@19.1.10)(react-dom@19.1.2(react@19.1.2))(react@19.1.2)(typescript@5.8.3))(typescript@5.8.3))(@types/react@19.1.10)(react-dom@19.1.2(react@19.1.2))(react@19.1.2)': + dependencies: + '@prisma/client': 6.14.0(prisma@7.1.0(@types/react@19.1.10)(react-dom@19.1.2(react@19.1.2))(react@19.1.2)(typescript@5.8.3))(typescript@5.8.3) '@types/react': 19.1.10 - react: 19.1.0 - react-dom: 19.1.0(react@19.1.0) + react: 19.1.2 + react-dom: 19.1.2(react@19.1.2) + + '@prisma/studio-core@0.8.2(@types/react@19.1.10)(react-dom@19.1.2(react@19.1.2))(react@19.1.2)': + dependencies: + '@types/react': 19.1.10 + react: 19.1.2 + react-dom: 19.1.2(react@19.1.2) '@quansync/fs@0.1.5': dependencies: @@ -9269,12 +9492,12 @@ snapshots: picocolors: 1.1.1 redent: 3.0.0 - '@testing-library/react@16.3.0(@testing-library/dom@10.4.1)(@types/react-dom@19.1.7(@types/react@19.1.10))(@types/react@19.1.10)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + '@testing-library/react@16.3.0(@testing-library/dom@10.4.1)(@types/react-dom@19.1.7(@types/react@19.1.10))(@types/react@19.1.10)(react-dom@19.1.2(react@19.1.2))(react@19.1.2)': dependencies: '@babel/runtime': 7.27.6 '@testing-library/dom': 10.4.1 - react: 19.1.0 - react-dom: 19.1.0(react@19.1.0) + react: 19.1.2 + react-dom: 19.1.2(react@19.1.2) optionalDependencies: '@types/react': 19.1.10 '@types/react-dom': 19.1.7(@types/react@19.1.10) @@ -9782,6 +10005,8 @@ snapshots: asynckit@0.4.0: {} + aws-ssl-profiles@1.1.2: {} + aws4fetch@1.0.20: {} balanced-match@1.0.2: {} @@ -9859,7 +10084,7 @@ snapshots: dotenv: 16.6.1 exsolve: 1.0.7 giget: 2.0.0 - jiti: 2.5.1 + jiti: 2.6.1 ohash: 2.0.11 pathe: 2.0.3 perfect-debounce: 1.0.0 @@ -9900,6 +10125,15 @@ snapshots: check-error@2.1.1: {} + chevrotain@10.5.0: + dependencies: + '@chevrotain/cst-dts-gen': 10.5.0 + '@chevrotain/gast': 10.5.0 + '@chevrotain/types': 10.5.0 + '@chevrotain/utils': 10.5.0 + lodash: 4.17.21 + regexp-to-ast: 0.5.0 + chokidar@4.0.0: dependencies: readdirp: 4.1.2 @@ -10096,6 +10330,8 @@ snapshots: delayed-stream@1.0.0: {} + denque@2.1.0: {} + depd@1.1.2: {} depd@2.0.0: {} @@ -10161,7 +10397,7 @@ snapshots: ee-first@1.1.1: {} - effect@3.16.12: + effect@3.18.4: dependencies: '@standard-schema/spec': 1.0.0 fast-check: 3.23.2 @@ -10622,6 +10858,10 @@ snapshots: function-bind@1.1.2: {} + generate-function@2.3.1: + dependencies: + is-property: 1.0.2 + generic-pool@3.4.2: {} gensync@1.0.0-beta.2: {} @@ -10643,6 +10883,8 @@ snapshots: hasown: 2.0.2 math-intrinsics: 1.1.0 + get-port-please@3.1.2: {} + get-proto@1.0.1: dependencies: dunder-proto: 1.0.1 @@ -10737,6 +10979,8 @@ snapshots: graceful-fs@4.2.11: {} + grammex@3.1.12: {} + gzip-size@6.0.0: dependencies: duplexer: 0.1.2 @@ -10753,6 +10997,8 @@ snapshots: dependencies: function-bind: 1.1.2 + hono@4.10.6: {} + hono@4.9.4: {} hookable@5.5.3: {} @@ -10789,6 +11035,8 @@ snapshots: transitivePeerDependencies: - supports-color + http-status-codes@2.3.0: {} + https-proxy-agent@7.0.6: dependencies: agent-base: 7.1.4 @@ -10816,6 +11064,10 @@ snapshots: dependencies: safer-buffer: 2.1.2 + iconv-lite@0.7.0: + dependencies: + safer-buffer: 2.1.2 + ignore@5.3.2: {} import-fresh@3.3.1: @@ -10863,6 +11115,8 @@ snapshots: is-promise@4.0.0: {} + is-property@1.0.2: {} + is-stream@2.0.1: {} is-stream@4.0.1: {} @@ -10899,8 +11153,7 @@ snapshots: jiti@2.5.1: {} - jiti@2.6.1: - optional: true + jiti@2.6.1: {} jose@5.9.6: {} @@ -11017,6 +11270,8 @@ snapshots: lightningcss-win32-arm64-msvc: 1.30.1 lightningcss-win32-x64-msvc: 1.30.1 + lilconfig@2.1.0: {} + lines-and-columns@1.2.4: {} locate-path@5.0.0: @@ -11045,6 +11300,10 @@ snapshots: lodash.upperfirst@4.3.1: {} + lodash@4.17.21: {} + + long@5.3.2: {} + loupe@3.1.4: {} lru-cache@10.4.3: {} @@ -11059,9 +11318,11 @@ snapshots: dependencies: yallist: 4.0.0 - lucide-react@0.541.0(react@19.1.0): + lru.min@1.1.3: {} + + lucide-react@0.541.0(react@19.1.2): dependencies: - react: 19.1.0 + react: 19.1.2 lz-string@1.5.0: {} @@ -11215,19 +11476,35 @@ snapshots: mustache@4.2.0: {} + mysql2@3.15.3: + dependencies: + aws-ssl-profiles: 1.1.2 + denque: 2.1.0 + generate-function: 2.3.1 + iconv-lite: 0.7.0 + long: 5.3.2 + lru.min: 1.1.3 + named-placeholders: 1.1.4 + seq-queue: 0.0.5 + sqlstring: 2.3.3 + + named-placeholders@1.1.4: + dependencies: + lru.min: 1.1.3 + nanoid@3.3.11: {} negotiator@1.0.0: {} - next@15.4.6(@babel/core@7.28.4)(react-dom@19.1.0(react@19.1.0))(react@19.1.0): + next@15.4.6(@babel/core@7.28.4)(react-dom@19.1.2(react@19.1.2))(react@19.1.2): dependencies: '@next/env': 15.4.6 '@swc/helpers': 0.5.15 caniuse-lite: 1.0.30001735 postcss: 8.4.31 - react: 19.1.0 - react-dom: 19.1.0(react@19.1.0) - styled-jsx: 5.1.6(@babel/core@7.28.4)(react@19.1.0) + react: 19.1.2 + react-dom: 19.1.2(react@19.1.2) + styled-jsx: 5.1.6(@babel/core@7.28.4)(react@19.1.2) optionalDependencies: '@next/swc-darwin-arm64': 15.4.6 '@next/swc-darwin-x64': 15.4.6 @@ -11488,6 +11765,8 @@ snapshots: dependencies: xtend: 4.0.2 + postgres@3.4.7: {} + posthog-js@1.282.0: dependencies: '@posthog/core': 1.4.0 @@ -11524,17 +11803,30 @@ snapshots: optionalDependencies: fsevents: 2.3.3 - prisma@6.14.0(typescript@5.8.3): + prisma@7.1.0(@types/react@19.1.10)(react-dom@19.1.2(react@19.1.2))(react@19.1.2)(typescript@5.8.3): dependencies: - '@prisma/config': 6.14.0 - '@prisma/engines': 6.14.0 + '@prisma/config': 7.1.0 + '@prisma/dev': 0.15.0(typescript@5.8.3) + '@prisma/engines': 7.1.0 + '@prisma/studio-core': 0.8.2(@types/react@19.1.10)(react-dom@19.1.2(react@19.1.2))(react@19.1.2) + mysql2: 3.15.3 + postgres: 3.4.7 optionalDependencies: typescript: 5.8.3 transitivePeerDependencies: + - '@types/react' - magicast + - react + - react-dom promisepipe@3.0.0: {} + proper-lockfile@4.1.2: + dependencies: + graceful-fs: 4.2.11 + retry: 0.12.0 + signal-exit: 3.0.7 + proxy-addr@2.0.7: dependencies: forwarded: 0.2.0 @@ -11581,23 +11873,23 @@ snapshots: defu: 6.1.4 destr: 2.0.5 - react-dom@19.1.0(react@19.1.0): + react-dom@19.1.2(react@19.1.2): dependencies: - react: 19.1.0 + react: 19.1.2 scheduler: 0.26.0 - react-hot-toast@2.6.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0): + react-hot-toast@2.6.0(react-dom@19.1.2(react@19.1.2))(react@19.1.2): dependencies: csstype: 3.1.3 goober: 2.1.16(csstype@3.1.3) - react: 19.1.0 - react-dom: 19.1.0(react@19.1.0) + react: 19.1.2 + react-dom: 19.1.2(react@19.1.2) react-is@17.0.2: {} react-refresh@0.17.0: {} - react@19.1.0: {} + react@19.1.2: {} read-yaml-file@1.1.0: dependencies: @@ -11613,6 +11905,12 @@ snapshots: indent-string: 4.0.0 strip-indent: 3.0.0 + regexp-to-ast@0.5.0: {} + + remeda@2.21.3: + dependencies: + type-fest: 4.41.0 + require-directory@2.1.1: {} require-from-string@2.0.2: {} @@ -11623,6 +11921,8 @@ snapshots: resolve-pkg-maps@1.0.0: {} + retry@0.12.0: {} + retry@0.13.1: {} reusify@1.1.0: {} @@ -11770,6 +12070,8 @@ snapshots: transitivePeerDependencies: - supports-color + seq-queue@0.0.5: {} + serve-static@2.2.0: dependencies: encodeurl: 2.0.0 @@ -11913,6 +12215,8 @@ snapshots: sprintf-js@1.0.3: {} + sqlstring@2.3.3: {} + stackback@0.0.2: {} stacktracey@2.1.8: @@ -11988,10 +12292,10 @@ snapshots: strnum@2.1.1: {} - styled-jsx@5.1.6(@babel/core@7.28.4)(react@19.1.0): + styled-jsx@5.1.6(@babel/core@7.28.4)(react@19.1.2): dependencies: client-only: 0.0.1 - react: 19.1.0 + react: 19.1.2 optionalDependencies: '@babel/core': 7.28.4 @@ -12110,12 +12414,13 @@ snapshots: tree-kill@1.2.2: {} - trpc-cli@0.12.1(@orpc/server@1.12.2(ws@8.18.3))(effect@3.16.12)(zod@4.1.13): + trpc-cli@0.12.1(@orpc/server@1.12.2(ws@8.18.3))(effect@3.18.4)(valibot@1.2.0(typescript@5.9.3))(zod@4.1.13): dependencies: commander: 14.0.2 optionalDependencies: '@orpc/server': 1.12.2(ws@8.18.3) - effect: 3.16.12 + effect: 3.18.4 + valibot: 1.2.0(typescript@5.9.3) zod: 4.1.13 ts-morph@12.0.0: @@ -12185,6 +12490,8 @@ snapshots: optionalDependencies: fsevents: 2.3.3 + type-fest@4.41.0: {} + type-fest@5.3.0: dependencies: tagged-tag: 1.0.0 @@ -12275,6 +12582,15 @@ snapshots: v8-compile-cache-lib@3.0.1: {} + valibot@1.2.0(typescript@5.8.3): + optionalDependencies: + typescript: 5.8.3 + + valibot@1.2.0(typescript@5.9.3): + optionalDependencies: + typescript: 5.9.3 + optional: true + vary@1.1.2: {} vercel@46.0.2(rollup@4.53.3): @@ -12751,6 +13067,10 @@ snapshots: cookie: 1.1.1 youch-core: 0.3.3 + zeptomatch@2.0.2: + dependencies: + grammex: 3.1.12 + zod@3.22.3: {} zod@3.25.76: {} diff --git a/schema-api-routes/src/routes/schema/pull.ts b/schema-api-routes/src/routes/schema/pull.ts index c12848a..14e111d 100644 --- a/schema-api-routes/src/routes/schema/pull.ts +++ b/schema-api-routes/src/routes/schema/pull.ts @@ -13,7 +13,6 @@ app.post("/", async (c) => { datasource db { provider = "postgresql" - url = env("DATABASE_URL") }`; try {