diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8daf5396e8..caa5eca64f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -103,6 +103,14 @@ jobs: - name: Build Packages run: pnpm build + - name: patch compose file volumes + uses: mikefarah/yq@v4.44.1 + with: + cmd: yq -i 'del(.services.*.volumes)' examples/hackernews/docker-compose.yml + + - name: Start Docker + run: docker compose -f examples/hackernews/docker-compose.yml up -d --wait postgres + - name: Run Tests uses: nick-fields/retry@v3 with: @@ -263,6 +271,43 @@ jobs: failOnRequired: true debug: true + hackernews-docker-container: + runs-on: ubuntu-latest + env: + DOCKER_REGISTRY: ghcr.io/${{ github.repository }}/ + DOCKER_TAG: :${{ github.event.pull_request.head.sha }} + steps: + - name: Checkout Repository + uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4 + + - name: Setup env + uses: the-guild-org/shared-config/setup@main + with: + nodeVersion: 22 + packageManager: pnpm + + - name: Build Packages + run: pnpm build + + - name: Build Hackernews App + run: pnpm --filter=example-hackernews build + + - name: Isolate Docker Image Build Context + run: pnpm build:hackernews:docker + + - name: Build Hackernews Docker Image + run: docker compose -f .hackernews-deploy/docker-compose.yml build api + + - name: Log in to the Container registry + uses: docker/login-action@65b78e6e13532edd9afa3aa52ac7964289d1a9c1 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Push Docker Image + run: docker compose -f .hackernews-deploy/docker-compose.yml push api + # TODO: have the example and packages use singleton nestjs dependencies # but without using .pnpmfile.cjs because it causes issues with renovate: https://github.com/dotansimha/graphql-yoga/pull/2622 # nestjs-apollo-federation-compatibility: diff --git a/.gitignore b/.gitignore index 347cc5aa84..236587c794 100644 --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,4 @@ packages/graphql-yoga/src/graphiql-html.ts .tool-versions .mise.toml +.hackernews-deploy diff --git a/examples/hackernews/.env.sample b/examples/hackernews/.env.sample new file mode 100644 index 0000000000..3411cd58f4 --- /dev/null +++ b/examples/hackernews/.env.sample @@ -0,0 +1 @@ +PG_CONNECTION_STRING=postgres://postgres:postgres@localhost:5432/postgres diff --git a/examples/hackernews/.gitignore b/examples/hackernews/.gitignore index 771dd00973..4035501b39 100644 --- a/examples/hackernews/.gitignore +++ b/examples/hackernews/.gitignore @@ -1,5 +1,4 @@ node_modules # Keep environment variables out of version control .env -prisma/migrations/migration_lock.toml -prisma/*.db* +.pg-data diff --git a/examples/hackernews/Dockerfile b/examples/hackernews/Dockerfile new file mode 100644 index 0000000000..581a69a0fc --- /dev/null +++ b/examples/hackernews/Dockerfile @@ -0,0 +1,23 @@ +FROM node:22-slim + +RUN apt-get update && apt-get install -y ca-certificates + +WORKDIR /usr/src/app + +COPY ./package.json /usr/src/app +COPY ./node_modules /usr/src/app/node_modules + +COPY ./dist/src/ /usr/src/app/ + +LABEL org.opencontainers.image.title="Hackernews GraphQL Server" +LABEL org.opencontainers.image.version=$RELEASE +LABEL org.opencontainers.image.description="A Hackernews clone built with GraphQL and graphql-yoga." +LABEL org.opencontainers.image.authors="The Guild" +LABEL org.opencontainers.image.vendor="Laurin Quast" +LABEL org.opencontainers.image.url="https://github.com/dotansimha/graphql-yoga/tree/main/examples/hackernews" +LABEL org.opencontainers.image.source="https://github.com/dotansimha/graphql-yoga/tree/main/examples/hackernews" + +ENV ENVIRONMENT production +ENV RELEASE $RELEASE + +ENTRYPOINT ["node", "main.js"] diff --git a/examples/hackernews/README.md b/examples/hackernews/README.md index ec6818516e..d94dfb665b 100644 --- a/examples/hackernews/README.md +++ b/examples/hackernews/README.md @@ -3,7 +3,7 @@ ## Stack - GraphQL Yoga -- Prisma +- Drizzle ## Tutorial diff --git a/examples/hackernews/__integration-tests__/hackernews.spec.ts b/examples/hackernews/__integration-tests__/hackernews.spec.ts index 8bff6abf30..1556365850 100644 --- a/examples/hackernews/__integration-tests__/hackernews.spec.ts +++ b/examples/hackernews/__integration-tests__/hackernews.spec.ts @@ -1,57 +1,51 @@ -import path from 'node:path'; +import { resolve } from 'node:path'; +import { sql } from 'drizzle-orm'; +import { drizzle, NodePgDatabase } from 'drizzle-orm/node-postgres'; +import { migrate } from 'drizzle-orm/postgres-js/migrator'; import { createYoga, YogaServerInstance } from 'graphql-yoga'; -import { PrismaClient } from '@prisma/client'; -import { DbDrop, MigrateDev } from '@prisma/migrate'; -import type { GraphQLContext } from '../src/context'; -import { schema } from '../src/schema'; +import pg from 'pg'; +import type { GraphQLContext } from '../src/context.js'; +import { schema } from '../src/schema.js'; + +const connectionString = + process.env['PG_CONNECTION_STRING'] ?? + 'postgres://postgres:postgres@localhost:5432/postgres?currentSchema=integrationTests'; + +async function resetDatabase(db: NodePgDatabase, schema: string) { + // sql query for resetting the database + const query = sql` + DROP SCHEMA IF EXISTS ${sql.raw(schema)} CASCADE; + CREATE SCHEMA ${sql.raw(schema)}; + `; + await db.execute(query); +} describe('hackernews example integration', () => { let yoga: YogaServerInstance; + let db: NodePgDatabase; + let client: pg.Client; + let currentSchema: string; beforeAll(async () => { - const { createContext } = await import('../src/context'); - yoga = createYoga({ schema, context: createContext }); - - // migrate - await MigrateDev.new().parse([ - `--schema=${path.resolve(__dirname, '..', 'prisma', 'schema.prisma')}`, - ]); - - // seed - const client = new PrismaClient(); - await client.link.create({ - data: { - url: 'https://www.prisma.io', - description: 'Prisma replaces traditional ORMs', - }, + const url = new URL(connectionString); + const cs = url.searchParams.get('currentSchema'); + if (!cs) { + throw new Error("Must provide 'currentSchema' in the connection string"); + } + currentSchema = cs; + client = new pg.Client(connectionString); + await client.connect(); + db = drizzle(client); + await resetDatabase(db, currentSchema); + await migrate(drizzle(client), { + migrationsSchema: currentSchema, + migrationsFolder: resolve(__dirname, '../src/drizzle'), }); - await client.$disconnect(); + yoga = createYoga({ schema, context: { db } }); }); afterAll(async () => { - // drop - await DbDrop.new().parse([ - `--schema=${path.resolve(__dirname, '..', 'prisma', 'schema.prisma')}`, - '--preview-feature', // DbDrop is an experimental feature - '--force', - ]); - }); - - it('should get posts from feed', async () => { - const response = await yoga.fetch('http://yoga/graphql?query={feed{url,description}}'); - - const body = await response.json(); - expect(body).toMatchInlineSnapshot(` - { - "data": { - "feed": [ - { - "description": "Prisma replaces traditional ORMs", - "url": "https://www.prisma.io", - }, - ], - }, - } - `); + await resetDatabase(db, currentSchema); + await client.end(); }); it('should create a new post', async () => { diff --git a/examples/hackernews/__integration-tests__/prisma__migrate.d.ts b/examples/hackernews/__integration-tests__/prisma__migrate.d.ts deleted file mode 100644 index 2a4cd115c1..0000000000 --- a/examples/hackernews/__integration-tests__/prisma__migrate.d.ts +++ /dev/null @@ -1,17 +0,0 @@ -/* eslint-disable */ -declare module '@prisma/migrate' { - // https://github.com/prisma/prisma/blob/main/packages/internals/src/cli/types.ts - class Command { - public async parse(argv: string[]): Promise; - } - - // https://github.com/prisma/prisma/blob/main/packages/migrate/src/commands/DbDrop.ts - class DbDrop extends Command { - public static new(): DbDrop; - } - - // https://github.com/prisma/prisma/blob/main/packages/migrate/src/commands/MigrateDev.ts - class MigrateDev extends Command { - public static new(): DbDrop; - } -} diff --git a/examples/hackernews/docker-compose.yml b/examples/hackernews/docker-compose.yml new file mode 100644 index 0000000000..c08e3183cf --- /dev/null +++ b/examples/hackernews/docker-compose.yml @@ -0,0 +1,42 @@ +version: '3.8' +services: + postgres: + image: postgres:14.12-alpine + networks: + - 'stack' + healthcheck: + test: ['CMD-SHELL', 'pg_isready -d $${POSTGRES_DB} -U $${POSTGRES_USER}'] + interval: 10s + timeout: 5s + retries: 5 + environment: + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + POSTGRES_DB: postgres + PGDATA: /var/lib/postgresql/data + volumes: + - ./.pg-data:/var/lib/postgresql/data + ports: + - '5432:5432' + + api: + image: ${DOCKER_REGISTRY}yoga-hackernews${DOCKER_TAG} + build: + context: . + dockerfile: Dockerfile + networks: + - 'stack' + depends_on: + - postgres + environment: + PG_CONNECTION_STRING: postgres://postgres:postgres@postgres:5432/postgres + ports: + - '4000:4000' + healthcheck: + test: ['CMD', 'curl', '-f', 'http://localhost:3000/health'] + interval: 10s + timeout: 5s + retries: 5 + +networks: + stack: {} diff --git a/examples/hackernews/drizzle.config.ts b/examples/hackernews/drizzle.config.ts new file mode 100644 index 0000000000..2b572e5a38 --- /dev/null +++ b/examples/hackernews/drizzle.config.ts @@ -0,0 +1,18 @@ +import { defineConfig } from 'drizzle-kit'; + +export default defineConfig({ + dialect: 'postgresql', + out: './src/drizzle', + schema: './src/drizzle/schema.ts', + dbCredentials: { + host: process.env.DB_HOST!, + port: Number(process.env.DB_PORT!), + user: process.env.DB_USERNAME!, + password: process.env.DB_PASSWORD!, + database: process.env.DB_NAME!, + }, + // Print all statements + verbose: true, + // Always ask for confirmation + strict: true, +}); diff --git a/examples/hackernews/package.json b/examples/hackernews/package.json index 8398a66379..f5758eb953 100644 --- a/examples/hackernews/package.json +++ b/examples/hackernews/package.json @@ -1,29 +1,39 @@ { "name": "example-hackernews", "version": "4.3.0", - "description": "", - "author": "", - "license": "ISC", + "type": "module", + "description": "Hackernews API Clone using GraphQL Yoga.", + "author": "Laurin Quast", + "license": "MIT", "private": true, + "files": [ + "Dockerfile", + "dist/src", + "docker-compose.yml", + "package.json", + "README.md" + ], "keywords": [], "scripts": { + "build": "tsc", "check": "tsc --pretty --noEmit", - "dev": "cross-env NODE_ENV=development ts-node-dev --exit-child --respawn src/main.ts", - "migrate": "prisma migrate dev", - "postinstall": "prisma generate", + "dev": "cross-env NODE_ENV=development node --loader=ts-node/esm --env-file .env --watch src/main.ts", + "migrate": "pnpm drizzle-kit generate", + "postbuild": "npx cpy 'src/**/*.{json,sql}' ./dist/src --parents", "start": "ts-node src/main.ts" }, "dependencies": { + "drizzle-orm": "0.31.2", "graphql": "16.6.0", - "graphql-yoga": "workspace:*" + "graphql-yoga": "workspace:*", + "pg": "8.12.0" }, "devDependencies": { - "@prisma/client": "5.13.0", - "@prisma/internals": "5.13.0", - "@prisma/migrate": "5.13.0", "@types/node": "18.16.16", + "@types/pg": "8.11.6", + "cpy-cli": "5.0.0", "cross-env": "7.0.3", - "prisma": "5.13.0", + "drizzle-kit": "0.22.7", "ts-node": "10.9.1", "ts-node-dev": "2.0.0", "typescript": "5.1.6" diff --git a/examples/hackernews/prisma/migrations/20220223111842_init/migration.sql b/examples/hackernews/prisma/migrations/20220223111842_init/migration.sql deleted file mode 100644 index 065f69384f..0000000000 --- a/examples/hackernews/prisma/migrations/20220223111842_init/migration.sql +++ /dev/null @@ -1,7 +0,0 @@ --- CreateTable -CREATE TABLE "Link" ( - "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, - "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - "description" TEXT NOT NULL, - "url" TEXT NOT NULL -); diff --git a/examples/hackernews/prisma/migrations/20220223114846_comments/migration.sql b/examples/hackernews/prisma/migrations/20220223114846_comments/migration.sql deleted file mode 100644 index ff142114dd..0000000000 --- a/examples/hackernews/prisma/migrations/20220223114846_comments/migration.sql +++ /dev/null @@ -1,8 +0,0 @@ --- CreateTable -CREATE TABLE "Comment" ( - "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, - "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - "body" TEXT NOT NULL, - "linkId" INTEGER, - CONSTRAINT "Comment_linkId_fkey" FOREIGN KEY ("linkId") REFERENCES "Link" ("id") ON DELETE SET NULL ON UPDATE CASCADE -); diff --git a/examples/hackernews/prisma/schema.prisma b/examples/hackernews/prisma/schema.prisma deleted file mode 100644 index 63b1b4a8f9..0000000000 --- a/examples/hackernews/prisma/schema.prisma +++ /dev/null @@ -1,27 +0,0 @@ -// This is your Prisma schema file, -// learn more about it in the docs: https://pris.ly/d/prisma-schema - -datasource db { - provider = "sqlite" - url = "file:./dev.db" -} - -generator client { - provider = "prisma-client-js" -} - -model Link { - id Int @id @default(autoincrement()) - createdAt DateTime @default(now()) - description String - url String - comments Comment[] -} - -model Comment { - id Int @id @default(autoincrement()) - createdAt DateTime @default(now()) - body String - link Link? @relation(fields: [linkId], references: [id]) - linkId Int? -} diff --git a/examples/hackernews/src/context.ts b/examples/hackernews/src/context.ts index fa53f606f4..acb6e2e19f 100644 --- a/examples/hackernews/src/context.ts +++ b/examples/hackernews/src/context.ts @@ -1,13 +1,5 @@ -import { PrismaClient } from '@prisma/client'; - -const prisma = new PrismaClient(); +import type { NodePgDatabase } from 'drizzle-orm/node-postgres'; export type GraphQLContext = { - prisma: PrismaClient; + db: NodePgDatabase; }; - -export async function createContext(): Promise { - return { - prisma, - }; -} diff --git a/examples/hackernews/src/drizzle/0000_lush_butterfly.sql b/examples/hackernews/src/drizzle/0000_lush_butterfly.sql new file mode 100644 index 0000000000..915434b176 --- /dev/null +++ b/examples/hackernews/src/drizzle/0000_lush_butterfly.sql @@ -0,0 +1,6 @@ +CREATE TABLE IF NOT EXISTS "links" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "created_at" timestamp with time zone DEFAULT now() NOT NULL, + "description" varchar NOT NULL, + "url" varchar NOT NULL +); diff --git a/examples/hackernews/src/drizzle/0001_gray_umar.sql b/examples/hackernews/src/drizzle/0001_gray_umar.sql new file mode 100644 index 0000000000..12c07bcd41 --- /dev/null +++ b/examples/hackernews/src/drizzle/0001_gray_umar.sql @@ -0,0 +1,12 @@ +CREATE TABLE IF NOT EXISTS "comments" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "created_at" timestamp with time zone DEFAULT now() NOT NULL, + "body" varchar NOT NULL, + "link_id" uuid NOT NULL +); +--> statement-breakpoint +DO $$ BEGIN + ALTER TABLE "comments" ADD CONSTRAINT "comments_link_id_links_id_fk" FOREIGN KEY ("link_id") REFERENCES "public"."links"("id") ON DELETE cascade ON UPDATE no action; +EXCEPTION + WHEN duplicate_object THEN null; +END $$; diff --git a/examples/hackernews/src/drizzle/meta/0000_snapshot.json b/examples/hackernews/src/drizzle/meta/0000_snapshot.json new file mode 100644 index 0000000000..4715638e19 --- /dev/null +++ b/examples/hackernews/src/drizzle/meta/0000_snapshot.json @@ -0,0 +1,51 @@ +{ + "id": "4a2f31ab-3566-4c36-a32e-d1c7ecc40f27", + "prevId": "00000000-0000-0000-0000-000000000000", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.links": { + "name": "links", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "description": { + "name": "description", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "url": { + "name": "url", + "type": "varchar", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + } + }, + "enums": {}, + "schemas": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} diff --git a/examples/hackernews/src/drizzle/meta/0001_snapshot.json b/examples/hackernews/src/drizzle/meta/0001_snapshot.json new file mode 100644 index 0000000000..4484fc673d --- /dev/null +++ b/examples/hackernews/src/drizzle/meta/0001_snapshot.json @@ -0,0 +1,97 @@ +{ + "id": "8bfd7301-aec1-44b0-bb60-23a2c82c2279", + "prevId": "4a2f31ab-3566-4c36-a32e-d1c7ecc40f27", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.comments": { + "name": "comments", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "body": { + "name": "body", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "link_id": { + "name": "link_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "comments_link_id_links_id_fk": { + "name": "comments_link_id_links_id_fk", + "tableFrom": "comments", + "tableTo": "links", + "columnsFrom": ["link_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.links": { + "name": "links", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "description": { + "name": "description", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "url": { + "name": "url", + "type": "varchar", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + } + }, + "enums": {}, + "schemas": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} diff --git a/examples/hackernews/src/drizzle/meta/_journal.json b/examples/hackernews/src/drizzle/meta/_journal.json new file mode 100644 index 0000000000..eeb9b3761e --- /dev/null +++ b/examples/hackernews/src/drizzle/meta/_journal.json @@ -0,0 +1,20 @@ +{ + "version": "7", + "dialect": "postgresql", + "entries": [ + { + "idx": 0, + "version": "7", + "when": 1718965925210, + "tag": "0000_lush_butterfly", + "breakpoints": true + }, + { + "idx": 1, + "version": "7", + "when": 1718965930437, + "tag": "0001_gray_umar", + "breakpoints": true + } + ] +} diff --git a/examples/hackernews/src/drizzle/schema.ts b/examples/hackernews/src/drizzle/schema.ts new file mode 100644 index 0000000000..bcbef84dc7 --- /dev/null +++ b/examples/hackernews/src/drizzle/schema.ts @@ -0,0 +1,18 @@ +import { pgTable, timestamp, uuid, varchar } from 'drizzle-orm/pg-core'; + +export const links = pgTable('links', { + id: uuid('id').primaryKey().defaultRandom(), + createdAt: timestamp('created_at', { mode: 'string', withTimezone: true }).notNull().defaultNow(), + description: varchar('description').notNull(), + url: varchar('url').notNull(), +}); + +export const comments = pgTable('comments', { + id: uuid('id').primaryKey().defaultRandom(), + createdAt: timestamp('created_at', { mode: 'string', withTimezone: true }).notNull().defaultNow(), + body: varchar('body').notNull(), + linkId: uuid('link_id') + .notNull() + .references(() => links.id, { onDelete: 'cascade' }) + .notNull(), +}); diff --git a/examples/hackernews/src/main.ts b/examples/hackernews/src/main.ts index 2ffb72dd69..46533c9cdb 100644 --- a/examples/hackernews/src/main.ts +++ b/examples/hackernews/src/main.ts @@ -1,10 +1,16 @@ import { createServer } from 'node:http'; +import { drizzle } from 'drizzle-orm/node-postgres'; import { createYoga } from 'graphql-yoga'; -import { createContext } from './context'; -import { schema } from './schema'; +import pg from 'pg'; +import { migrateDatabase } from './migrate-database.js'; +import { schema } from './schema.js'; -function main() { - const yoga = createYoga({ schema, context: createContext }); +async function main() { + const client = new pg.Client(process.env['PG_CONNECTION_STRING']); + await client.connect(); + await migrateDatabase(client); + const db = drizzle(client); + const yoga = createYoga({ schema, context: { db } }); const server = createServer(yoga); server.listen(4000, () => { console.info(`Server is running on http://localhost:4000${yoga.graphqlEndpoint}`); diff --git a/examples/hackernews/src/migrate-database.ts b/examples/hackernews/src/migrate-database.ts new file mode 100644 index 0000000000..f4b8820b50 --- /dev/null +++ b/examples/hackernews/src/migrate-database.ts @@ -0,0 +1,11 @@ +import { resolve } from 'node:path'; +import { drizzle } from 'drizzle-orm/node-postgres'; +import { migrate } from 'drizzle-orm/postgres-js/migrator'; +import type pg from 'pg'; + +// get current directory in esm +const __dirname = new URL('.', import.meta.url).pathname; + +export async function migrateDatabase(client: pg.Client) { + await migrate(drizzle(client), { migrationsFolder: resolve(__dirname, 'drizzle') }); +} diff --git a/examples/hackernews/src/schema.ts b/examples/hackernews/src/schema.ts index 4bb08fcb5e..d6d7222a65 100644 --- a/examples/hackernews/src/schema.ts +++ b/examples/hackernews/src/schema.ts @@ -1,6 +1,14 @@ +import { eq, ilike, or } from 'drizzle-orm'; import { createGraphQLError, createSchema } from 'graphql-yoga'; -import { Prisma, type Link } from '@prisma/client'; -import type { GraphQLContext } from './context'; +import pg from 'pg'; +import type { GraphQLContext } from './context.js'; +import { comments, links } from './drizzle/schema.js'; + +function isUUIDV4(uuid: string) { + return /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/.test(uuid); +} + +type Link = typeof links.$inferSelect; const typeDefinitions = /* GraphQL */ ` type Link { @@ -27,13 +35,6 @@ const typeDefinitions = /* GraphQL */ ` } `; -const parseIntSafe = (value: string): number | null => { - if (/^(\d+)$/.test(value)) { - return parseInt(value, 10); - } - return null; -}; - const applyTakeConstraints = (params: { min: number; max: number; value: number }) => { if (params.value < params.min || params.value > params.max) { throw createGraphQLError( @@ -47,35 +48,28 @@ const resolvers = { Query: { hello: () => `Hello World!`, feed: async ( - parent: unknown, + _parent: unknown, args: { filterNeedle?: string; skip?: number; take?: number }, context: GraphQLContext, ) => { - const where = args.filterNeedle - ? { - OR: [ - { description: { contains: args.filterNeedle } }, - { url: { contains: args.filterNeedle } }, - ], - } - : {}; - const take = applyTakeConstraints({ min: 1, max: 50, value: args.take ?? 30, }); - return context.prisma.link.findMany({ - where, - skip: args.skip, - take, - }); + return context.db + .select() + .from(links) + .where( + args.filterNeedle + ? or(ilike(links.description, args.filterNeedle), ilike(links.url, args.filterNeedle)) + : undefined, + ) + .limit(take); }, comment: async (parent: unknown, args: { id: string }, context: GraphQLContext) => { - return context.prisma.comment.findUnique({ - where: { id: parseInt(args.id) }, - }); + return context.db.select().from(comments).where(eq(comments.id, args.id)); }, }, Link: { @@ -83,48 +77,48 @@ const resolvers = { description: (parent: Link) => parent.description, url: (parent: Link) => parent.url, comments: (parent: Link, _: unknown, context: GraphQLContext) => { - return context.prisma.comment.findMany({ - where: { - linkId: parent.id, - }, - }); + return context.db.select().from(comments).where(eq(comments.id, parent.id)); }, }, Mutation: { postLink: async ( - parent: unknown, + _parent: unknown, args: { description: string; url: string }, context: GraphQLContext, ) => { - const newLink = await context.prisma.link.create({ - data: { - url: args.url, + const newLink = await context.db + .insert(links) + .values({ description: args.description, - }, - }); - return newLink; + url: args.url, + }) + .returning(); + + return newLink[0]; }, postCommentOnLink: async ( - parent: unknown, + _parent: unknown, args: { linkId: string; body: string }, context: GraphQLContext, ) => { - const linkId = parseIntSafe(args.linkId); - if (linkId === null) { + if (isUUIDV4(args.linkId) === false) { return Promise.reject( createGraphQLError(`Cannot post common on non-existing link with id '${args.linkId}'.`), ); } - const comment = await context.prisma.comment - .create({ - data: { - body: args.body, - linkId, - }, + const comment = await context.db + .insert(comments) + .values({ + body: args.body, + linkId: args.linkId, }) + .returning() .catch((err: unknown) => { - if (err instanceof Prisma.PrismaClientKnownRequestError && err.code === 'P2003') { + if ( + err instanceof pg.DatabaseError && + err.constraint === 'comments_link_id_links_id_fk' + ) { return Promise.reject( createGraphQLError( `Cannot post common on non-existing link with id '${args.linkId}'.`, @@ -133,7 +127,7 @@ const resolvers = { } return Promise.reject(err); }); - return comment; + return comment[0]; }, }, }; diff --git a/examples/hackernews/src/script.ts b/examples/hackernews/src/script.ts index 0582b5440f..0d8ebcd7c3 100644 --- a/examples/hackernews/src/script.ts +++ b/examples/hackernews/src/script.ts @@ -1,24 +1,31 @@ // 1 -import { PrismaClient } from '@prisma/client'; +import { drizzle } from 'drizzle-orm/node-postgres'; +import { migrate } from 'drizzle-orm/postgres-js/migrator'; +import pg from 'pg'; +import { links } from './drizzle/schema.js'; // 2 -const prisma = new PrismaClient(); +const client = new pg.Client(process.env['PG_CONNECTION_STRING']); -// 3 async function main() { - await prisma.link.create({ - data: { - description: 'Fullstack tutorial for GraphQL', - url: 'www.howtographql.com', - }, + // 3 + await client.connect(); + const db = drizzle(client); + await migrate(db, { migrationsFolder: './drizzle' }); + + // 4 + await db.insert(links).values({ + description: 'Fullstack tutorial for GraphQL', + url: 'www.howtographql.com', }); - const allLinks = await prisma.link.findMany(); + + const allLinks = await db.select().from(links); console.log(allLinks); } -// 4 +// 5 main() - // 5 + // 6 .finally(async () => { - await prisma.$disconnect(); + await client.end(); }); diff --git a/examples/hackernews/tsconfig.json b/examples/hackernews/tsconfig.json index d79d11f62d..e455c0b7d7 100644 --- a/examples/hackernews/tsconfig.json +++ b/examples/hackernews/tsconfig.json @@ -1,101 +1,12 @@ { "compilerOptions": { /* Visit https://aka.ms/tsconfig.json to read more about this file */ - - /* Projects */ - // "incremental": true, /* Enable incremental compilation */ - // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ - // "tsBuildInfoFile": "./", /* Specify the folder for .tsbuildinfo incremental compilation files. */ - // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects */ - // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ - // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ - - /* Language and Environment */ "target": "es2016" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */, - // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ - // "jsx": "preserve", /* Specify what JSX code is generated. */ - // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */ - // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ - // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h' */ - // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ - // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using `jsx: react-jsx*`.` */ - // "reactNamespace": "", /* Specify the object invoked for `createElement`. This only applies when targeting `react` JSX emit. */ - // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ - // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ - - /* Modules */ - "module": "commonjs" /* Specify what module code is generated. */, - // "rootDir": "./", /* Specify the root folder within your source files. */ - // "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */ - // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ - // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ - // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ - // "typeRoots": [], /* Specify multiple folders that act like `./node_modules/@types`. */ - // "types": [], /* Specify type package names to be included without being referenced in a source file. */ - // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ - // "resolveJsonModule": true, /* Enable importing .json files */ - // "noResolve": true, /* Disallow `import`s, `require`s or ``s from expanding the number of files TypeScript should add to a project. */ - - /* JavaScript Support */ - // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the `checkJS` option to get errors from these files. */ - // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ - // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from `node_modules`. Only applicable with `allowJs`. */ - - /* Emit */ - // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ - // "declarationMap": true, /* Create sourcemaps for d.ts files. */ - // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ - // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ - // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If `declaration` is true, also designates a file that bundles all .d.ts output. */ - // "outDir": "./", /* Specify an output folder for all emitted files. */ - // "removeComments": true, /* Disable emitting comments. */ - // "noEmit": true, /* Disable emitting files from a compilation. */ - // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ - // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types */ - // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ - // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ - // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ - // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ - // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ - // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ - // "newLine": "crlf", /* Set the newline character for emitting files. */ - // "stripInternal": true, /* Disable emitting declarations that have `@internal` in their JSDoc comments. */ - // "noEmitHelpers": true, /* Disable generating custom helper functions like `__extends` in compiled output. */ - // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ - // "preserveConstEnums": true, /* Disable erasing `const enum` declarations in generated code. */ - // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ - // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ - - /* Interop Constraints */ - // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ - // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ + "module": "NodeNext" /* Specify what module code is generated. */, "esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */, - // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ "forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */, - - /* Type Checking */ "strict": true /* Enable all strict type-checking options. */, - // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied `any` type.. */ - // "strictNullChecks": true, /* When type checking, take into account `null` and `undefined`. */ - // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ - // "strictBindCallApply": true, /* Check that the arguments for `bind`, `call`, and `apply` methods match the original function. */ - // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ - // "noImplicitThis": true, /* Enable error reporting when `this` is given the type `any`. */ - // "useUnknownInCatchVariables": true, /* Type catch clause variables as 'unknown' instead of 'any'. */ - // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ - // "noUnusedLocals": true, /* Enable error reporting when a local variables aren't read. */ - // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read */ - // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ - // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ - // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ - // "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */ - // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ - // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type */ - // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ - // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ - - /* Completeness */ - // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ - "skipLibCheck": true /* Skip type checking all .d.ts files. */ + "skipLibCheck": true /* Skip type checking all .d.ts files. */, + "outDir": "./dist" /* Redirect output structure to the directory. */ } } diff --git a/examples/sveltekit/__integration-tests__/sveltekit.spec.ts b/examples/sveltekit/__integration-tests__/sveltekit.spec.ts index 54b069840a..99fc4e7ec9 100644 --- a/examples/sveltekit/__integration-tests__/sveltekit.spec.ts +++ b/examples/sveltekit/__integration-tests__/sveltekit.spec.ts @@ -76,7 +76,8 @@ describe('SvelteKit integration', () => { } catch (error) {} // Build svelteKit - execSync('pnpm --filter example-sveltekit build'); + console.log('run "pnpm --filter example-sveltekit build" (output is piped into process)'); + execSync('pnpm --filter example-sveltekit build', { stdio: 'inherit' }); // Start sveltekit sveltekitProcess = spawn('pnpm', ['--filter', 'example-sveltekit', 'preview']); diff --git a/package.json b/package.json index 5ab4eabee5..976971ad5a 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,7 @@ "scripts": { "build": "pnpm --filter=@graphql-yoga/graphiql run build && pnpm --filter=@graphql-yoga/render-graphiql run build && pnpm --filter=graphql-yoga run generate-graphiql-html && bob build", "build-website": "pnpm build && cd website && pnpm build", + "build:hackernews:docker": "pnpm --filter=example-hackernews deploy --prod .hackernews-deploy", "changeset": "changeset", "check": "pnpm -r run check", "lint": "eslint --ignore-path .eslintignore --ext ts,js,tsx,jsx .", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index cdada4aa0a..575e349dc2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -725,31 +725,34 @@ importers: examples/hackernews: dependencies: + drizzle-orm: + specifier: 0.31.2 + version: 0.31.2(@cloudflare/workers-types@4.20230518.0)(@opentelemetry/api@1.4.1)(@types/pg@8.11.6)(@types/react@18.2.55)(pg@8.12.0)(react@18.2.0) graphql: specifier: 16.8.1 version: 16.8.1 graphql-yoga: specifier: workspace:* version: link:../../packages/graphql-yoga/dist + pg: + specifier: 8.12.0 + version: 8.12.0 devDependencies: - '@prisma/client': - specifier: 5.13.0 - version: 5.13.0(prisma@5.13.0) - '@prisma/internals': - specifier: 5.13.0 - version: 5.13.0 - '@prisma/migrate': - specifier: 5.13.0 - version: 5.13.0(@prisma/generator-helper@5.13.0)(@prisma/internals@5.13.0) '@types/node': specifier: 18.16.16 version: 18.16.16 + '@types/pg': + specifier: 8.11.6 + version: 8.11.6 + cpy-cli: + specifier: 5.0.0 + version: 5.0.0 cross-env: specifier: 7.0.3 version: 7.0.3 - prisma: - specifier: 5.13.0 - version: 5.13.0 + drizzle-kit: + specifier: 0.22.7 + version: 0.22.7 ts-node: specifier: 10.9.1 version: 10.9.1(@swc/core@1.3.37)(@types/node@18.16.16)(typescript@5.1.6) @@ -6780,48 +6783,6 @@ packages: peerDependencies: graphql: 16.8.1 - '@prisma/client@5.13.0': - resolution: {integrity: sha512-uYdfpPncbZ/syJyiYBwGZS8Gt1PTNoErNYMuqHDa2r30rNSFtgTA/LXsSk55R7pdRTMi5pHkeP9B14K6nHmwkg==} - engines: {node: '>=16.13'} - peerDependencies: - prisma: '*' - peerDependenciesMeta: - prisma: - optional: true - - '@prisma/debug@5.13.0': - resolution: {integrity: sha512-699iqlEvzyCj9ETrXhs8o8wQc/eVW+FigSsHpiskSFydhjVuwTJEfj/nIYqTaWFYuxiWQRfm3r01meuW97SZaQ==} - - '@prisma/engines-version@5.13.0-23.b9a39a7ee606c28e3455d0fd60e78c3ba82b1a2b': - resolution: {integrity: sha512-AyUuhahTINGn8auyqYdmxsN+qn0mw3eg+uhkp8zwknXYIqoT3bChG4RqNY/nfDkPvzWAPBa9mrDyBeOnWSgO6A==} - - '@prisma/engines@5.13.0': - resolution: {integrity: sha512-hIFLm4H1boj6CBZx55P4xKby9jgDTeDG0Jj3iXtwaaHmlD5JmiDkZhh8+DYWkTGchu+rRF36AVROLnk0oaqhHw==} - - '@prisma/fetch-engine@5.13.0': - resolution: {integrity: sha512-Yh4W+t6YKyqgcSEB3odBXt7QyVSm0OQlBSldQF2SNXtmOgMX8D7PF/fvH6E6qBCpjB/yeJLy/FfwfFijoHI6sA==} - - '@prisma/generator-helper@5.13.0': - resolution: {integrity: sha512-i+53beJ0dxkDrkHdsXxmeMf+eVhyhOIpL0SdBga8vwe0qHPrAIJ/lpuT/Hj0y5awTmq40qiUEmhXwCEuM/Z17w==} - - '@prisma/get-platform@5.13.0': - resolution: {integrity: sha512-B/WrQwYTzwr7qCLifQzYOmQhZcFmIFhR81xC45gweInSUn2hTEbfKUPd2keAog+y5WI5xLAFNJ3wkXplvSVkSw==} - - '@prisma/internals@5.13.0': - resolution: {integrity: sha512-OPMzS+IBPzCLT4s+IfGUbOhGFY51CFbokIFMZuoSeLKWE8UvDlitiXZ3OlVqDPUc0AlH++ysQHzDISHbZD+ZUg==} - - '@prisma/migrate@5.13.0': - resolution: {integrity: sha512-cFIYfX92yVyOGp/tYGGMShXVptlER23Yg05HoGdzjPBzy6jDQEBBs9VhlRA9I39I8zGFxNFOql+5pIGxktkGyw==} - peerDependencies: - '@prisma/generator-helper': '*' - '@prisma/internals': '*' - - '@prisma/prisma-schema-wasm@5.13.0-23.b9a39a7ee606c28e3455d0fd60e78c3ba82b1a2b': - resolution: {integrity: sha512-+IhHvuE1wKlyOpJgwAhGop1oqEt+1eixrCeikBIshRhdX6LwjmtRxVxVMlP5nS1yyughmpfkysIW4jZTa+Zjuw==} - - '@prisma/schema-files-loader@5.13.0': - resolution: {integrity: sha512-6sVMoqobkWKsmzb98LfLiIt/aFRucWfkzSUBsqk7sc+h99xjynJt6aKtM2SSkyndFdWpRU0OiCHfQ9UlYUEJIw==} - '@protobufjs/aspromise@1.1.2': resolution: {integrity: sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==} @@ -7906,6 +7867,9 @@ packages: '@types/parse-json@4.0.0': resolution: {integrity: sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==} + '@types/pg@8.11.6': + resolution: {integrity: sha512-/2WmmBXHLsfRqzfHW7BNZ8SbYzE8OSk7i3WjFYvfgRHj7S1xj+16Je5fUKv3lVdVzk/zn9TXOqf+avFCFIE0yQ==} + '@types/prettier@2.7.1': resolution: {integrity: sha512-ri0UmynRRvZiiUJdiz38MmIblKK+oH30MztdBVR95dv/Ubw6neWSb8u1XpRb72L4qsZOhz+L+z9JD40SJmfWow==} @@ -9639,6 +9603,15 @@ packages: resolution: {integrity: sha512-3scnzFj/94eb7y4wyXRWwvzLFaQp87yyfTnChIjlfYrVqp5lVO3E2hIJMeQIltUT0K2ZAB3An1qXcBmwGyvuwA==} engines: {node: '>=10'} + cpy-cli@5.0.0: + resolution: {integrity: sha512-fb+DZYbL9KHc0BC4NYqGRrDIJZPXUmjjtqdw4XRRg8iV8dIfghUX/WiL+q4/B/KFTy3sK6jsbUhBaz0/Hxg7IQ==} + engines: {node: '>=16'} + hasBin: true + + cpy@10.1.0: + resolution: {integrity: sha512-VC2Gs20JcTyeQob6UViBLnyP0bYHkBh6EiKzot9vi2DmeGlFT9Wd7VG3NBrkNx/jYvFBeyDOMMHdHQhbtKLgHQ==} + engines: {node: '>=16'} + cpy@9.0.1: resolution: {integrity: sha512-D9U0DR5FjTCN3oMTcFGktanHnAG5l020yvOCR1zKILmAyPP7I/9pl6NFgRbDcmSENtbK1sQLBz1p9HIOlroiNg==} engines: {node: ^12.20.0 || ^14.17.0 || >=16.0.0} @@ -10200,6 +10173,93 @@ packages: resolution: {integrity: sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g==} engines: {node: '>=10'} + drizzle-kit@0.22.7: + resolution: {integrity: sha512-9THPCb2l1GPt7wxhws9LvTR0YG565ZlVgTuqGMwjs590Kch1pXu4GyjEArVijSF5m0OBj3qgdeKmuJXhKXgWFw==} + hasBin: true + + drizzle-orm@0.31.2: + resolution: {integrity: sha512-QnenevbnnAzmbNzQwbhklvIYrDE8YER8K7kSrAWQSV1YvFCdSQPzj+jzqRdTSsV2cDqSpQ0NXGyL1G9I43LDLg==} + peerDependencies: + '@aws-sdk/client-rds-data': '>=3' + '@cloudflare/workers-types': '>=3' + '@electric-sql/pglite': '>=0.1.1' + '@libsql/client': '*' + '@neondatabase/serverless': '>=0.1' + '@op-engineering/op-sqlite': '>=2' + '@opentelemetry/api': ^1.4.1 + '@planetscale/database': '>=1' + '@tidbcloud/serverless': '*' + '@types/better-sqlite3': '*' + '@types/pg': '*' + '@types/react': 18.2.55 + '@types/sql.js': '*' + '@vercel/postgres': '>=0.8.0' + '@xata.io/client': '*' + better-sqlite3: '>=7' + bun-types: '*' + expo-sqlite: '>=13.2.0' + knex: '*' + kysely: '*' + mysql2: '>=2' + pg: '>=8' + postgres: '>=3' + react: '>=18' + sql.js: '>=1' + sqlite3: '>=5' + peerDependenciesMeta: + '@aws-sdk/client-rds-data': + optional: true + '@cloudflare/workers-types': + optional: true + '@electric-sql/pglite': + optional: true + '@libsql/client': + optional: true + '@neondatabase/serverless': + optional: true + '@op-engineering/op-sqlite': + optional: true + '@opentelemetry/api': + optional: true + '@planetscale/database': + optional: true + '@tidbcloud/serverless': + optional: true + '@types/better-sqlite3': + optional: true + '@types/pg': + optional: true + '@types/react': + optional: true + '@types/sql.js': + optional: true + '@vercel/postgres': + optional: true + '@xata.io/client': + optional: true + better-sqlite3: + optional: true + bun-types: + optional: true + expo-sqlite: + optional: true + knex: + optional: true + kysely: + optional: true + mysql2: + optional: true + pg: + optional: true + postgres: + optional: true + react: + optional: true + sql.js: + optional: true + sqlite3: + optional: true + dset@3.1.2: resolution: {integrity: sha512-g/M9sqy3oHe477Ar4voQxWtaPIFw1jTdKZuomOjhCcBx9nHUNn0pu6NopuFFrTh/TRZIKEj+76vLWFu9BNKk+Q==} engines: {node: '>=4'} @@ -10341,6 +10401,11 @@ packages: es6-promisify@6.1.1: resolution: {integrity: sha512-HBL8I3mIki5C1Cc9QjKUenHtnG0A5/xA8Q/AllRcfiwl2CZFXGK7ddBiCoRwAix4i2KxcQfjtIVcrVbB3vbmwg==} + esbuild-register@3.5.0: + resolution: {integrity: sha512-+4G/XmakeBAsvJuDugJvtyF1x+XJT4FMocynNpxrvEBViirpfUn2PgNpCHedfWhF4WokNsO/OvMKrmJOIJsI5A==} + peerDependencies: + esbuild: '>=0.12 <1' + esbuild@0.17.19: resolution: {integrity: sha512-XQ0jAPFkK/u3LcVRcvVHQcTIqD6E2H1fvZMA5dQPSOWb3suUbWbfbRf94pjc0bNzRYLfIrDRQXr7X+LHIm5oHw==} engines: {node: '>=12'} @@ -11317,6 +11382,10 @@ packages: resolution: {integrity: sha512-8krCNHXvlCgHDpegPzleMq07yMYTO2sXKASmZmquEYWEmCx6J5UTRbp5RwMJkTJGtcQ44YpiUYUiN0b9mzy8Bw==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + globby@13.2.2: + resolution: {integrity: sha512-Y1zNGV+pzQdh7H39l9zgB4PJqjRNqydvdYCDG4HFXM4XuvSaQQlEc91IU1yALL8gUTDomgBAfz3XJdmUS+oo0w==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + globrex@0.1.2: resolution: {integrity: sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==} @@ -12711,6 +12780,10 @@ packages: resolution: {integrity: sha512-ojtSU++zLJ3jQG9bAYjg94w+/DOJtRyD7nPaerMFrBhmdVmiV5/exYH5t4uHga4G/95nT6hr1OJoKIFbYbrW5w==} engines: {node: '>=12.20'} + junk@4.0.1: + resolution: {integrity: sha512-Qush0uP+G8ZScpGMZvHUiRfI0YBWuB3gVBYlI0v0vvOJt5FLicco+IkP0a50LqTTQhmts/m6tP5SWE+USyIvcQ==} + engines: {node: '>=12.20'} + just-diff-apply@5.5.0: resolution: {integrity: sha512-OYTthRfSh55WOItVqwpefPtNt2VdKsq5AnAK6apdtR6yCH8pr0CmSr710J0Mf+WdQy7K/OzMy7K2MgAfdQURDw==} @@ -13309,6 +13382,10 @@ packages: memoize-one@6.0.0: resolution: {integrity: sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==} + meow@12.1.1: + resolution: {integrity: sha512-BhXM0Au22RwUneMPwSCnyhTOizdWoIEPU9sp0Aqa1PnDMR5Wv2FGXYDjuzJEIX+Eo2Rb8xuYe5jrnm5QowQFkw==} + engines: {node: '>=16.10'} + meow@6.1.1: resolution: {integrity: sha512-3YffViIt2QWgTy6Pale5QpopX/IvU3LPL03jOTqp6pGj3VjesdO/U8CuHMKpnQr4shCNCM5fd5XFFvIIl6JBHg==} engines: {node: '>=8'} @@ -14188,6 +14265,9 @@ packages: obliterator@2.0.4: resolution: {integrity: sha512-lgHwxlxV1qIg1Eap7LgIeoBWIMFibOjbrYPIPJZcI1mmGAI2m3lNYpK12Y+GBdPQ0U1hRwSord7GIaawz962qQ==} + obuf@1.1.2: + resolution: {integrity: sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==} + oidc-token-hash@5.0.1: resolution: {integrity: sha512-EvoOtz6FIEBzE+9q253HsLCVRiK/0doEJ2HCvvqMQb3dHZrP3WlJKYtJ55CRTw4jmYomzH4wkPuCj/I3ZvpKxQ==} engines: {node: ^10.13.0 || >=12.0.0} @@ -14354,6 +14434,10 @@ packages: resolution: {integrity: sha512-VFqfGDHlx87K66yZrNdI4YGtD70IRyd+zSvgks6mzHPRNkoKy+9EKP4SFC77/vTTQYmRmti7dvqC+m5jBrBAcg==} engines: {node: '>=12'} + p-map@6.0.0: + resolution: {integrity: sha512-T8BatKGY+k5rU+Q/GTYgrEf2r4xRMevAN5mtXc2aPc4rS1j3s+vWTaO2Wag94neXuCAUAs8cxBL9EeB5EA6diw==} + engines: {node: '>=16'} + p-reduce@3.0.0: resolution: {integrity: sha512-xsrIUgI0Kn6iyDYm9StOpOeK29XM1aboGji26+QEortiFST1hGZaUQOLhtEbqHErPpGW/aSz6allwK2qcptp0Q==} engines: {node: '>=12'} @@ -14543,6 +14627,48 @@ packages: periscopic@3.1.0: resolution: {integrity: sha512-vKiQ8RRtkl9P+r/+oefh25C3fhybptkHKCZSPlcXiJux2tJF55GnEj3BVn4A5gKfq9NWWXXrxkHBwVPUfH0opw==} + pg-cloudflare@1.1.1: + resolution: {integrity: sha512-xWPagP/4B6BgFO+EKz3JONXv3YDgvkbVrGw2mTo3D6tVDQRh1e7cqVGvyR3BE+eQgAvx1XhW/iEASj4/jCWl3Q==} + + pg-connection-string@2.6.4: + resolution: {integrity: sha512-v+Z7W/0EO707aNMaAEfiGnGL9sxxumwLl2fJvCQtMn9Fxsg+lPpPkdcyBSv/KFgpGdYkMfn+EI1Or2EHjpgLCA==} + + pg-int8@1.0.1: + resolution: {integrity: sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==} + engines: {node: '>=4.0.0'} + + pg-numeric@1.0.2: + resolution: {integrity: sha512-BM/Thnrw5jm2kKLE5uJkXqqExRUY/toLHda65XgFTBTFYZyopbKjBe29Ii3RbkvlsMoFwD+tHeGaCjjv0gHlyw==} + engines: {node: '>=4'} + + pg-pool@3.6.2: + resolution: {integrity: sha512-Htjbg8BlwXqSBQ9V8Vjtc+vzf/6fVUuak/3/XXKA9oxZprwW3IMDQTGHP+KDmVL7rtd+R1QjbnCFPuTHm3G4hg==} + peerDependencies: + pg: '>=8.0' + + pg-protocol@1.6.1: + resolution: {integrity: sha512-jPIlvgoD63hrEuihvIg+tJhoGjUsLPn6poJY9N5CnlPd91c2T18T/9zBtLxZSb1EhYxBRoZJtzScCaWlYLtktg==} + + pg-types@2.2.0: + resolution: {integrity: sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==} + engines: {node: '>=4'} + + pg-types@4.0.2: + resolution: {integrity: sha512-cRL3JpS3lKMGsKaWndugWQoLOCoP+Cic8oseVcbr0qhPzYD5DWXK+RZ9LY9wxRf7RQia4SCwQlXk0q6FCPrVng==} + engines: {node: '>=10'} + + pg@8.12.0: + resolution: {integrity: sha512-A+LHUSnwnxrnL/tZ+OLfqR1SxLN3c/pgDztZ47Rpbsd4jUytsTtwQo/TLPRzPJMp/1pbhYVhH9cuSZLAajNfjQ==} + engines: {node: '>= 8.0.0'} + peerDependencies: + pg-native: '>=3.0.1' + peerDependenciesMeta: + pg-native: + optional: true + + pgpass@1.0.5: + resolution: {integrity: sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==} + picocolors@1.0.0: resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==} @@ -14831,6 +14957,41 @@ packages: resolution: {integrity: sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==} engines: {node: ^10 || ^12 || >=14} + postgres-array@2.0.0: + resolution: {integrity: sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==} + engines: {node: '>=4'} + + postgres-array@3.0.2: + resolution: {integrity: sha512-6faShkdFugNQCLwucjPcY5ARoW1SlbnrZjmGl0IrrqewpvxvhSLHimCVzqeuULCbG0fQv7Dtk1yDbG3xv7Veog==} + engines: {node: '>=12'} + + postgres-bytea@1.0.0: + resolution: {integrity: sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==} + engines: {node: '>=0.10.0'} + + postgres-bytea@3.0.0: + resolution: {integrity: sha512-CNd4jim9RFPkObHSjVHlVrxoVQXz7quwNFpz7RY1okNNme49+sVyiTvTRobiLV548Hx/hb1BG+iE7h9493WzFw==} + engines: {node: '>= 6'} + + postgres-date@1.0.7: + resolution: {integrity: sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==} + engines: {node: '>=0.10.0'} + + postgres-date@2.1.0: + resolution: {integrity: sha512-K7Juri8gtgXVcDfZttFKVmhglp7epKb1K4pgrkLxehjqkrgPhfG6OO8LHLkfaqkbpjNRnra018XwAr1yQFWGcA==} + engines: {node: '>=12'} + + postgres-interval@1.2.0: + resolution: {integrity: sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==} + engines: {node: '>=0.10.0'} + + postgres-interval@3.0.0: + resolution: {integrity: sha512-BSNDnbyZCXSxgA+1f5UU2GmwhoI0aU5yMxRGO8CdFEcY2BQF9xm/7MqKnYoM1nJDk8nONNWDk9WeSmePFhQdlw==} + engines: {node: '>=12'} + + postgres-range@1.1.4: + resolution: {integrity: sha512-i/hbxIE9803Alj/6ytL7UHQxRvZkI9O4Sy+J3HGc4F4oo/2eQAjTSNJ0bfxyse3bH0nuVesCk+3IRLaMtG3H6w==} + postinstall-postinstall@2.1.0: resolution: {integrity: sha512-7hQX6ZlZXIoRiWNrbMQaLzUUfH+sSx39u8EJ9HYuDc1kLo9IXKWjM5RSquZN1ad5GnH8CGFM78fsAAQi3OKEEQ==} @@ -14928,11 +15089,6 @@ packages: printable-characters@1.0.42: resolution: {integrity: sha512-dKp+C4iXWK4vVYZmYSd0KBH5F/h1HoZRsbJ82AVKRO3PEo8L4lBS/vLwhVtpwwuYcoIsVY+1JYKR268yn480uQ==} - prisma@5.13.0: - resolution: {integrity: sha512-kGtcJaElNRAdAGsCNykFSZ7dBKpL14Cbs+VaQ8cECxQlRPDjBlMHNFYeYt0SKovAVy2Y65JXQwB3A5+zIQwnTg==} - engines: {node: '>=16.13'} - hasBin: true - proc-log@3.0.0: resolution: {integrity: sha512-++Vn7NS4Xf9NacaU9Xq3URUuqZETPsf8L4j5/ckhaRYsfPeRyzGw+iDjFhV/Jr3uNmTvvddEJFWh5R1gRgUH8A==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} @@ -20777,7 +20933,7 @@ snapshots: debug: 4.3.4(supports-color@9.2.3) espree: 9.6.0 globals: 13.19.0 - ignore: 5.2.4 + ignore: 5.3.1 import-fresh: 3.3.0 js-yaml: 4.1.0 minimatch: 3.1.2 @@ -21250,7 +21406,7 @@ snapshots: '@graphql-tools/executor-http@1.0.9(@types/node@18.16.16)(graphql@16.8.1)': dependencies: - '@graphql-tools/utils': 10.2.2(graphql@16.8.1) + '@graphql-tools/utils': 10.1.1(graphql@16.8.1) '@repeaterjs/repeater': 3.0.4 '@whatwg-node/fetch': 0.9.18 extract-files: 11.0.0 @@ -23450,7 +23606,7 @@ snapshots: execa: 6.1.0 filter-obj: 5.1.0 find-up: 6.3.0 - get-tsconfig: 4.7.0 + get-tsconfig: 4.7.5 glob: 8.1.0 is-builtin-module: 3.2.1 is-path-inside: 4.0.0 @@ -23487,7 +23643,7 @@ snapshots: execa: 6.1.0 filter-obj: 5.1.0 find-up: 6.3.0 - get-tsconfig: 4.7.0 + get-tsconfig: 4.7.5 glob: 8.1.0 is-builtin-module: 3.2.1 is-path-inside: 4.0.0 @@ -24112,62 +24268,6 @@ snapshots: dependencies: graphql: 16.8.1 - '@prisma/client@5.13.0(prisma@5.13.0)': - optionalDependencies: - prisma: 5.13.0 - - '@prisma/debug@5.13.0': {} - - '@prisma/engines-version@5.13.0-23.b9a39a7ee606c28e3455d0fd60e78c3ba82b1a2b': {} - - '@prisma/engines@5.13.0': - dependencies: - '@prisma/debug': 5.13.0 - '@prisma/engines-version': 5.13.0-23.b9a39a7ee606c28e3455d0fd60e78c3ba82b1a2b - '@prisma/fetch-engine': 5.13.0 - '@prisma/get-platform': 5.13.0 - - '@prisma/fetch-engine@5.13.0': - dependencies: - '@prisma/debug': 5.13.0 - '@prisma/engines-version': 5.13.0-23.b9a39a7ee606c28e3455d0fd60e78c3ba82b1a2b - '@prisma/get-platform': 5.13.0 - - '@prisma/generator-helper@5.13.0': - dependencies: - '@prisma/debug': 5.13.0 - - '@prisma/get-platform@5.13.0': - dependencies: - '@prisma/debug': 5.13.0 - - '@prisma/internals@5.13.0': - dependencies: - '@prisma/debug': 5.13.0 - '@prisma/engines': 5.13.0 - '@prisma/fetch-engine': 5.13.0 - '@prisma/generator-helper': 5.13.0 - '@prisma/get-platform': 5.13.0 - '@prisma/prisma-schema-wasm': 5.13.0-23.b9a39a7ee606c28e3455d0fd60e78c3ba82b1a2b - '@prisma/schema-files-loader': 5.13.0 - arg: 5.0.2 - prompts: 2.4.2 - - '@prisma/migrate@5.13.0(@prisma/generator-helper@5.13.0)(@prisma/internals@5.13.0)': - dependencies: - '@prisma/debug': 5.13.0 - '@prisma/engines-version': 5.13.0-23.b9a39a7ee606c28e3455d0fd60e78c3ba82b1a2b - '@prisma/generator-helper': 5.13.0 - '@prisma/get-platform': 5.13.0 - '@prisma/internals': 5.13.0 - prompts: 2.4.2 - - '@prisma/prisma-schema-wasm@5.13.0-23.b9a39a7ee606c28e3455d0fd60e78c3ba82b1a2b': {} - - '@prisma/schema-files-loader@5.13.0': - dependencies: - fs-extra: 11.1.1 - '@protobufjs/aspromise@1.1.2': {} '@protobufjs/base64@1.1.2': {} @@ -25502,6 +25602,12 @@ snapshots: '@types/parse-json@4.0.0': {} + '@types/pg@8.11.6': + dependencies: + '@types/node': 18.16.16 + pg-protocol: 1.6.1 + pg-types: 4.0.2 + '@types/prettier@2.7.1': {} '@types/prettier@2.7.2': {} @@ -25611,7 +25717,7 @@ snapshots: debug: 4.3.4(supports-color@9.2.3) eslint: 8.44.0 graphemer: 1.4.0 - ignore: 5.2.4 + ignore: 5.3.1 natural-compare-lite: 1.4.0 semver: 7.5.4 tsutils: 3.21.0(typescript@5.1.6) @@ -27654,6 +27760,22 @@ snapshots: nested-error-stacks: 2.1.1 p-event: 4.2.0 + cpy-cli@5.0.0: + dependencies: + cpy: 10.1.0 + meow: 12.1.1 + + cpy@10.1.0: + dependencies: + arrify: 3.0.0 + cp-file: 10.0.0 + globby: 13.2.2 + junk: 4.0.1 + micromatch: 4.0.5 + nested-error-stacks: 2.1.1 + p-filter: 3.0.0 + p-map: 6.0.0 + cpy@9.0.1: dependencies: arrify: 3.0.0 @@ -28237,6 +28359,23 @@ snapshots: dotenv@8.6.0: {} + drizzle-kit@0.22.7: + dependencies: + '@esbuild-kit/esm-loader': 2.5.5 + esbuild: 0.19.12 + esbuild-register: 3.5.0(esbuild@0.19.12) + transitivePeerDependencies: + - supports-color + + drizzle-orm@0.31.2(@cloudflare/workers-types@4.20230518.0)(@opentelemetry/api@1.4.1)(@types/pg@8.11.6)(@types/react@18.2.55)(pg@8.12.0)(react@18.2.0): + optionalDependencies: + '@cloudflare/workers-types': 4.20230518.0 + '@opentelemetry/api': 1.4.1 + '@types/pg': 8.11.6 + '@types/react': 18.2.55 + pg: 8.12.0 + react: 18.2.0 + dset@3.1.2: {} duplexer@0.1.2: {} @@ -28406,6 +28545,13 @@ snapshots: es6-promisify@6.1.1: {} + esbuild-register@3.5.0(esbuild@0.19.12): + dependencies: + debug: 4.3.4(supports-color@9.2.3) + esbuild: 0.19.12 + transitivePeerDependencies: + - supports-color + esbuild@0.17.19: optionalDependencies: '@esbuild/android-arm': 0.17.19 @@ -28794,7 +28940,7 @@ snapshots: builtins: 5.0.1 eslint: 8.44.0 eslint-plugin-es-x: 7.1.0(eslint@8.44.0) - ignore: 5.2.4 + ignore: 5.3.1 is-core-module: 2.13.1 minimatch: 3.1.2 resolve: 1.22.8 @@ -29988,7 +30134,15 @@ snapshots: dependencies: dir-glob: 3.0.1 fast-glob: 3.3.2 - ignore: 5.2.4 + ignore: 5.3.1 + merge2: 1.4.1 + slash: 4.0.0 + + globby@13.2.2: + dependencies: + dir-glob: 3.0.1 + fast-glob: 3.3.2 + ignore: 5.3.1 merge2: 1.4.1 slash: 4.0.0 @@ -31858,6 +32012,8 @@ snapshots: junk@4.0.0: {} + junk@4.0.1: {} + just-diff-apply@5.5.0: {} just-diff@6.0.2: {} @@ -32720,6 +32876,8 @@ snapshots: memoize-one@6.0.0: {} + meow@12.1.1: {} + meow@6.1.1: dependencies: '@types/minimist': 1.2.2 @@ -34157,6 +34315,8 @@ snapshots: obliterator@2.0.4: {} + obuf@1.1.2: {} + oidc-token-hash@5.0.1: {} omit.js@2.0.2: {} @@ -34346,6 +34506,8 @@ snapshots: dependencies: aggregate-error: 4.0.1 + p-map@6.0.0: {} + p-reduce@3.0.0: {} p-retry@5.1.1: @@ -34555,6 +34717,53 @@ snapshots: estree-walker: 3.0.3 is-reference: 3.0.1 + pg-cloudflare@1.1.1: + optional: true + + pg-connection-string@2.6.4: {} + + pg-int8@1.0.1: {} + + pg-numeric@1.0.2: {} + + pg-pool@3.6.2(pg@8.12.0): + dependencies: + pg: 8.12.0 + + pg-protocol@1.6.1: {} + + pg-types@2.2.0: + dependencies: + pg-int8: 1.0.1 + postgres-array: 2.0.0 + postgres-bytea: 1.0.0 + postgres-date: 1.0.7 + postgres-interval: 1.2.0 + + pg-types@4.0.2: + dependencies: + pg-int8: 1.0.1 + pg-numeric: 1.0.2 + postgres-array: 3.0.2 + postgres-bytea: 3.0.0 + postgres-date: 2.1.0 + postgres-interval: 3.0.0 + postgres-range: 1.1.4 + + pg@8.12.0: + dependencies: + pg-connection-string: 2.6.4 + pg-pool: 3.6.2(pg@8.12.0) + pg-protocol: 1.6.1 + pg-types: 2.2.0 + pgpass: 1.0.5 + optionalDependencies: + pg-cloudflare: 1.1.1 + + pgpass@1.0.5: + dependencies: + split2: 4.2.0 + picocolors@1.0.0: {} picomatch@2.3.1: {} @@ -34859,6 +35068,28 @@ snapshots: picocolors: 1.0.0 source-map-js: 1.2.0 + postgres-array@2.0.0: {} + + postgres-array@3.0.2: {} + + postgres-bytea@1.0.0: {} + + postgres-bytea@3.0.0: + dependencies: + obuf: 1.1.2 + + postgres-date@1.0.7: {} + + postgres-date@2.1.0: {} + + postgres-interval@1.2.0: + dependencies: + xtend: 4.0.2 + + postgres-interval@3.0.0: {} + + postgres-range@1.1.4: {} + postinstall-postinstall@2.1.0: {} preact-render-to-string@5.2.5(preact@10.13.2): @@ -34965,10 +35196,6 @@ snapshots: printable-characters@1.0.42: {} - prisma@5.13.0: - dependencies: - '@prisma/engines': 5.13.0 - proc-log@3.0.0: {} proc-log@4.2.0: {} @@ -37451,7 +37678,7 @@ snapshots: debug: 4.3.4(supports-color@9.2.3) fault: 2.0.1 glob: 8.1.0 - ignore: 5.2.4 + ignore: 5.3.1 is-buffer: 2.0.5 is-empty: 1.2.0 is-plain-obj: 4.1.0