From 1ce729651dcf9dd5327ae9d26d88ab13d1b3eaa5 Mon Sep 17 00:00:00 2001 From: Hong Minhee Date: Sat, 20 Jun 2026 14:34:14 +0900 Subject: [PATCH 01/10] Add models and server packages, expand graphql - New @drfed/models package with Drizzle ORM schema defining accounts, instances, and instance_members tables (PostgreSQL), with support for both PGlite (local file-based) and PostgreSQL backends, plus database/transaction type definitions and an initial migration - New @drfed/drfed package: main server CLI using srvx and GraphQL Yoga, with @optique-based argument parsing for database backend selection (--pglite-data-path or --postgres-url), listen address (--listen), and migration control (--no-migrate) - Expanded @drfed/graphql with a Pothos SchemaBuilder integrated with the Drizzle plugin for type-safe GraphQL schema generation, and a createYogaServer() factory function wiring it all together - Added .oxfmtrc.json formatter config and .zed/settings.json with per-language format-on-save via oxfmt - Updated mise.toml and pnpm workspace to include the new packages --- .gitignore | 2 + .oxfmtrc.json | 10 + .zed/settings.json | 141 +++ mise.toml | 45 + packages/drfed/bin/drfed-server.mjs | 19 + packages/drfed/package.json | 69 ++ packages/drfed/src/index.ts | 51 + packages/drfed/src/parser.ts | 99 ++ packages/drfed/src/program.ts | 30 + packages/graphql/dist/index.mjs | 1 - packages/graphql/package.json | 19 +- packages/graphql/src/builder.ts | 64 ++ packages/graphql/src/index.ts | 27 + packages/graphql/src/instance.ts | 31 + packages/graphql/src/schema.ts | 30 + packages/models/drizzle.config.ts | 21 + .../drizzle/20260620011017_init/migration.sql | 24 + .../drizzle/20260620011017_init/snapshot.json | 246 +++++ packages/models/package.json | 66 ++ packages/models/src/db.ts | 48 + packages/models/src/index.ts | 19 + packages/models/src/migrate.ts | 169 +++ packages/models/src/relations.ts | 45 + packages/models/src/schema.ts | 67 ++ pnpm-lock.yaml | 972 +++++++++++++++++- pnpm-workspace.yaml | 5 + 26 files changed, 2312 insertions(+), 8 deletions(-) create mode 100644 .oxfmtrc.json create mode 100644 .zed/settings.json create mode 100644 packages/drfed/bin/drfed-server.mjs create mode 100644 packages/drfed/package.json create mode 100644 packages/drfed/src/index.ts create mode 100644 packages/drfed/src/parser.ts create mode 100644 packages/drfed/src/program.ts delete mode 100644 packages/graphql/dist/index.mjs create mode 100644 packages/graphql/src/builder.ts create mode 100644 packages/graphql/src/instance.ts create mode 100644 packages/graphql/src/schema.ts create mode 100644 packages/models/drizzle.config.ts create mode 100644 packages/models/drizzle/20260620011017_init/migration.sql create mode 100644 packages/models/drizzle/20260620011017_init/snapshot.json create mode 100644 packages/models/package.json create mode 100644 packages/models/src/db.ts create mode 100644 packages/models/src/index.ts create mode 100644 packages/models/src/migrate.ts create mode 100644 packages/models/src/relations.ts create mode 100644 packages/models/src/schema.ts diff --git a/.gitignore b/.gitignore index c2658d7..e0a742d 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ +.pgdata/ node_modules/ +packages/*/dist/ diff --git a/.oxfmtrc.json b/.oxfmtrc.json new file mode 100644 index 0000000..fdaf92b --- /dev/null +++ b/.oxfmtrc.json @@ -0,0 +1,10 @@ +{ + "printWidth": 80, + "sortImports": true, + "sortPackageJson": false, + "ignorePatterns": [ + "*.md", + "**/*.md", + "**/dist/*.{cjs,d.cts,d.mts,d.ts,js,mjs}" + ] +} diff --git a/.zed/settings.json b/.zed/settings.json new file mode 100644 index 0000000..91c31e2 --- /dev/null +++ b/.zed/settings.json @@ -0,0 +1,141 @@ +{ + "show_wrap_guides": true, + "wrap_guides": [80], + "lsp": { + "oxfmt": { + "initialization_options": { + "settings": { + "fmt.configPath": null + } + } + } + }, + "languages": { + "CSS": { + "format_on_save": "on", + "prettier": { + "allowed": false + }, + "formatter": [ + { + "language_server": { + "name": "oxfmt" + } + } + ] + }, + "GraphQL": { + "format_on_save": "on", + "prettier": { + "allowed": false + }, + "formatter": [ + { + "language_server": { + "name": "oxfmt" + } + } + ] + }, + "HTML": { + "format_on_save": "on", + "prettier": { + "allowed": false + }, + "formatter": [ + { + "language_server": { + "name": "oxfmt" + } + } + ] + }, + "JavaScript": { + "format_on_save": "on", + "prettier": { + "allowed": false + }, + "formatter": [ + { + "language_server": { + "name": "oxfmt" + } + } + ] + }, + "JSON": { + "format_on_save": "on", + "prettier": { + "allowed": false + }, + "formatter": [ + { + "language_server": { + "name": "oxfmt" + } + } + ] + }, + "JSONC": { + "format_on_save": "on", + "prettier": { + "allowed": false + }, + "formatter": [ + { + "language_server": { + "name": "oxfmt" + } + } + ] + }, + "TypeScript": { + "format_on_save": "on", + "prettier": { + "allowed": false + }, + "formatter": [ + { + "language_server": { + "name": "oxfmt" + } + } + ] + }, + "TSX": { + "format_on_save": "on", + "prettier": { + "allowed": false + }, + "formatter": [ + { + "language_server": { + "name": "oxfmt" + } + } + ] + }, + "Markdown": { + "format_on_save": "on", + "formatter": { + "external": { + "command": "hongdown", + "arguments": ["-"] + } + } + }, + "YAML": { + "format_on_save": "on", + "prettier": { + "allowed": false + }, + "formatter": [ + { + "language_server": { + "name": "oxfmt" + } + } + ] + } + } +} diff --git a/mise.toml b/mise.toml index 24fb2ae..4a046dc 100644 --- a/mise.toml +++ b/mise.toml @@ -1,6 +1,9 @@ [tools] "aqua:dahlia/hongdown" = "0.4.3" +"aqua:oxc-project/oxc/oxfmt" = "1.70.0" +"github:nushell/nushell" = "latest" node = "26" +"npm:pglite-cli" = "latest" pnpm = "11" [deps.pnpm] @@ -13,6 +16,10 @@ postinstall = ["mise deps", "mise generate git-pre-commit --task check --write"] description = "Check all" depends = ["check:*"] +[tasks."check:fmt"] +description = "Check formatting" +run = "oxfmt --check" + [tasks."check:fmt:docs"] description = "Check Markdown formatting" run = "hongdown --check" @@ -24,6 +31,7 @@ run = "mise fmt --check" [tasks.fmt] description = "Format files" depends = ["fmt:*"] +run = "oxfmt --write" [tasks."fmt:docs"] description = "Format Markdown documents" @@ -32,3 +40,40 @@ run = "hongdown --write" [tasks."fmt:mise"] description = "Format mise.toml" run = "mise fmt" + +[tasks.build] +description = "Build the project" +run = "pnpm run --recursive build" + +[tasks."generate:migrate"] +description = "Generate database migration files" +usage = """ +flag "--name " help="Migration file name" +flag "--custom" help="Prepare empty migration file for custom SQL" +""" +dir = "packages/models" +run = """ +#!/usr/bin/env nu +let name = (if "usage_name" in $env { ["--name" $env.usage_name] } else { [] }) +let custom = (if "usage_custom" in $env { ["--custom"] } else { [] }) +pnpm exec drizzle-kit generate ...$name ...$custom +""" + +[tasks.dev] +description = "Run the development server" +run = """ +#!/usr/bin/env nu +rm -rf packages/*/dist/ +job spawn { pnpm --parallel --recursive exec tsdown --watch --no-clean } +loop { + let $allGenerated = ls packages + | each { |entry| + not ($entry.name | path join dist | path exists) or ( + ls ($entry.name | path join dist) | is-empty) + } + | all { |empty| not $empty } + if $allGenerated { break } +} +cd packages/drfed/ +node --watch bin/drfed-server.mjs --pglite-data-path ../../.pgdata +""" diff --git a/packages/drfed/bin/drfed-server.mjs b/packages/drfed/bin/drfed-server.mjs new file mode 100644 index 0000000..4dc320d --- /dev/null +++ b/packages/drfed/bin/drfed-server.mjs @@ -0,0 +1,19 @@ +#!/usr/bin/env node +// DrFed: A web-based platform for developing and debugging ActivityPub apps +// Copyright (C) 2026 DrFed team +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . +import { main } from "../dist/index.mjs"; + +await main(); diff --git a/packages/drfed/package.json b/packages/drfed/package.json new file mode 100644 index 0000000..408cce7 --- /dev/null +++ b/packages/drfed/package.json @@ -0,0 +1,69 @@ +{ + "name": "@drfed/drfed", + "version": "0.1.0", + "description": "The main entrypoint program for DrFed.", + "keywords": [ + "ActivityPub", + "fediverse", + "federation", + "debugger" + ], + "author": { + "name": "DrFed team", + "url": "https://drfed.org/" + }, + "maintainers": [ + { + "name": "ChanHaeng Lee", + "email": "2chanhaeng@gmail.com", + "url": "https://chomu.dev/" + }, + { + "name": "Hong Minhee", + "email": "hong@minhee.org", + "url": "https://hongminhee.org/" + }, + { + "name": "Hyeonseo Kim", + "email": "dodok8@gmail.com", + "url": "https://hackers.pub/@gaebalgom" + }, + { + "name": "Jiwon Kwon", + "email": "me@kwonjiwon.org", + "url": "https://kwonjiwon.org/" + } + ], + "license": "AGPL-3.0-only", + "engine": { + "node": ">=26.0.0" + }, + "type": "module", + "main": "dist/index.mjs", + "types": "dist/index.d.mts", + "bin": { + "drfed-server": "bin/drfed-server.mjs" + }, + "tsdown": { + "dts": true, + "sourcemap": true + }, + "scripts": { + "build": "tsdown" + }, + "devDependencies": { + "@types/pg": "^8.20.0", + "tsdown": "catalog:", + "typescript": "catalog:" + }, + "dependencies": { + "@drfed/graphql": "workspace:*", + "@drfed/models": "workspace:*", + "@electric-sql/pglite": "^0.5.3", + "@optique/core": "^1.1.0", + "@optique/run": "^1.1.0", + "drizzle-orm": "catalog:", + "pg": "^8.21.0", + "srvx": "^0.11.16" + } +} diff --git a/packages/drfed/src/index.ts b/packages/drfed/src/index.ts new file mode 100644 index 0000000..76e874a --- /dev/null +++ b/packages/drfed/src/index.ts @@ -0,0 +1,51 @@ +// DrFed: A web-based platform for developing and debugging ActivityPub apps +// Copyright (C) 2026 DrFed team +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . +import process from "node:process"; + +import { createYogaServer } from "@drfed/graphql"; +import { migrate } from "@drfed/models"; +import { run } from "@optique/run"; +import { serve } from "srvx"; + +import metadata from "../package.json" with { type: "json" }; +import type { Options } from "./parser.ts"; +import program from "./program.ts"; + +export async function main() { + const options: Options = run(program, { + help: "option", + version: { + value: metadata.version, + option: true, + }, + showChoices: true, + showDefault: true, + }); + if (options.drizzle.migrate) { + await migrate({ credentials: options.drizzle.credentials }); + } + const yogaServer = createYogaServer(options.drizzle.db); + const server = serve({ + hostname: options.address.host, + port: options.address.port, + manual: true, + fetch: yogaServer.fetch.bind(yogaServer), + }); + process.on("SIGTERM", () => { + server.close().then(() => process.exit(0)); + }); + await server.serve(); +} diff --git a/packages/drfed/src/parser.ts b/packages/drfed/src/parser.ts new file mode 100644 index 0000000..112836a --- /dev/null +++ b/packages/drfed/src/parser.ts @@ -0,0 +1,99 @@ +// DrFed: A web-based platform for developing and debugging ActivityPub apps +// Copyright (C) 2026 DrFed team +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . +import { relations, schema } from "@drfed/models"; +import { merge, object, or } from "@optique/core/constructs"; +import { message, optionNames } from "@optique/core/message"; +import { map, withDefault } from "@optique/core/modifiers"; +import { InferValue } from "@optique/core/parser"; +import { option } from "@optique/core/primitives"; +import { socketAddress, url } from "@optique/core/valueparser"; +import { path } from "@optique/run/valueparser"; +import { drizzle as drizzlePostgres } from "drizzle-orm/node-postgres"; +import { drizzle as drizzlePglite } from "drizzle-orm/pglite"; + +const pgliteParser = map( + option( + "--pglite-data-path", + "--data-path", + "-d", + path({ type: "directory" }), + { + description: message`The path to the directory where the PGlite database files will be stored. Mutually exclusive with ${optionNames(["--postgres-url", "--database-url", "-D"])}.`, + }, + ), + (path) => ({ + db: drizzlePglite({ + schema, + relations, + connection: { dataDir: path }, + }), + credentials: { + driver: "pglite" as const, + url: path, + }, + }), +); + +const postgresParser = map( + option( + "--postgres-url", + "--database-url", + "-D", + url({ allowedProtocols: ["postgres:", "postgresql:"] }), + { + description: message`The URL of the PostgreSQL database to connect to. Mutually exclusive with ${optionNames(["--pglite-data-path", "--data-path", "-d"])}.`, + }, + ), + (url) => ({ + db: drizzlePostgres({ + schema, + relations, + connection: { + connectionString: url.href, + }, + }), + credentials: { + url: url.href, + }, + }), +); + +export const parser = object({ + address: withDefault( + option("--listen", "-l", socketAddress({ requirePort: true }), { + description: message`The address to listen on.`, + }), + { + host: "localhost", + port: 8888, + }, + ), + drizzle: merge( + or(pgliteParser, postgresParser), + object({ + migrate: map( + option("--no-migrate", "-M", { + description: message`Disable automatic database migrations.`, + }), + (m) => !m, + ), + }), + ), +}); + +export type Options = InferValue; + +export default parser; diff --git a/packages/drfed/src/program.ts b/packages/drfed/src/program.ts new file mode 100644 index 0000000..064a9d2 --- /dev/null +++ b/packages/drfed/src/program.ts @@ -0,0 +1,30 @@ +// DrFed: A web-based platform for developing and debugging ActivityPub apps +// Copyright (C) 2026 DrFed team +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . +import { message } from "@optique/core/message"; +import type { InferValue } from "@optique/core/parser"; +import type { Program } from "@optique/core/program"; + +import parser from "./parser.ts"; + +const program: Program<"sync", InferValue> = { + parser, + metadata: { + name: "drfed-server", + description: message`Run a DrFed server.`, + }, +}; + +export default program; diff --git a/packages/graphql/dist/index.mjs b/packages/graphql/dist/index.mjs deleted file mode 100644 index cb0ff5c..0000000 --- a/packages/graphql/dist/index.mjs +++ /dev/null @@ -1 +0,0 @@ -export {}; diff --git a/packages/graphql/package.json b/packages/graphql/package.json index 09d1fb2..2205b58 100644 --- a/packages/graphql/package.json +++ b/packages/graphql/package.json @@ -35,12 +35,29 @@ } ], "license": "AGPL-3.0-only", + "engine": { + "node": ">=26.0.0" + }, "type": "module", "main": "dist/index.mjs", + "types": "dist/index.d.mts", + "tsdown": { + "dts": true, + "sourcemap": true + }, "scripts": { "build": "tsdown" }, "devDependencies": { - "tsdown": "catalog:" + "tsdown": "catalog:", + "typescript": "catalog:" + }, + "dependencies": { + "@drfed/models": "workspace:*", + "@pothos/core": "^4.13.0", + "@pothos/plugin-drizzle": "^0.17.4", + "drizzle-orm": "catalog:", + "graphql": "^16.14.2", + "graphql-yoga": "^5.21.2" } } diff --git a/packages/graphql/src/builder.ts b/packages/graphql/src/builder.ts new file mode 100644 index 0000000..df1ffb2 --- /dev/null +++ b/packages/graphql/src/builder.ts @@ -0,0 +1,64 @@ +// DrFed: A web-based platform for developing and debugging ActivityPub apps +// Copyright (C) 2026 DrFed team +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . +import { type Database, relations } from "@drfed/models"; +import SchemaBuilder from "@pothos/core"; +import DrizzlePlugin from "@pothos/plugin-drizzle"; +import { getTableConfig } from "drizzle-orm/pg-core"; + +/** + * The context data for the GraphQL server, which includes the incoming request + * object and any additional information needed for processing GraphQL queries + * and mutations. + */ +export interface ServerContext { + /** + * The incoming HTTP request. + */ + readonly request: Request; + + /** + * The database instance. + */ + readonly db: Database; +} + +/** + * The user-related context data for the GraphQL server, which include every + * field from the {@link ServerContext}. + */ +export interface UserContext extends ServerContext {} + +export interface PothosTypes { + DefaultFieldNullability: false; + Context: UserContext; +} + +/** + * The GraphQL schema builder. + */ +export const builder = new SchemaBuilder({ + plugins: [DrizzlePlugin], + defaultFieldNullability: false, + drizzle: { + client(ctx) { + return ctx.db; + }, + getTableConfig, + relations, + }, +}); + +export default builder; diff --git a/packages/graphql/src/index.ts b/packages/graphql/src/index.ts index c7f8f2f..398d529 100644 --- a/packages/graphql/src/index.ts +++ b/packages/graphql/src/index.ts @@ -13,3 +13,30 @@ // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . +import type { Database } from "@drfed/models"; +import { + createYoga, + useExecutionCancellation, + YogaServerInstance, +} from "graphql-yoga"; + +import type { ServerContext, UserContext } from "./builder.ts"; +import { schema } from "./schema.ts"; + +/** + * Creates a Yoga server instance with the provided schema and context. + * @param db The database instance. + * @returns A `YogaServerInstance` configured with the schema and context for + * handling GraphQL requests. + */ +export function createYogaServer( + db: Database, +): YogaServerInstance { + return createYoga({ + plugins: [useExecutionCancellation()], + schema, + async context(ctx) { + return { request: ctx.request, db }; + }, + }); +} diff --git a/packages/graphql/src/instance.ts b/packages/graphql/src/instance.ts new file mode 100644 index 0000000..b8126b6 --- /dev/null +++ b/packages/graphql/src/instance.ts @@ -0,0 +1,31 @@ +// DrFed: A web-based platform for developing and debugging ActivityPub apps +// Copyright (C) 2026 DrFed team +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . +import builder from "./builder.ts"; + +export interface Instance { + slug: string; +} + +export const InstanceRef = builder.objectRef("Instance"); + +InstanceRef.implement({ + description: "An ActivityPub server instance.", + fields(t) { + return { + slug: t.exposeString("slug"), + }; + }, +}); diff --git a/packages/graphql/src/schema.ts b/packages/graphql/src/schema.ts new file mode 100644 index 0000000..af0767a --- /dev/null +++ b/packages/graphql/src/schema.ts @@ -0,0 +1,30 @@ +// DrFed: A web-based platform for developing and debugging ActivityPub apps +// Copyright (C) 2026 DrFed team +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . +import builder from "./builder.ts"; +import { InstanceRef } from "./instance.ts"; + +builder.queryType({ + fields: (t) => ({ + instances: t.field({ + type: [InstanceRef], + resolve() { + return [{ slug: "foo" }, { slug: "bar" }]; + }, + }), + }), +}); + +export const schema = builder.toSchema(); diff --git a/packages/models/drizzle.config.ts b/packages/models/drizzle.config.ts new file mode 100644 index 0000000..f66da70 --- /dev/null +++ b/packages/models/drizzle.config.ts @@ -0,0 +1,21 @@ +// DrFed: A web-based platform for developing and debugging ActivityPub apps +// Copyright (C) 2026 DrFed team +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . +import { defineConfig } from "drizzle-kit"; + +export default defineConfig({ + dialect: "postgresql", + schema: "./src/schema.ts", +}); diff --git a/packages/models/drizzle/20260620011017_init/migration.sql b/packages/models/drizzle/20260620011017_init/migration.sql new file mode 100644 index 0000000..ed111aa --- /dev/null +++ b/packages/models/drizzle/20260620011017_init/migration.sql @@ -0,0 +1,24 @@ +CREATE TABLE "accounts" ( + "id" uuid PRIMARY KEY, + "email" varchar(255) NOT NULL UNIQUE, + "created" timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL, + CONSTRAINT "accounts_email_check" CHECK ("email" ~ '^[^@]+@[^@]+\.[^@]+$') +); +--> statement-breakpoint +CREATE TABLE "instance_members" ( + "instanceId" uuid PRIMARY KEY, + "accountId" uuid NOT NULL, + "created" timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL +); +--> statement-breakpoint +CREATE TABLE "instances" ( + "id" uuid PRIMARY KEY, + "slug" varchar(100) NOT NULL UNIQUE, + "expires" timestamp with time zone NOT NULL, + "created" timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL, + CONSTRAINT "instances_slug_check" CHECK ("slug" ~ '^[a-z0-9-]{4,100}$'), + CONSTRAINT "instances_expires_check" CHECK ("expires" < ("created" + INTERVAL '1 year')) +); +--> statement-breakpoint +ALTER TABLE "instance_members" ADD CONSTRAINT "instance_members_instanceId_instances_id_fkey" FOREIGN KEY ("instanceId") REFERENCES "instances"("id");--> statement-breakpoint +ALTER TABLE "instance_members" ADD CONSTRAINT "instance_members_accountId_accounts_id_fkey" FOREIGN KEY ("accountId") REFERENCES "accounts"("id"); \ No newline at end of file diff --git a/packages/models/drizzle/20260620011017_init/snapshot.json b/packages/models/drizzle/20260620011017_init/snapshot.json new file mode 100644 index 0000000..a65d9e3 --- /dev/null +++ b/packages/models/drizzle/20260620011017_init/snapshot.json @@ -0,0 +1,246 @@ +{ + "version": "8", + "dialect": "postgres", + "id": "e399ae79-2d5c-4f12-b5e7-8d602703fcc1", + "prevIds": ["00000000-0000-0000-0000-000000000000"], + "ddl": [ + { + "isRlsEnabled": false, + "name": "accounts", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "instance_members", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "instances", + "entityType": "tables", + "schema": "public" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "accounts" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "email", + "entityType": "columns", + "schema": "public", + "table": "accounts" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "CURRENT_TIMESTAMP", + "generated": null, + "identity": null, + "name": "created", + "entityType": "columns", + "schema": "public", + "table": "accounts" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "instanceId", + "entityType": "columns", + "schema": "public", + "table": "instance_members" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "accountId", + "entityType": "columns", + "schema": "public", + "table": "instance_members" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "CURRENT_TIMESTAMP", + "generated": null, + "identity": null, + "name": "created", + "entityType": "columns", + "schema": "public", + "table": "instance_members" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "instances" + }, + { + "type": "varchar(100)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "slug", + "entityType": "columns", + "schema": "public", + "table": "instances" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "expires", + "entityType": "columns", + "schema": "public", + "table": "instances" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "CURRENT_TIMESTAMP", + "generated": null, + "identity": null, + "name": "created", + "entityType": "columns", + "schema": "public", + "table": "instances" + }, + { + "nameExplicit": false, + "columns": ["instanceId"], + "schemaTo": "public", + "tableTo": "instances", + "columnsTo": ["id"], + "onUpdate": "NO ACTION", + "onDelete": "NO ACTION", + "name": "instance_members_instanceId_instances_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "instance_members" + }, + { + "nameExplicit": false, + "columns": ["accountId"], + "schemaTo": "public", + "tableTo": "accounts", + "columnsTo": ["id"], + "onUpdate": "NO ACTION", + "onDelete": "NO ACTION", + "name": "instance_members_accountId_accounts_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "instance_members" + }, + { + "columns": ["id"], + "nameExplicit": false, + "name": "accounts_pkey", + "schema": "public", + "table": "accounts", + "entityType": "pks" + }, + { + "columns": ["instanceId"], + "nameExplicit": false, + "name": "instance_members_pkey", + "schema": "public", + "table": "instance_members", + "entityType": "pks" + }, + { + "columns": ["id"], + "nameExplicit": false, + "name": "instances_pkey", + "schema": "public", + "table": "instances", + "entityType": "pks" + }, + { + "nameExplicit": false, + "columns": ["email"], + "nullsNotDistinct": false, + "name": "accounts_email_key", + "schema": "public", + "table": "accounts", + "entityType": "uniques" + }, + { + "nameExplicit": false, + "columns": ["slug"], + "nullsNotDistinct": false, + "name": "instances_slug_key", + "schema": "public", + "table": "instances", + "entityType": "uniques" + }, + { + "value": "\"email\" ~ '^[^@]+@[^@]+\\.[^@]+$'", + "name": "accounts_email_check", + "entityType": "checks", + "schema": "public", + "table": "accounts" + }, + { + "value": "\"slug\" ~ '^[a-z0-9-]{4,100}$'", + "name": "instances_slug_check", + "entityType": "checks", + "schema": "public", + "table": "instances" + }, + { + "value": "\"expires\" < (\"created\" + INTERVAL '1 year')", + "name": "instances_expires_check", + "entityType": "checks", + "schema": "public", + "table": "instances" + } + ], + "renames": [] +} diff --git a/packages/models/package.json b/packages/models/package.json new file mode 100644 index 0000000..e3ec59e --- /dev/null +++ b/packages/models/package.json @@ -0,0 +1,66 @@ +{ + "name": "@drfed/models", + "version": "0.1.0", + "description": "Database models for DrFed.", + "keywords": [ + "ActivityPub", + "fediverse", + "federation", + "debugger" + ], + "author": { + "name": "DrFed team", + "url": "https://drfed.org/" + }, + "maintainers": [ + { + "name": "ChanHaeng Lee", + "email": "2chanhaeng@gmail.com", + "url": "https://chomu.dev/" + }, + { + "name": "Hong Minhee", + "email": "hong@minhee.org", + "url": "https://hongminhee.org/" + }, + { + "name": "Hyeonseo Kim", + "email": "dodok8@gmail.com", + "url": "https://hackers.pub/@gaebalgom" + }, + { + "name": "Jiwon Kwon", + "email": "me@kwonjiwon.org", + "url": "https://kwonjiwon.org/" + } + ], + "license": "AGPL-3.0-only", + "engine": { + "node": ">=26.0.0" + }, + "type": "module", + "main": "dist/index.mjs", + "types": "dist/index.d.mts", + "files": [ + "dist/", + "drizzle/" + ], + "tsdown": { + "dts": true, + "sourcemap": true + }, + "scripts": { + "build": "tsdown" + }, + "devDependencies": { + "@types/pg": "^8.20.0", + "drizzle-kit": "1.0.0-beta.22", + "tsdown": "catalog:", + "typescript": "catalog:" + }, + "dependencies": { + "@electric-sql/pglite": "^0.5.3", + "drizzle-orm": "catalog:", + "pg": "^8.21.0" + } +} diff --git a/packages/models/src/db.ts b/packages/models/src/db.ts new file mode 100644 index 0000000..207748b --- /dev/null +++ b/packages/models/src/db.ts @@ -0,0 +1,48 @@ +// DrFed: A web-based platform for developing and debugging ActivityPub apps +// Copyright (C) 2026 DrFed team +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . +import { RelationsFilter as RelationsFilterImpl } from "drizzle-orm"; +import type { + PgAsyncDatabase, + PgAsyncTransaction, + PgQueryResultHKT, +} from "drizzle-orm/pg-core"; + +import { relations } from "./relations.ts"; +import * as schema from "./schema.ts"; + +/** + * A database instance. + */ +export type Database = PgAsyncDatabase< + PgQueryResultHKT, + typeof schema, + typeof relations +>; + +/** + * A transaction instance. + */ +export type Transaction = PgAsyncTransaction< + PgQueryResultHKT, + typeof schema, + typeof relations +>; + +/** + * A filter for relations. + */ +export type RelationsFilter = + RelationsFilterImpl<(typeof relations)[T], typeof relations>; diff --git a/packages/models/src/index.ts b/packages/models/src/index.ts new file mode 100644 index 0000000..c221231 --- /dev/null +++ b/packages/models/src/index.ts @@ -0,0 +1,19 @@ +// DrFed: A web-based platform for developing and debugging ActivityPub apps +// Copyright (C) 2026 DrFed team +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . +export * from "./db.ts"; +export * from "./migrate.ts"; +export { relations } from "./relations.ts"; +export * as schema from "./schema.ts"; diff --git a/packages/models/src/migrate.ts b/packages/models/src/migrate.ts new file mode 100644 index 0000000..a1b753b --- /dev/null +++ b/packages/models/src/migrate.ts @@ -0,0 +1,169 @@ +// DrFed: A web-based platform for developing and debugging ActivityPub apps +// Copyright (C) 2026 DrFed team +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . +import { existsSync } from "node:fs"; +import { dirname, join } from "node:path"; +import { fileURLToPath } from "node:url"; + +import { PGlite, type PGliteOptions } from "@electric-sql/pglite"; +import { drizzle as drizzlePostgres } from "drizzle-orm/node-postgres"; +import { migrate as migratePostgres } from "drizzle-orm/node-postgres/migrator"; +import { drizzle as drizzlePglite } from "drizzle-orm/pglite"; +import { migrate as migratePglite } from "drizzle-orm/pglite/migrator"; +import { Pool, type PoolConfig } from "pg"; + +export type MigrateCredentials = + | PostgresMigrateCredentials + | PGliteMigrateCredentials; + +export type PostgresMigrateCredentials = + | (Omit & { + readonly url: string; + readonly driver?: never; + }) + | (PoolConfig & { + readonly url?: never; + readonly driver?: never; + }); + +export type PGliteMigrateCredentials = + | { + readonly driver: "pglite"; + readonly url: string; + readonly options?: PGliteOptions; + } + | { + readonly driver: "pglite"; + readonly client: PGlite; + }; + +export interface MigrateOptions { + /** + * PostgreSQL or PGlite credentials. + */ + readonly credentials: MigrateCredentials; + + /** + * Custom migrations table/schema. Equivalent to drizzle-kit config's + * `migrations` option. + */ + readonly migrations?: { + readonly table?: string; + readonly schema?: string; + }; + + /** + * Custom migrations table. Takes precedence over `migrations.table`. + */ + readonly migrationsTable?: string; + + /** + * Custom migrations schema. Takes precedence over `migrations.schema`. + */ + readonly migrationsSchema?: string; +} + +const migrationsFolder = join( + dirname(fileURLToPath(import.meta.url)), + "..", + "drizzle", +); + +export async function migrate(options: MigrateOptions): Promise { + assertV3MigrationsFolder(migrationsFolder); + + const config = { + migrationsFolder, + migrationsTable: + options.migrationsTable ?? + options.migrations?.table ?? + "__drizzle_migrations", + migrationsSchema: + options.migrationsSchema ?? options.migrations?.schema ?? "drizzle", + }; + + if (isPGliteMigrateCredentials(options.credentials)) { + await migratePGliteDatabase(options.credentials, config); + } else { + await migratePostgresDatabase(options.credentials, config); + } +} + +function assertV3MigrationsFolder(migrationsFolder: string): void { + if (!existsSync(join(migrationsFolder, "meta", "_journal.json"))) return; + + throw new Error( + `The migrations folder format is outdated: ${migrationsFolder}. ` + + "Run `drizzle-kit up` before using migrate().", + ); +} + +function isPGliteMigrateCredentials( + credentials: MigrateCredentials, +): credentials is PGliteMigrateCredentials { + return credentials.driver === "pglite"; +} + +async function migratePGliteDatabase( + credentials: PGliteMigrateCredentials, + config: MigrationConfig, +): Promise { + const client = + "client" in credentials + ? credentials.client + : new PGlite(normalizePGliteUrl(credentials.url), credentials.options); + const shouldCloseClient = !("client" in credentials); + + try { + await client.waitReady; + await migratePglite(drizzlePglite({ client }), config); + } finally { + if (shouldCloseClient) await client.close(); + } +} + +async function migratePostgresDatabase( + credentials: PostgresMigrateCredentials, + config: MigrationConfig, +): Promise { + const pool = + "url" in credentials + ? new Pool({ + ...credentials, + connectionString: credentials.url, + max: 1, + }) + : new Pool({ + ...credentials, + max: 1, + }); + + try { + await migratePostgres(drizzlePostgres({ client: pool }), config); + } finally { + await pool.end(); + } +} + +function normalizePGliteUrl(url: string): string { + if (url.startsWith("file:")) return url.slice("file:".length); + return url; +} + +interface MigrationConfig { + readonly migrationsFolder: string; + readonly migrationsTable: string; + readonly migrationsSchema: string; +} diff --git a/packages/models/src/relations.ts b/packages/models/src/relations.ts new file mode 100644 index 0000000..73ef89e --- /dev/null +++ b/packages/models/src/relations.ts @@ -0,0 +1,45 @@ +// DrFed: A web-based platform for developing and debugging ActivityPub apps +// Copyright (C) 2026 DrFed team +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . +import { defineRelations } from "drizzle-orm"; + +import * as schema from "./schema.ts"; + +export const relations = defineRelations(schema, (r) => ({ + accounts: { + instances: r.many.instances({ + from: r.accounts.id.through(r.instanceMembers.accountId), + to: r.instances.id.through(r.instanceMembers.instanceId), + }), + }, + instances: { + members: r.many.accounts({ + from: r.instances.id.through(r.instanceMembers.instanceId), + to: r.accounts.id.through(r.instanceMembers.accountId), + }), + }, + instanceMembers: { + instance: r.one.instances({ + from: r.instanceMembers.instanceId, + to: r.instances.id, + }), + account: r.one.accounts({ + from: r.instanceMembers.accountId, + to: r.accounts.id, + }), + }, +})); + +export default relations; diff --git a/packages/models/src/schema.ts b/packages/models/src/schema.ts new file mode 100644 index 0000000..d41b705 --- /dev/null +++ b/packages/models/src/schema.ts @@ -0,0 +1,67 @@ +// DrFed: A web-based platform for developing and debugging ActivityPub apps +// Copyright (C) 2026 DrFed team +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . +import { sql } from "drizzle-orm"; +import { uuid, varchar, pgTable, check, timestamp } from "drizzle-orm/pg-core"; + +export const accounts = pgTable( + "accounts", + { + id: uuid().primaryKey(), + email: varchar({ length: 255 }).notNull().unique(), + created: timestamp({ withTimezone: true }) + .notNull() + .default(sql`CURRENT_TIMESTAMP`), + }, + (table) => [ + check( + "accounts_email_check", + sql`${table.email} ~ '^[^@]+@[^@]+\\.[^@]+$'`, + ), + ], +); + +export const instances = pgTable( + "instances", + { + id: uuid().primaryKey(), + slug: varchar({ length: 100 }).notNull().unique(), + expires: timestamp({ withTimezone: true }).notNull(), + created: timestamp({ withTimezone: true }) + .notNull() + .default(sql`CURRENT_TIMESTAMP`), + }, + (table) => [ + check("instances_slug_check", sql`${table.slug} ~ '^[a-z0-9-]{4,100}$'`), + check( + "instances_expires_check", + sql`${table.expires} < (${table.created} + INTERVAL '1 year')`, + ), + ], +); + +export const instanceMembers = pgTable("instance_members", { + instanceId: uuid() + .notNull() + .primaryKey() + .references(() => instances.id), + accountId: uuid() + .notNull() + .primaryKey() + .references(() => accounts.id), + created: timestamp({ withTimezone: true }) + .notNull() + .default(sql`CURRENT_TIMESTAMP`), +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 07aa6a4..9777f7e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -6,17 +6,107 @@ settings: catalogs: default: + drizzle-orm: + specifier: 1.0.0-beta.22 + version: 1.0.0-beta.22 tsdown: specifier: ^0.22.3 version: 0.22.3 + typescript: + specifier: ^6.0.3 + version: 6.0.3 importers: + packages/drfed: + dependencies: + '@drfed/graphql': + specifier: workspace:* + version: link:../graphql + '@drfed/models': + specifier: workspace:* + version: link:../models + '@electric-sql/pglite': + specifier: ^0.5.3 + version: 0.5.3 + '@optique/core': + specifier: ^1.1.0 + version: 1.1.0 + '@optique/run': + specifier: ^1.1.0 + version: 1.1.0 + drizzle-orm: + specifier: 'catalog:' + version: 1.0.0-beta.22(@electric-sql/pglite@0.5.3)(@types/pg@8.20.0)(pg@8.21.0) + pg: + specifier: ^8.21.0 + version: 8.21.0 + srvx: + specifier: ^0.11.16 + version: 0.11.16 + devDependencies: + '@types/pg': + specifier: ^8.20.0 + version: 8.20.0 + tsdown: + specifier: 'catalog:' + version: 0.22.3(typescript@6.0.3) + typescript: + specifier: 'catalog:' + version: 6.0.3 + packages/graphql: + dependencies: + '@drfed/models': + specifier: workspace:* + version: link:../models + '@pothos/core': + specifier: ^4.13.0 + version: 4.13.0(graphql@16.14.2) + '@pothos/plugin-drizzle': + specifier: ^0.17.4 + version: 0.17.4(@pothos/core@4.13.0(graphql@16.14.2))(drizzle-orm@1.0.0-beta.22(@electric-sql/pglite@0.5.3)(@types/pg@8.20.0)(pg@8.21.0))(graphql@16.14.2) + drizzle-orm: + specifier: 'catalog:' + version: 1.0.0-beta.22(@electric-sql/pglite@0.5.3)(@types/pg@8.20.0)(pg@8.21.0) + graphql: + specifier: ^16.14.2 + version: 16.14.2 + graphql-yoga: + specifier: ^5.21.2 + version: 5.21.2(graphql@16.14.2) devDependencies: tsdown: specifier: 'catalog:' - version: 0.22.3 + version: 0.22.3(typescript@6.0.3) + typescript: + specifier: 'catalog:' + version: 6.0.3 + + packages/models: + dependencies: + '@electric-sql/pglite': + specifier: ^0.5.3 + version: 0.5.3 + drizzle-orm: + specifier: 'catalog:' + version: 1.0.0-beta.22(@electric-sql/pglite@0.5.3)(@types/pg@8.20.0)(pg@8.21.0) + pg: + specifier: ^8.21.0 + version: 8.21.0 + devDependencies: + '@types/pg': + specifier: ^8.20.0 + version: 8.20.0 + drizzle-kit: + specifier: 1.0.0-beta.22 + version: 1.0.0-beta.22 + tsdown: + specifier: 'catalog:' + version: 0.22.3(typescript@6.0.3) + typescript: + specifier: 'catalog:' + version: 6.0.3 packages: @@ -41,6 +131,12 @@ packages: resolution: {integrity: sha512-K8ponJDxBwDHigkeFqaqT5wLGl4bTlwMafR8k7b5CPxr6Ww+UG9ls8Yx6Tcpboxu97eeGVEEyKcHmEyOwN1vSw==} engines: {node: ^22.18.0 || >=24.11.0} + '@drizzle-team/brocli@0.11.0': + resolution: {integrity: sha512-hD3pekGiPg0WPCCGAZmusBBJsDqGUR66Y452YgQsZOnkdQ7ViEPKuyP4huUGEZQefp8g34RRodXYmJ2TbCH+tg==} + + '@electric-sql/pglite@0.5.3': + resolution: {integrity: sha512-iTTYbA5Uesrl+N7zss0J5LopT7KE4j9aymYo+EZZh+rZbARQCUQOs+n2pay64JRUpc3fCkpfrniTNJnvYzOE+g==} + '@emnapi/core@1.11.0': resolution: {integrity: sha512-l9Oo58x0HOP5znGzVhYW9U3e5wVuA4LAZU2AGezTmkhO1CgQRFDhDg4nneHsu/t3WniXg9QrG2nIXL/ZS8ln8Q==} @@ -50,6 +146,224 @@ packages: '@emnapi/wasi-threads@1.2.2': resolution: {integrity: sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==} + '@envelop/core@5.5.1': + resolution: {integrity: sha512-3DQg8sFskDo386TkL5j12jyRAdip/8yzK3x7YGbZBgobZ4aKXrvDU0GppU0SnmrpQnNaiTUsxBs9LKkwQ/eyvw==} + engines: {node: '>=18.0.0'} + + '@envelop/instrumentation@1.0.0': + resolution: {integrity: sha512-cxgkB66RQB95H3X27jlnxCRNTmPuSTgmBAq6/4n2Dtv4hsk4yz8FadA1ggmd0uZzvKqWD6CR+WFgTjhDqg7eyw==} + engines: {node: '>=18.0.0'} + + '@envelop/types@5.2.1': + resolution: {integrity: sha512-CsFmA3u3c2QoLDTfEpGr4t25fjMU31nyvse7IzWTvb0ZycuPjMjb0fjlheh+PbhBYb9YLugnT2uY6Mwcg1o+Zg==} + engines: {node: '>=18.0.0'} + + '@esbuild/aix-ppc64@0.25.12': + resolution: {integrity: sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.25.12': + resolution: {integrity: sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.25.12': + resolution: {integrity: sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.25.12': + resolution: {integrity: sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.25.12': + resolution: {integrity: sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.25.12': + resolution: {integrity: sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.25.12': + resolution: {integrity: sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.25.12': + resolution: {integrity: sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.25.12': + resolution: {integrity: sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.25.12': + resolution: {integrity: sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.25.12': + resolution: {integrity: sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.25.12': + resolution: {integrity: sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.25.12': + resolution: {integrity: sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.25.12': + resolution: {integrity: sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.25.12': + resolution: {integrity: sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.25.12': + resolution: {integrity: sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.25.12': + resolution: {integrity: sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.25.12': + resolution: {integrity: sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.25.12': + resolution: {integrity: sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.25.12': + resolution: {integrity: sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.25.12': + resolution: {integrity: sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openharmony-arm64@0.25.12': + resolution: {integrity: sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/sunos-x64@0.25.12': + resolution: {integrity: sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.25.12': + resolution: {integrity: sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.25.12': + resolution: {integrity: sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.25.12': + resolution: {integrity: sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@fastify/busboy@3.2.0': + resolution: {integrity: sha512-m9FVDXU3GT2ITSe0UaMA5rU3QkfC/UXtCU8y0gSN/GugTqtVldOBWIB5V6V3sbmenVZUIpU6f+mPEO2+m5iTaA==} + + '@graphql-tools/executor@1.5.3': + resolution: {integrity: sha512-mgBFC0bsrZPZLu9EnydpMnAuQ8Iiq0CEbUcsmvXsm2/iYektGHDN/+bmb7hicA6dWZtdPfklYJmr21WD0GnOfA==} + engines: {node: '>=16.0.0'} + peerDependencies: + graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + + '@graphql-tools/merge@9.1.9': + resolution: {integrity: sha512-iHUWNjRHeQRYdgIMIuChThOwoKzA9vrzYeslgfBo5eUYEyHGZCoDPjAavssoYXLwstYt1dZj2J22jSzc2DrN0Q==} + engines: {node: '>=16.0.0'} + peerDependencies: + graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + + '@graphql-tools/schema@10.0.33': + resolution: {integrity: sha512-O6P3RIftO0jafnSsFAqpjurUuUxJ43s/AdPVLQsBkI6y4Ic/tKm4C1Qm1KKQsCDTOxXPJClh/v3g7k7yLKCFBQ==} + engines: {node: '>=16.0.0'} + peerDependencies: + graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + + '@graphql-tools/utils@10.11.0': + resolution: {integrity: sha512-iBFR9GXIs0gCD+yc3hoNswViL1O5josI33dUqiNStFI/MHLCEPduasceAcazRH77YONKNiviHBV8f7OgcT4o2Q==} + engines: {node: '>=16.0.0'} + peerDependencies: + graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + + '@graphql-tools/utils@11.1.0': + resolution: {integrity: sha512-PtFVG4r8Z2LEBSaPYQMusBiB3o6kjLVJyjCLbnWem/SpSuM21v6LTmgpkXfYU1qpBV2UGsFyuEnSJInl8fR1Ag==} + engines: {node: '>=16.0.0'} + peerDependencies: + graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + + '@graphql-typed-document-node/core@3.2.0': + resolution: {integrity: sha512-mB9oAsNCm9aM3/SOv4YtBMqZbYj10R7dkq8byBqxGY/ncFwhf2oQzMV+LCRlWoDSEBJ3COiR1yeDvMtsoOsuFQ==} + peerDependencies: + graphql: ^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + + '@graphql-yoga/logger@2.0.1': + resolution: {integrity: sha512-Nv0BoDGLMg9QBKy9cIswQ3/6aKaKjlTh87x3GiBg2Z4RrjyrM48DvOOK0pJh1C1At+b0mUIM67cwZcFTDLN4sA==} + engines: {node: '>=18.0.0'} + + '@graphql-yoga/subscription@5.0.5': + resolution: {integrity: sha512-oCMWOqFs6QV96/NZRt/ZhTQvzjkGB4YohBOpKM4jH/lDT4qb7Lex/aGCxpi/JD9njw3zBBtMqxbaC22+tFHVvw==} + engines: {node: '>=18.0.0'} + + '@graphql-yoga/typed-event-target@3.0.2': + resolution: {integrity: sha512-ZpJxMqB+Qfe3rp6uszCQoag4nSw42icURnBRfFYSOmTgEeOe4rD0vYlbA8spvCu2TlCesNTlEN9BLWtQqLxabA==} + engines: {node: '>=18.0.0'} + '@jridgewell/gen-mapping@0.3.13': resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} @@ -63,18 +377,45 @@ packages: '@jridgewell/trace-mapping@0.3.31': resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + '@js-temporal/polyfill@0.5.1': + resolution: {integrity: sha512-hloP58zRVCRSpgDxmqCWJNlizAlUgJFqG2ypq79DCvyv9tHjRYMDOcPFjzfl/A1/YxDvRCZz8wvZvmapQnKwFQ==} + engines: {node: '>=12'} + '@napi-rs/wasm-runtime@1.1.5': resolution: {integrity: sha512-AWPoBRJ9tsnVhor4sjO7rkni+7p+2IAEFj6cx06UgP10jkQHqay/36uRV/bFkgrh18D9vb4cr8Q0Pthskgzy+Q==} peerDependencies: '@emnapi/core': ^1.7.1 '@emnapi/runtime': ^1.7.1 + '@optique/core@1.1.0': + resolution: {integrity: sha512-eBqai76tHiFDoShlTNXN9AAPs9XznCJRrk4qmGhjZUSMmePCF9o1XU3okUHxHdDXXCHj4auKoIvCN79KNKErxA==} + engines: {bun: '>=1.2.0', deno: '>=2.3.0', node: '>=20.0.0'} + + '@optique/run@1.1.0': + resolution: {integrity: sha512-dcuqqqU1Cpm9CLGEkCkpT/cpJ6H6a+hs0rP+iD8Tgwb+CPPZtX/hCfdIrqYyZ2RtYLxgc3S6KqC81AZAwEUPew==} + engines: {bun: '>=1.2.0', deno: '>=2.3.0', node: '>=20.0.0'} + '@oxc-project/types@0.135.0': resolution: {integrity: sha512-wR+xRdFkUBMvcAjBJ2q2kcZM6d+DKu2NgoOyxZgYwZdLhmiv6+rnO8PZ/P68kMiZtIKm+pW7zyEJ4kSOs0vo+Q==} + '@pothos/core@4.13.0': + resolution: {integrity: sha512-bIaVdLTkwPkkmIn0Ji13vsc9Zy0mEi++a9iMFIRVa88l0G7JuFJrJX5qGPo1GGu4drvRW4SAr2aDm2V5KcYj4A==} + peerDependencies: + graphql: ^16.10.0 || ^17.0.0 + + '@pothos/plugin-drizzle@0.17.4': + resolution: {integrity: sha512-cAaMexSSOtoXf7Khj62yOHGxHyaOnuIzNdgl+w0rNfnJdtVOr4slQjHinWJWzYWdRauOclZddxWbLaou4NK0QQ==} + peerDependencies: + '@pothos/core': '*' + drizzle-orm: '>=1.0.0-beta.2' + graphql: ^16.10.0 + '@quansync/fs@1.0.0': resolution: {integrity: sha512-4TJ3DFtlf1L5LDMaM6CanJ/0lckGNtJcMjQ1NAV6zDmA0tEHKZtxNKin8EgPaVX1YzljbxckyT2tJrpQKAtngQ==} + '@repeaterjs/repeater@3.1.0': + resolution: {integrity: sha512-TaoVksZRSx2KWYYpyLQtMQXXeS98VsgZImzW65xmiVgbYhXLk+aEsmzPLirqVuE4/XuUapH2iMtxUzaBNDzdSQ==} + '@rolldown/binding-android-arm64@1.1.1': resolution: {integrity: sha512-BLf9Wak/gfwVb7NQTQW4wBgL3oAfPy7ArEkhwV543OVw/uY6B47z5xYsqPSZ9PDOorvURPinws6ThaFuNgGLgA==} engines: {node: ^20.19.0 || >=22.12.0} @@ -182,6 +523,36 @@ packages: '@types/jsesc@2.5.1': resolution: {integrity: sha512-9VN+6yxLOPLOav+7PwjZbxiID2bVaeq0ED4qSQmdQTdjnXJSaCVKTR58t15oqH1H5t8Ng2ZX1SabJVoN9Q34bw==} + '@types/node@25.9.3': + resolution: {integrity: sha512-603BddQMv3pUcr4U2dhujk83N2tTDVr/34wII2B6bJy6g+8WD6yUb11jszNs0gdi4PesVWl7ABt8nYMVpnLUcg==} + + '@types/pg@8.20.0': + resolution: {integrity: sha512-bEPFOaMAHTEP1EzpvHTbmwR8UsFyHSKsRisLIHVMXnpNefSbGA1bD6CVy+qKjGSqmZqNqBDV2azOBo8TgkcVow==} + + '@whatwg-node/disposablestack@0.0.6': + resolution: {integrity: sha512-LOtTn+JgJvX8WfBVJtF08TGrdjuFzGJc4mkP8EdDI8ADbvO7kiexYep1o8dwnt0okb0jYclCDXF13xU7Ge4zSw==} + engines: {node: '>=18.0.0'} + + '@whatwg-node/events@0.1.2': + resolution: {integrity: sha512-ApcWxkrs1WmEMS2CaLLFUEem/49erT3sxIVjpzU5f6zmVcnijtDSrhoK2zVobOIikZJdH63jdAXOrvjf6eOUNQ==} + engines: {node: '>=18.0.0'} + + '@whatwg-node/fetch@0.10.13': + resolution: {integrity: sha512-b4PhJ+zYj4357zwk4TTuF2nEe0vVtOrwdsrNo5hL+u1ojXNhh1FgJ6pg1jzDlwlT4oBdzfSwaBwMCtFCsIWg8Q==} + engines: {node: '>=18.0.0'} + + '@whatwg-node/node-fetch@0.8.6': + resolution: {integrity: sha512-BDMdYFcerLQkwA2RTldxOqRCs6ZQD1S7UgP3pUdGUkcbgTrP/V5ko77ZkCww9DHmC4lpoYuwigGfQYj285gMvA==} + engines: {node: '>=18.0.0'} + + '@whatwg-node/promise-helpers@1.3.2': + resolution: {integrity: sha512-Nst5JdK47VIl9UcGwtv2Rcgyn5lWtZ0/mhRQ4G8NN2isxpq2TO30iqHzmwoJycjWuyUfg3GFXqP/gFHXeV57IA==} + engines: {node: '>=16.0.0'} + + '@whatwg-node/server@0.11.0': + resolution: {integrity: sha512-VSdkwnJRr8Yv9UgB2aXB3VUPWwd6Oqnn0hycFwhg9pZgWxJXb7JmhsiXe9tmpMwjHFxli12PGcz9aI63YYloGQ==} + engines: {node: '>=18.0.0'} + ansis@4.3.1: resolution: {integrity: sha512-BJ8/l4R5LRE7hW9WdSuGYrLSHi2ynxeFpDFbH0K/CgNeY/tyhk+vO6TYxXC5r5CpUhNVX310xzPsN/H9lCdfOA==} engines: {node: '>=14'} @@ -197,9 +568,136 @@ packages: resolution: {integrity: sha512-tixWYgm5ZoOD+3g6UTea91eow5z6AAHaho3g0V9CNSNb45gM8SmflpAc+GRd1InC4AqN/07Unrgp56Y94N9hJQ==} engines: {node: '>=20.19.0'} + cross-inspect@1.0.1: + resolution: {integrity: sha512-Pcw1JTvZLSJH83iiGWt6fRcT+BjZlCDRVwYLbUcHzv/CRpB7r0MlSrGbIyQvVSNyGnbt7G4AXuyCiDR3POvZ1A==} + engines: {node: '>=16.0.0'} + defu@6.1.7: resolution: {integrity: sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==} + drizzle-kit@1.0.0-beta.22: + resolution: {integrity: sha512-9HTZuQRljQKTgCx4UhiGn8KYYfHGk4+B/bRR1714W67kz0qgJvdrG527i8rQD8uUyET9UTGR1u8syySJD4znGw==} + hasBin: true + + drizzle-orm@1.0.0-beta.22: + resolution: {integrity: sha512-F+DZyVIvH0oVKa/w08Cle1xfoH+pc+htIXHG/frnMLG72aby9NYYr9oc+9XvghnoO4umxFItduz0OMmQJMnenw==} + peerDependencies: + '@aws-sdk/client-rds-data': '>=3' + '@cloudflare/workers-types': '>=4' + '@effect/sql': ^0.48.5 + '@effect/sql-pg': ^0.49.7 + '@electric-sql/pglite': '>=0.2.0' + '@libsql/client': '>=0.10.0' + '@libsql/client-wasm': '>=0.10.0' + '@neondatabase/serverless': '>=0.10.0' + '@op-engineering/op-sqlite': '>=2' + '@opentelemetry/api': ^1.4.1 + '@planetscale/database': '>=1.13' + '@sinclair/typebox': '>=0.34.8' + '@sqlitecloud/drivers': '>=1.0.653' + '@tidbcloud/serverless': '*' + '@tursodatabase/database': '>=0.2.1' + '@tursodatabase/database-common': '>=0.2.1' + '@tursodatabase/database-wasm': '>=0.2.1' + '@types/better-sqlite3': '*' + '@types/mssql': ^9.1.4 + '@types/pg': '*' + '@types/sql.js': '*' + '@upstash/redis': '>=1.34.7' + '@vercel/postgres': '>=0.8.0' + '@xata.io/client': '*' + arktype: '>=2.0.0' + better-sqlite3: '>=9.3.0' + bun-types: '*' + expo-sqlite: '>=14.0.0' + gel: '>=2' + mssql: ^11.0.1 + mysql2: '>=2' + pg: '>=8' + postgres: '>=3' + sql.js: '>=1' + sqlite3: '>=5' + typebox: '>=1.0.0' + valibot: '>=1.0.0-beta.7' + zod: ^3.25.0 || ^4.0.0 + peerDependenciesMeta: + '@aws-sdk/client-rds-data': + optional: true + '@cloudflare/workers-types': + optional: true + '@effect/sql': + optional: true + '@effect/sql-pg': + optional: true + '@electric-sql/pglite': + optional: true + '@libsql/client': + optional: true + '@libsql/client-wasm': + optional: true + '@neondatabase/serverless': + optional: true + '@op-engineering/op-sqlite': + optional: true + '@opentelemetry/api': + optional: true + '@planetscale/database': + optional: true + '@sinclair/typebox': + optional: true + '@sqlitecloud/drivers': + optional: true + '@tidbcloud/serverless': + optional: true + '@tursodatabase/database': + optional: true + '@tursodatabase/database-common': + optional: true + '@tursodatabase/database-wasm': + optional: true + '@types/better-sqlite3': + optional: true + '@types/mssql': + optional: true + '@types/pg': + optional: true + '@types/sql.js': + optional: true + '@upstash/redis': + optional: true + '@vercel/postgres': + optional: true + '@xata.io/client': + optional: true + arktype: + optional: true + better-sqlite3: + optional: true + bun-types: + optional: true + expo-sqlite: + optional: true + gel: + optional: true + mssql: + optional: true + mysql2: + optional: true + pg: + optional: true + postgres: + optional: true + sql.js: + optional: true + sqlite3: + optional: true + typebox: + optional: true + valibot: + optional: true + zod: + optional: true + dts-resolver@3.0.0: resolution: {integrity: sha512-1T1f+z+4tl9XD+m+0HBgWoL/nm0bOIffyWaUuUSBlFg/86IWvfx+wjNaO/ybU0AJzG9/Mi5hBUgGV6zCmWEN7Q==} engines: {node: ^22.18.0 || >=24.0.0} @@ -213,6 +711,11 @@ packages: resolution: {integrity: sha512-YGRs8knHhKHVShLkFET/rWAU8kmHbOV5LwN938RHI0pljAJ1Gf6SzXsSmRaEzcXTtOOmVqJ5+WtQPL5uigY50Q==} engines: {node: '>=14'} + esbuild@0.25.12: + resolution: {integrity: sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==} + engines: {node: '>=18'} + hasBin: true + estree-walker@3.0.3: resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} @@ -225,10 +728,23 @@ packages: picomatch: optional: true + get-tsconfig@4.14.0: + resolution: {integrity: sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==} + get-tsconfig@5.0.0-beta.5: resolution: {integrity: sha512-/6gFNr0N04nob252sTQxyFLi3eKFRqIg1I87YcqAMT1i6SQrSF6KujUEQrtrjMV0H/eejTCltLdDSTEMzHbnsQ==} engines: {node: '>=20.20.0'} + graphql-yoga@5.21.2: + resolution: {integrity: sha512-IIRF/3xtjj2D6caAWL9177hQ8tV3mWB3hve1GRnz7njPhQ3iY1jFtSp98fNGv0yV9kaPh9kKQ8JWdJZnedVmDw==} + engines: {node: '>=18.0.0'} + peerDependencies: + graphql: ^15.2.0 || ^16.0.0 + + graphql@16.14.2: + resolution: {integrity: sha512-Chq1s4CY7jmh8gO2qvLIJyfCDIN+EHLFW/9iShnp1z8FjBQMoodWP1kDC36VAMXXIvAjj4ARa7ntfAV2BrjsbA==} + engines: {node: ^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0} + hookable@6.1.1: resolution: {integrity: sha512-U9LYDy1CwhMCnprUfeAZWZGByVbhd54hwepegYTK7Pi5NvqEj63ifz5z+xukznehT7i6NIZRu89Ay1AZmRsLEQ==} @@ -236,11 +752,21 @@ packages: resolution: {integrity: sha512-NkJQA7oZ4YHQhd2+H3BoRFKF3d/XNsiKpHZCQEMH9pDX27hQQLsTyOocyRgaIVtf8gHX3Nt3LPkR4e5EdtPAGQ==} engines: {node: ^22.18.0 || >=24.0.0} + jiti@2.7.0: + resolution: {integrity: sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==} + hasBin: true + + jsbi@4.3.2: + resolution: {integrity: sha512-9fqMSQbhJykSeii05nxKl4m6Eqn2P6rOlYiS+C5Dr/HPIU/7yZxu5qzbs40tgaFORiw2Amd0mirjxatXYMkIew==} + jsesc@3.1.0: resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} engines: {node: '>=6'} hasBin: true + lru-cache@10.4.3: + resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} + obug@2.1.3: resolution: {integrity: sha512-9miFgM2OFba7hB+pRgvtV84pYTBaoTHohvmIgiRt6dRIzbwEOIaNaP+dIlGs2fNFoB0SeISs0Jz5WFVRid6Xyg==} engines: {node: '>=12.20.0'} @@ -248,10 +774,60 @@ packages: pathe@2.0.3: resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + pg-cloudflare@1.4.0: + resolution: {integrity: sha512-Vo7z/6rrQYxpNRylp4Tlob2elzbh+N/MOQbxFVWCxS7oEx6jF53GTJFxK2WWpKuBRkmiin4Mt+xofFDjx09R0A==} + + pg-connection-string@2.13.0: + resolution: {integrity: sha512-EMnU9E2fSULdsbErBbMaXJvFeD9B4+nPcM3f+4lsiCR0BHLPrLVjv3DbyM2hgQQviKJaTWIRRTjKjWlHg3p2ig==} + + pg-int8@1.0.1: + resolution: {integrity: sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==} + engines: {node: '>=4.0.0'} + + pg-pool@3.14.0: + resolution: {integrity: sha512-gKtPkFdQPU3DksooVLi9LsjZxrsBUZIpa+7aVx+LV5pNh0KzP4Zleud2po+ConrxbuXGBJ6Hfer6hdgpIBpBaw==} + peerDependencies: + pg: '>=8.0' + + pg-protocol@1.14.0: + resolution: {integrity: sha512-n5taZ1kO3s9ngDTVxsEznOqCyToTgz0FLuPq0B33COy5pPpuWJpY3/2oRBVETuOgzdqRXfWpM9HIhp2LBBT1BA==} + + pg-types@2.2.0: + resolution: {integrity: sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==} + engines: {node: '>=4'} + + pg@8.21.0: + resolution: {integrity: sha512-AUP1EYJuHraQGsVoCQVIcM7TEJVGtDzxWtGFZd8rds9d+CCXlU5Js1rYgfLNvxy9iJrpHjGrRjoi/3BT9fRyiA==} + engines: {node: '>= 16.0.0'} + peerDependencies: + pg-native: '>=3.0.1' + peerDependenciesMeta: + pg-native: + optional: true + + pgpass@1.0.5: + resolution: {integrity: sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==} + picomatch@4.0.4: resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==} engines: {node: '>=12'} + postgres-array@2.0.0: + resolution: {integrity: sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==} + engines: {node: '>=4'} + + postgres-bytea@1.0.1: + resolution: {integrity: sha512-5+5HqXnsZPE65IJZSMkZtURARZelel2oXUEO8rH83VS/hxH5vv1uHquPg5wZs8yMAfdv971IU+kcPUczi7NVBQ==} + engines: {node: '>=0.10.0'} + + postgres-date@1.0.7: + resolution: {integrity: sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==} + engines: {node: '>=0.10.0'} + + postgres-interval@1.2.0: + resolution: {integrity: sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==} + engines: {node: '>=0.10.0'} + quansync@1.0.0: resolution: {integrity: sha512-5xZacEEufv3HSTPQuchrvV6soaiACMFnq1H8wkVioctoH3TRha9Sz66lOxRwPK/qZj7HPiSveih9yAyh98gvqA==} @@ -287,6 +863,15 @@ packages: engines: {node: '>=10'} hasBin: true + split2@4.2.0: + resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} + engines: {node: '>= 10.x'} + + srvx@0.11.16: + resolution: {integrity: sha512-bp07zRuycfTY43IjAvvTFnmnJi8ikW0VFiHwOhhYcVW/L4xQ1XY4PAd4Nuum1rsA17C39zL7x+CDhrn5AL32Rw==} + engines: {node: '>=20.16.0'} + hasBin: true + tinyexec@1.2.4: resolution: {integrity: sha512-SHf/r48b7vOrjve9PxJo3MN5v5yuyjHvdUcrQffT3WXMUfnGmHDVbC4k3sHJaJTgZCwpUplIaAo5ANtMyp3YHg==} engines: {node: '>=18'} @@ -336,9 +921,24 @@ packages: tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + typescript@6.0.3: + resolution: {integrity: sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==} + engines: {node: '>=14.17'} + hasBin: true + unconfig-core@7.5.0: resolution: {integrity: sha512-Su3FauozOGP44ZmKdHy2oE6LPjk51M/TRRjHv2HNCWiDvfvCoxC2lno6jevMA91MYAdCdwP05QnWdWpSbncX/w==} + undici-types@7.24.6: + resolution: {integrity: sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==} + + urlpattern-polyfill@10.1.0: + resolution: {integrity: sha512-IGjKp/o0NL3Bso1PymYURCJxMPNAf/ILOpendP9f5B6e1rTJgdgiOvgfoT8VxCAdY+Wisb9uhGaJJf3yZ2V9nw==} + + xtend@4.0.2: + resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} + engines: {node: '>=0.4'} + snapshots: '@babel/generator@8.0.0': @@ -363,6 +963,10 @@ snapshots: '@babel/helper-string-parser': 8.0.0 '@babel/helper-validator-identifier': 8.0.0 + '@drizzle-team/brocli@0.11.0': {} + + '@electric-sql/pglite@0.5.3': {} + '@emnapi/core@1.11.0': dependencies: '@emnapi/wasi-threads': 1.2.2 @@ -379,6 +983,162 @@ snapshots: tslib: 2.8.1 optional: true + '@envelop/core@5.5.1': + dependencies: + '@envelop/instrumentation': 1.0.0 + '@envelop/types': 5.2.1 + '@whatwg-node/promise-helpers': 1.3.2 + tslib: 2.8.1 + + '@envelop/instrumentation@1.0.0': + dependencies: + '@whatwg-node/promise-helpers': 1.3.2 + tslib: 2.8.1 + + '@envelop/types@5.2.1': + dependencies: + '@whatwg-node/promise-helpers': 1.3.2 + tslib: 2.8.1 + + '@esbuild/aix-ppc64@0.25.12': + optional: true + + '@esbuild/android-arm64@0.25.12': + optional: true + + '@esbuild/android-arm@0.25.12': + optional: true + + '@esbuild/android-x64@0.25.12': + optional: true + + '@esbuild/darwin-arm64@0.25.12': + optional: true + + '@esbuild/darwin-x64@0.25.12': + optional: true + + '@esbuild/freebsd-arm64@0.25.12': + optional: true + + '@esbuild/freebsd-x64@0.25.12': + optional: true + + '@esbuild/linux-arm64@0.25.12': + optional: true + + '@esbuild/linux-arm@0.25.12': + optional: true + + '@esbuild/linux-ia32@0.25.12': + optional: true + + '@esbuild/linux-loong64@0.25.12': + optional: true + + '@esbuild/linux-mips64el@0.25.12': + optional: true + + '@esbuild/linux-ppc64@0.25.12': + optional: true + + '@esbuild/linux-riscv64@0.25.12': + optional: true + + '@esbuild/linux-s390x@0.25.12': + optional: true + + '@esbuild/linux-x64@0.25.12': + optional: true + + '@esbuild/netbsd-arm64@0.25.12': + optional: true + + '@esbuild/netbsd-x64@0.25.12': + optional: true + + '@esbuild/openbsd-arm64@0.25.12': + optional: true + + '@esbuild/openbsd-x64@0.25.12': + optional: true + + '@esbuild/openharmony-arm64@0.25.12': + optional: true + + '@esbuild/sunos-x64@0.25.12': + optional: true + + '@esbuild/win32-arm64@0.25.12': + optional: true + + '@esbuild/win32-ia32@0.25.12': + optional: true + + '@esbuild/win32-x64@0.25.12': + optional: true + + '@fastify/busboy@3.2.0': {} + + '@graphql-tools/executor@1.5.3(graphql@16.14.2)': + dependencies: + '@graphql-tools/utils': 11.1.0(graphql@16.14.2) + '@graphql-typed-document-node/core': 3.2.0(graphql@16.14.2) + '@repeaterjs/repeater': 3.1.0 + '@whatwg-node/disposablestack': 0.0.6 + '@whatwg-node/promise-helpers': 1.3.2 + graphql: 16.14.2 + tslib: 2.8.1 + + '@graphql-tools/merge@9.1.9(graphql@16.14.2)': + dependencies: + '@graphql-tools/utils': 11.1.0(graphql@16.14.2) + graphql: 16.14.2 + tslib: 2.8.1 + + '@graphql-tools/schema@10.0.33(graphql@16.14.2)': + dependencies: + '@graphql-tools/merge': 9.1.9(graphql@16.14.2) + '@graphql-tools/utils': 11.1.0(graphql@16.14.2) + graphql: 16.14.2 + tslib: 2.8.1 + + '@graphql-tools/utils@10.11.0(graphql@16.14.2)': + dependencies: + '@graphql-typed-document-node/core': 3.2.0(graphql@16.14.2) + '@whatwg-node/promise-helpers': 1.3.2 + cross-inspect: 1.0.1 + graphql: 16.14.2 + tslib: 2.8.1 + + '@graphql-tools/utils@11.1.0(graphql@16.14.2)': + dependencies: + '@graphql-typed-document-node/core': 3.2.0(graphql@16.14.2) + '@whatwg-node/promise-helpers': 1.3.2 + cross-inspect: 1.0.1 + graphql: 16.14.2 + tslib: 2.8.1 + + '@graphql-typed-document-node/core@3.2.0(graphql@16.14.2)': + dependencies: + graphql: 16.14.2 + + '@graphql-yoga/logger@2.0.1': + dependencies: + tslib: 2.8.1 + + '@graphql-yoga/subscription@5.0.5': + dependencies: + '@graphql-yoga/typed-event-target': 3.0.2 + '@repeaterjs/repeater': 3.1.0 + '@whatwg-node/events': 0.1.2 + tslib: 2.8.1 + + '@graphql-yoga/typed-event-target@3.0.2': + dependencies: + '@repeaterjs/repeater': 3.1.0 + tslib: 2.8.1 + '@jridgewell/gen-mapping@0.3.13': dependencies: '@jridgewell/sourcemap-codec': 1.5.5 @@ -393,6 +1153,10 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.5 + '@js-temporal/polyfill@0.5.1': + dependencies: + jsbi: 4.3.2 + '@napi-rs/wasm-runtime@1.1.5(@emnapi/core@1.11.0)(@emnapi/runtime@1.11.0)': dependencies: '@emnapi/core': 1.11.0 @@ -400,12 +1164,30 @@ snapshots: '@tybys/wasm-util': 0.10.2 optional: true + '@optique/core@1.1.0': {} + + '@optique/run@1.1.0': + dependencies: + '@optique/core': 1.1.0 + '@oxc-project/types@0.135.0': {} + '@pothos/core@4.13.0(graphql@16.14.2)': + dependencies: + graphql: 16.14.2 + + '@pothos/plugin-drizzle@0.17.4(@pothos/core@4.13.0(graphql@16.14.2))(drizzle-orm@1.0.0-beta.22(@electric-sql/pglite@0.5.3)(@types/pg@8.20.0)(pg@8.21.0))(graphql@16.14.2)': + dependencies: + '@pothos/core': 4.13.0(graphql@16.14.2) + drizzle-orm: 1.0.0-beta.22(@electric-sql/pglite@0.5.3)(@types/pg@8.20.0)(pg@8.21.0) + graphql: 16.14.2 + '@quansync/fs@1.0.0': dependencies: quansync: 1.0.0 + '@repeaterjs/repeater@3.1.0': {} + '@rolldown/binding-android-arm64@1.1.1': optional: true @@ -466,6 +1248,49 @@ snapshots: '@types/jsesc@2.5.1': {} + '@types/node@25.9.3': + dependencies: + undici-types: 7.24.6 + + '@types/pg@8.20.0': + dependencies: + '@types/node': 25.9.3 + pg-protocol: 1.14.0 + pg-types: 2.2.0 + + '@whatwg-node/disposablestack@0.0.6': + dependencies: + '@whatwg-node/promise-helpers': 1.3.2 + tslib: 2.8.1 + + '@whatwg-node/events@0.1.2': + dependencies: + tslib: 2.8.1 + + '@whatwg-node/fetch@0.10.13': + dependencies: + '@whatwg-node/node-fetch': 0.8.6 + urlpattern-polyfill: 10.1.0 + + '@whatwg-node/node-fetch@0.8.6': + dependencies: + '@fastify/busboy': 3.2.0 + '@whatwg-node/disposablestack': 0.0.6 + '@whatwg-node/promise-helpers': 1.3.2 + tslib: 2.8.1 + + '@whatwg-node/promise-helpers@1.3.2': + dependencies: + tslib: 2.8.1 + + '@whatwg-node/server@0.11.0': + dependencies: + '@envelop/instrumentation': 1.0.0 + '@whatwg-node/disposablestack': 0.0.6 + '@whatwg-node/fetch': 0.10.13 + '@whatwg-node/promise-helpers': 1.3.2 + tslib: 2.8.1 + ansis@4.3.1: {} ast-kit@3.0.0: @@ -478,12 +1303,59 @@ snapshots: cac@7.0.0: {} + cross-inspect@1.0.1: + dependencies: + tslib: 2.8.1 + defu@6.1.7: {} + drizzle-kit@1.0.0-beta.22: + dependencies: + '@drizzle-team/brocli': 0.11.0 + '@js-temporal/polyfill': 0.5.1 + esbuild: 0.25.12 + get-tsconfig: 4.14.0 + jiti: 2.7.0 + + drizzle-orm@1.0.0-beta.22(@electric-sql/pglite@0.5.3)(@types/pg@8.20.0)(pg@8.21.0): + optionalDependencies: + '@electric-sql/pglite': 0.5.3 + '@types/pg': 8.20.0 + pg: 8.21.0 + dts-resolver@3.0.0: {} empathic@2.0.1: {} + esbuild@0.25.12: + optionalDependencies: + '@esbuild/aix-ppc64': 0.25.12 + '@esbuild/android-arm': 0.25.12 + '@esbuild/android-arm64': 0.25.12 + '@esbuild/android-x64': 0.25.12 + '@esbuild/darwin-arm64': 0.25.12 + '@esbuild/darwin-x64': 0.25.12 + '@esbuild/freebsd-arm64': 0.25.12 + '@esbuild/freebsd-x64': 0.25.12 + '@esbuild/linux-arm': 0.25.12 + '@esbuild/linux-arm64': 0.25.12 + '@esbuild/linux-ia32': 0.25.12 + '@esbuild/linux-loong64': 0.25.12 + '@esbuild/linux-mips64el': 0.25.12 + '@esbuild/linux-ppc64': 0.25.12 + '@esbuild/linux-riscv64': 0.25.12 + '@esbuild/linux-s390x': 0.25.12 + '@esbuild/linux-x64': 0.25.12 + '@esbuild/netbsd-arm64': 0.25.12 + '@esbuild/netbsd-x64': 0.25.12 + '@esbuild/openbsd-arm64': 0.25.12 + '@esbuild/openbsd-x64': 0.25.12 + '@esbuild/openharmony-arm64': 0.25.12 + '@esbuild/sunos-x64': 0.25.12 + '@esbuild/win32-arm64': 0.25.12 + '@esbuild/win32-ia32': 0.25.12 + '@esbuild/win32-x64': 0.25.12 + estree-walker@3.0.3: dependencies: '@types/estree': 1.0.9 @@ -492,27 +1364,100 @@ snapshots: optionalDependencies: picomatch: 4.0.4 + get-tsconfig@4.14.0: + dependencies: + resolve-pkg-maps: 1.0.0 + get-tsconfig@5.0.0-beta.5: dependencies: resolve-pkg-maps: 1.0.0 + graphql-yoga@5.21.2(graphql@16.14.2): + dependencies: + '@envelop/core': 5.5.1 + '@envelop/instrumentation': 1.0.0 + '@graphql-tools/executor': 1.5.3(graphql@16.14.2) + '@graphql-tools/schema': 10.0.33(graphql@16.14.2) + '@graphql-tools/utils': 10.11.0(graphql@16.14.2) + '@graphql-yoga/logger': 2.0.1 + '@graphql-yoga/subscription': 5.0.5 + '@whatwg-node/fetch': 0.10.13 + '@whatwg-node/promise-helpers': 1.3.2 + '@whatwg-node/server': 0.11.0 + graphql: 16.14.2 + lru-cache: 10.4.3 + tslib: 2.8.1 + + graphql@16.14.2: {} + hookable@6.1.1: {} import-without-cache@0.4.0: {} + jiti@2.7.0: {} + + jsbi@4.3.2: {} + jsesc@3.1.0: {} + lru-cache@10.4.3: {} + obug@2.1.3: {} pathe@2.0.3: {} + pg-cloudflare@1.4.0: + optional: true + + pg-connection-string@2.13.0: {} + + pg-int8@1.0.1: {} + + pg-pool@3.14.0(pg@8.21.0): + dependencies: + pg: 8.21.0 + + pg-protocol@1.14.0: {} + + pg-types@2.2.0: + dependencies: + pg-int8: 1.0.1 + postgres-array: 2.0.0 + postgres-bytea: 1.0.1 + postgres-date: 1.0.7 + postgres-interval: 1.2.0 + + pg@8.21.0: + dependencies: + pg-connection-string: 2.13.0 + pg-pool: 3.14.0(pg@8.21.0) + pg-protocol: 1.14.0 + pg-types: 2.2.0 + pgpass: 1.0.5 + optionalDependencies: + pg-cloudflare: 1.4.0 + + pgpass@1.0.5: + dependencies: + split2: 4.2.0 + picomatch@4.0.4: {} + postgres-array@2.0.0: {} + + postgres-bytea@1.0.1: {} + + postgres-date@1.0.7: {} + + postgres-interval@1.2.0: + dependencies: + xtend: 4.0.2 + quansync@1.0.0: {} resolve-pkg-maps@1.0.0: {} - rolldown-plugin-dts@0.26.0(rolldown@1.1.1): + rolldown-plugin-dts@0.26.0(rolldown@1.1.1)(typescript@6.0.3): dependencies: '@babel/generator': 8.0.0 '@babel/helper-validator-identifier': 8.0.0 @@ -523,6 +1468,8 @@ snapshots: get-tsconfig: 5.0.0-beta.5 obug: 2.1.3 rolldown: 1.1.1 + optionalDependencies: + typescript: 6.0.3 transitivePeerDependencies: - oxc-resolver @@ -549,6 +1496,10 @@ snapshots: semver@7.8.4: {} + split2@4.2.0: {} + + srvx@0.11.16: {} + tinyexec@1.2.4: {} tinyglobby@0.2.17: @@ -558,7 +1509,7 @@ snapshots: tree-kill@1.2.2: {} - tsdown@0.22.3: + tsdown@0.22.3(typescript@6.0.3): dependencies: ansis: 4.3.1 cac: 7.0.0 @@ -569,22 +1520,31 @@ snapshots: obug: 2.1.3 picomatch: 4.0.4 rolldown: 1.1.1 - rolldown-plugin-dts: 0.26.0(rolldown@1.1.1) + rolldown-plugin-dts: 0.26.0(rolldown@1.1.1)(typescript@6.0.3) semver: 7.8.4 tinyexec: 1.2.4 tinyglobby: 0.2.17 tree-kill: 1.2.2 unconfig-core: 7.5.0 + optionalDependencies: + typescript: 6.0.3 transitivePeerDependencies: - '@ts-macro/tsc' - '@typescript/native-preview' - oxc-resolver - vue-tsc - tslib@2.8.1: - optional: true + tslib@2.8.1: {} + + typescript@6.0.3: {} unconfig-core@7.5.0: dependencies: '@quansync/fs': 1.0.0 quansync: 1.0.0 + + undici-types@7.24.6: {} + + urlpattern-polyfill@10.1.0: {} + + xtend@4.0.2: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 76f36d4..01b41fe 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,5 +1,10 @@ packages: - packages/* +allowBuilds: + esbuild: true + catalog: + drizzle-orm: 1.0.0-beta.22 tsdown: ^0.22.3 + typescript: ^6.0.3 From a6b308f8c6d6b4d3a667643fb2ead46fc3dee344 Mon Sep 17 00:00:00 2001 From: Hong Minhee Date: Sat, 20 Jun 2026 23:38:50 +0900 Subject: [PATCH 02/10] Add Account/Instance GraphQL nodes and custom scalars - Add @pothos/plugin-relay and graphql-scalars dependencies - Add DateTime, Email, UUID custom scalar types to the schema builder - Rename PothosTypes to SchemaTypes and add Scalars definition - Implement Account drizzleNode with UUID, email, created fields - Add accountByUuid query field - Refactor Instance to drizzleNode with slug, expires, created fields - Add normalizeEmail() utility function to @drfed/models - Export normalizeEmail from @drfed/models package index --- packages/graphql/package.json | 2 + packages/graphql/src/account.ts | 58 ++++++++++++++++++++++++ packages/graphql/src/builder.ts | 38 +++++++++++++--- packages/graphql/src/instance.ts | 30 ++++++++----- packages/graphql/src/schema.ts | 15 ++----- packages/models/src/email.ts | 75 ++++++++++++++++++++++++++++++++ packages/models/src/index.ts | 1 + pnpm-lock.yaml | 28 ++++++++++++ 8 files changed, 219 insertions(+), 28 deletions(-) create mode 100644 packages/graphql/src/account.ts create mode 100644 packages/models/src/email.ts diff --git a/packages/graphql/package.json b/packages/graphql/package.json index 2205b58..816af19 100644 --- a/packages/graphql/package.json +++ b/packages/graphql/package.json @@ -56,8 +56,10 @@ "@drfed/models": "workspace:*", "@pothos/core": "^4.13.0", "@pothos/plugin-drizzle": "^0.17.4", + "@pothos/plugin-relay": "^4.7.0", "drizzle-orm": "catalog:", "graphql": "^16.14.2", + "graphql-scalars": "^1.25.0", "graphql-yoga": "^5.21.2" } } diff --git a/packages/graphql/src/account.ts b/packages/graphql/src/account.ts new file mode 100644 index 0000000..68743a4 --- /dev/null +++ b/packages/graphql/src/account.ts @@ -0,0 +1,58 @@ +// DrFed: A web-based platform for developing and debugging ActivityPub apps +// Copyright (C) 2026 DrFed team +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . +import builder from "./builder.ts"; + +export const Account = builder.drizzleNode("accounts", { + name: "Account", + id: { + column(account) { + return account.id; + }, + description: "The unique identifier of the `Account`.", + }, + fields: (t) => ({ + uuid: t.expose("id", { + type: "UUID", + description: "The UUID of the `Account`.", + }), + email: t.expose("email", { + type: "Email", + description: "The email address of the `Account`.", + }), + created: t.expose("created", { + type: "DateTime", + description: "The date/time when the `Account` was created.", + }), + }), +}); + +builder.queryFields((t) => ({ + accountByUuid: t.drizzleField({ + type: Account, + description: "Get an `Account` by its UUID.", + args: { + uuid: t.arg({ + type: "UUID", + required: true, + description: "The UUID of the `Account` to retrieve.", + }), + }, + nullable: true, + resolve(query, _, { uuid }, ctx) { + return ctx.db.query.accounts.findFirst(query({ where: { id: uuid } })); + }, + }), +})); diff --git a/packages/graphql/src/builder.ts b/packages/graphql/src/builder.ts index df1ffb2..62df5f6 100644 --- a/packages/graphql/src/builder.ts +++ b/packages/graphql/src/builder.ts @@ -13,10 +13,12 @@ // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -import { type Database, relations } from "@drfed/models"; +import { type Database, normalizeEmail, relations } from "@drfed/models"; import SchemaBuilder from "@pothos/core"; import DrizzlePlugin from "@pothos/plugin-drizzle"; +import RelayPlugin from "@pothos/plugin-relay"; import { getTableConfig } from "drizzle-orm/pg-core"; +import { DateTimeResolver, UUIDResolver } from "graphql-scalars"; /** * The context data for the GraphQL server, which includes the incoming request @@ -41,16 +43,31 @@ export interface ServerContext { */ export interface UserContext extends ServerContext {} -export interface PothosTypes { - DefaultFieldNullability: false; +export interface SchemaTypes { Context: UserContext; + Scalars: { + DateTime: { + Input: Date; + Output: Date; + }; + Email: { + Input: string; + Output: string; + }; + UUID: { + Input: string; + Output: string; + }; + }; + DefaultFieldNullability: false; + DrizzleRelations: typeof relations; } /** * The GraphQL schema builder. */ -export const builder = new SchemaBuilder({ - plugins: [DrizzlePlugin], +export const builder = new SchemaBuilder({ + plugins: [DrizzlePlugin, RelayPlugin], defaultFieldNullability: false, drizzle: { client(ctx) { @@ -61,4 +78,15 @@ export const builder = new SchemaBuilder({ }, }); +builder.addScalarType("DateTime", DateTimeResolver); + +builder.scalarType("Email", { + serialize: (v) => normalizeEmail(v), + parseValue: (v) => normalizeEmail(String(v)), +}); + +builder.addScalarType("UUID", UUIDResolver); + +export const Node = builder.nodeInterfaceRef(); + export default builder; diff --git a/packages/graphql/src/instance.ts b/packages/graphql/src/instance.ts index b8126b6..c74d622 100644 --- a/packages/graphql/src/instance.ts +++ b/packages/graphql/src/instance.ts @@ -15,17 +15,23 @@ // along with this program. If not, see . import builder from "./builder.ts"; -export interface Instance { - slug: string; -} - -export const InstanceRef = builder.objectRef("Instance"); - -InstanceRef.implement({ - description: "An ActivityPub server instance.", - fields(t) { - return { - slug: t.exposeString("slug"), - }; +export const Instance = builder.drizzleNode("instances", { + name: "Instance", + id: { + column(instance) { + return instance.id; + }, + description: "The unique identifier of the `Instance`.", }, + fields: (t) => ({ + slug: t.exposeString("slug"), + expires: t.expose("expires", { + type: "DateTime", + description: "The expiration date/time of the `Instance`.", + }), + created: t.expose("created", { + type: "DateTime", + description: "The creation date/time of the `Instance`.", + }), + }), }); diff --git a/packages/graphql/src/schema.ts b/packages/graphql/src/schema.ts index af0767a..7acc9e1 100644 --- a/packages/graphql/src/schema.ts +++ b/packages/graphql/src/schema.ts @@ -13,18 +13,11 @@ // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . +import "./account.ts"; +import "./instance.ts"; import builder from "./builder.ts"; -import { InstanceRef } from "./instance.ts"; -builder.queryType({ - fields: (t) => ({ - instances: t.field({ - type: [InstanceRef], - resolve() { - return [{ slug: "foo" }, { slug: "bar" }]; - }, - }), - }), -}); +builder.queryType({}); +// builder.mutationType({}); export const schema = builder.toSchema(); diff --git a/packages/models/src/email.ts b/packages/models/src/email.ts new file mode 100644 index 0000000..2ab6894 --- /dev/null +++ b/packages/models/src/email.ts @@ -0,0 +1,75 @@ +// DrFed: A web-based platform for developing and debugging ActivityPub apps +// Copyright (C) 2026 DrFed team +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +/** + * Normalizes an email address by trimming whitespace, converting the host + * to lowercase (and to punycode if necessary), and ensuring it has a + * single "@" character. If the email is invalid, it throws a `TypeError`. + * @param email The email address to normalize. + * @returns The normalized email address. + */ +export function normalizeEmail(email: string): string; + +/** + * Normalizes an email address by trimming whitespace, converting the host + * to lowercase (and to punycode if necessary), and ensuring it has a + * single "@" character. If the email is invalid, it throws a `TypeError`. + * @param email The email address to normalize. + * @returns The normalized email address. If the input is `null`, + * it returns `null`. + */ +export function normalizeEmail(email: string | null): string | null; + +/** + * Normalizes an email address by trimming whitespace, converting the host + * to lowercase (and to punycode if necessary), and ensuring it has a + * single "@" character. If the email is invalid, it throws a `TypeError`. + * @param email The email address to normalize. + * @returns The normalized email address. If the input is `undefined`, + * it returns `undefined`. + */ +export function normalizeEmail(email: string | undefined): string | undefined; + +/** + * Normalizes an email address by trimming whitespace, converting the host + * to lowercase (and to punycode if necessary), and ensuring it has a + * single "@" character. If the email is invalid, it throws a `TypeError`. + * @param email The email address to normalize. + * @returns The normalized email address. If the input is `undefined`, + * it returns `undefined`. If the input is `null`, it returns `null`. + */ +export function normalizeEmail( + email: string | null | undefined, +): string | null | undefined; + +export function normalizeEmail( + email: string | null | undefined, +): string | null | undefined { + if (typeof email === "undefined") return undefined; + else if (email == null) return null; + const [local, host, shouldNotExist] = email.trim().split("@"); + if ( + local == null || + local.trim() === "" || + host == null || + host.trim() === "" || + shouldNotExist != null + ) { + throw new TypeError("Invalid email format."); + } + const normalizedHost = new URL(`https://${host}/`).host; + return `${local}@${normalizedHost}`; +} diff --git a/packages/models/src/index.ts b/packages/models/src/index.ts index c221231..b69235a 100644 --- a/packages/models/src/index.ts +++ b/packages/models/src/index.ts @@ -13,6 +13,7 @@ // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . +export * from "./email.ts"; export * from "./db.ts"; export * from "./migrate.ts"; export { relations } from "./relations.ts"; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9777f7e..89128cd 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -66,12 +66,18 @@ importers: '@pothos/plugin-drizzle': specifier: ^0.17.4 version: 0.17.4(@pothos/core@4.13.0(graphql@16.14.2))(drizzle-orm@1.0.0-beta.22(@electric-sql/pglite@0.5.3)(@types/pg@8.20.0)(pg@8.21.0))(graphql@16.14.2) + '@pothos/plugin-relay': + specifier: ^4.7.0 + version: 4.7.0(@pothos/core@4.13.0(graphql@16.14.2))(graphql@16.14.2) drizzle-orm: specifier: 'catalog:' version: 1.0.0-beta.22(@electric-sql/pglite@0.5.3)(@types/pg@8.20.0)(pg@8.21.0) graphql: specifier: ^16.14.2 version: 16.14.2 + graphql-scalars: + specifier: ^1.25.0 + version: 1.25.0(graphql@16.14.2) graphql-yoga: specifier: ^5.21.2 version: 5.21.2(graphql@16.14.2) @@ -410,6 +416,12 @@ packages: drizzle-orm: '>=1.0.0-beta.2' graphql: ^16.10.0 + '@pothos/plugin-relay@4.7.0': + resolution: {integrity: sha512-IQ7f7WLu7uXxiZBcJgUTdspjIVzX3bgvd8XjnzxGJUbn/uE5HlVyCFmvhJffHIIS2OKSHhOOJRMwIwVwauwnSg==} + peerDependencies: + '@pothos/core': '*' + graphql: ^16.10.0 + '@quansync/fs@1.0.0': resolution: {integrity: sha512-4TJ3DFtlf1L5LDMaM6CanJ/0lckGNtJcMjQ1NAV6zDmA0tEHKZtxNKin8EgPaVX1YzljbxckyT2tJrpQKAtngQ==} @@ -735,6 +747,12 @@ packages: resolution: {integrity: sha512-/6gFNr0N04nob252sTQxyFLi3eKFRqIg1I87YcqAMT1i6SQrSF6KujUEQrtrjMV0H/eejTCltLdDSTEMzHbnsQ==} engines: {node: '>=20.20.0'} + graphql-scalars@1.25.0: + resolution: {integrity: sha512-b0xyXZeRFkne4Eq7NAnL400gStGqG/Sx9VqX0A05nHyEbv57UJnWKsjNnrpVqv5e/8N1MUxkt0wwcRXbiyKcFg==} + engines: {node: '>=10'} + peerDependencies: + graphql: ^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 + graphql-yoga@5.21.2: resolution: {integrity: sha512-IIRF/3xtjj2D6caAWL9177hQ8tV3mWB3hve1GRnz7njPhQ3iY1jFtSp98fNGv0yV9kaPh9kKQ8JWdJZnedVmDw==} engines: {node: '>=18.0.0'} @@ -1182,6 +1200,11 @@ snapshots: drizzle-orm: 1.0.0-beta.22(@electric-sql/pglite@0.5.3)(@types/pg@8.20.0)(pg@8.21.0) graphql: 16.14.2 + '@pothos/plugin-relay@4.7.0(@pothos/core@4.13.0(graphql@16.14.2))(graphql@16.14.2)': + dependencies: + '@pothos/core': 4.13.0(graphql@16.14.2) + graphql: 16.14.2 + '@quansync/fs@1.0.0': dependencies: quansync: 1.0.0 @@ -1372,6 +1395,11 @@ snapshots: dependencies: resolve-pkg-maps: 1.0.0 + graphql-scalars@1.25.0(graphql@16.14.2): + dependencies: + graphql: 16.14.2 + tslib: 2.8.1 + graphql-yoga@5.21.2(graphql@16.14.2): dependencies: '@envelop/core': 5.5.1 From 211bc808e51d8abd4ffc89fcc5ff0d30627e6179 Mon Sep 17 00:00:00 2001 From: Hong Minhee Date: Sat, 20 Jun 2026 23:39:35 +0900 Subject: [PATCH 03/10] Rewrite dev script in TypeScript with better process mgmt Replace the Nushell-based dev task with a Node.js/TypeScript script (scripts/dev.mts) that provides more robust process management: - Remove dist directories before starting builds - Spawn tsdown in watch mode across all packages - Poll until all packages have built their dist directories - Start the drfed server once builds are ready - Propagate SIGINT/SIGTERM gracefully to child process groups, with a 5-second timeout before force-killing - Handle double Ctrl+C with an immediate forced kill - Cross-platform support (Windows taskkill, Unix process groups) Also handle SIGINT (in addition to SIGTERM) in the server's main() to ensure clean shutdown when running under node --watch. Assisted-by: Codex:gpt-5.5 --- mise.toml | 17 +-- packages/drfed/src/index.ts | 6 +- scripts/dev.mts | 226 ++++++++++++++++++++++++++++++++++++ 3 files changed, 231 insertions(+), 18 deletions(-) create mode 100644 scripts/dev.mts diff --git a/mise.toml b/mise.toml index 4a046dc..f3c07d3 100644 --- a/mise.toml +++ b/mise.toml @@ -61,19 +61,4 @@ pnpm exec drizzle-kit generate ...$name ...$custom [tasks.dev] description = "Run the development server" -run = """ -#!/usr/bin/env nu -rm -rf packages/*/dist/ -job spawn { pnpm --parallel --recursive exec tsdown --watch --no-clean } -loop { - let $allGenerated = ls packages - | each { |entry| - not ($entry.name | path join dist | path exists) or ( - ls ($entry.name | path join dist) | is-empty) - } - | all { |empty| not $empty } - if $allGenerated { break } -} -cd packages/drfed/ -node --watch bin/drfed-server.mjs --pglite-data-path ../../.pgdata -""" +run = "node scripts/dev.mts" diff --git a/packages/drfed/src/index.ts b/packages/drfed/src/index.ts index 76e874a..5c938f1 100644 --- a/packages/drfed/src/index.ts +++ b/packages/drfed/src/index.ts @@ -44,8 +44,10 @@ export async function main() { manual: true, fetch: yogaServer.fetch.bind(yogaServer), }); - process.on("SIGTERM", () => { + const shutdown = () => { server.close().then(() => process.exit(0)); - }); + }; + process.once("SIGINT", shutdown); + process.once("SIGTERM", shutdown); await server.serve(); } diff --git a/scripts/dev.mts b/scripts/dev.mts new file mode 100644 index 0000000..97a8d88 --- /dev/null +++ b/scripts/dev.mts @@ -0,0 +1,226 @@ +// DrFed: A web-based platform for developing and debugging ActivityPub apps +// Copyright (C) 2026 DrFed team +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . +import { spawn, type ChildProcess } from "node:child_process"; +import { existsSync } from "node:fs"; +import { readdir, rm } from "node:fs/promises"; +import { dirname, join } from "node:path"; +import process from "node:process"; +import { fileURLToPath } from "node:url"; + +interface ExitResult { + readonly code: number | null; + readonly signal: NodeJS.Signals | null; + readonly error?: Error; +} + +interface ShutdownOptions { + readonly skipServer?: boolean; +} + +const root = join(dirname(fileURLToPath(import.meta.url)), ".."); +const packagesDir = join(root, "packages"); +const isWindows = process.platform === "win32"; +const pnpm = isWindows ? "pnpm.cmd" : "pnpm"; + +let buildProcess: ChildProcess | undefined; +let serverProcess: ChildProcess | undefined; +let shuttingDown = false; + +process.on("SIGINT", () => { + void shutdown(0, "SIGINT"); +}); +process.on("SIGTERM", () => { + void shutdown(143, "SIGTERM"); +}); + +try { + await removeDistDirs(); + + buildProcess = spawnManaged( + pnpm, + ["--parallel", "--recursive", "exec", "tsdown", "--watch", "--no-clean"], + root, + ); + const buildExit = waitForExit(buildProcess); + + await waitForBuilds(buildExit); + + serverProcess = spawnManaged( + process.execPath, + ["--watch", "bin/drfed-server.mjs", "--pglite-data-path", "../../.pgdata"], + join(root, "packages", "drfed"), + ); + const serverExit = await waitForExit(serverProcess); + if (serverExit.error != null) throw serverExit.error; + const exitCode = serverExit.code ?? signalExitCode(serverExit.signal) ?? 1; + await shutdown(exitCode, "SIGTERM", { skipServer: true }); +} catch (error) { + console.error(error instanceof Error ? error.message : error); + await shutdown(1, "SIGTERM"); +} + +async function removeDistDirs() { + const packages = await readdir(packagesDir, { withFileTypes: true }); + await Promise.all( + packages + .filter((entry) => entry.isDirectory()) + .map((entry) => + rm(join(packagesDir, entry.name, "dist"), { + force: true, + recursive: true, + }), + ), + ); +} + +function spawnManaged( + command: string, + args: readonly string[], + cwd: string, +): ChildProcess { + return spawn(command, args, { + cwd, + detached: !isWindows, + env: process.env, + stdio: "inherit", + windowsHide: true, + }); +} + +async function waitForBuilds(buildExit: Promise): Promise { + let buildExitResult: ExitResult | undefined; + buildExit.then((result) => { + buildExitResult = result; + }); + + while (true) { + if (buildExitResult != null) { + if (buildExitResult.error != null) throw buildExitResult.error; + const exitCode = + buildExitResult.code ?? signalExitCode(buildExitResult.signal); + throw new Error( + `Build watcher exited before startup completed: ${exitCode}`, + ); + } + + const packages = await readdir(packagesDir, { withFileTypes: true }); + const allGenerated = await Promise.all( + packages + .filter((entry) => entry.isDirectory()) + .map(async (entry) => { + const distDir = join(packagesDir, entry.name, "dist"); + if (!existsSync(distDir)) return false; + const entries = await readdir(distDir); + return entries.length > 0; + }), + ); + if (allGenerated.every(Boolean)) return; + + await sleep(100); + } +} + +async function shutdown( + exitCode: number, + signal: NodeJS.Signals, + options: ShutdownOptions = {}, +): Promise { + if (shuttingDown) { + forceKill(serverProcess); + forceKill(buildProcess); + process.exit(exitCode); + } + shuttingDown = true; + + await Promise.all([ + options.skipServer + ? Promise.resolve() + : terminate(serverProcess, signal === "SIGINT" ? "SIGINT" : "SIGTERM"), + terminate(buildProcess, "SIGTERM"), + ]); + process.exit(exitCode); +} + +function waitForExit(child: ChildProcess): Promise { + return new Promise((resolve) => { + child.once("exit", (code, signal) => resolve({ code, signal })); + child.once("error", (error) => resolve({ code: 1, error })); + }); +} + +function terminate( + child: ChildProcess | undefined, + signal: NodeJS.Signals, +): Promise { + if (child == null || child.exitCode != null || child.signalCode != null) { + return Promise.resolve(); + } + + return new Promise((resolve) => { + const timeout = setTimeout(() => { + forceKill(child); + }, 5000); + + child.once("exit", () => { + clearTimeout(timeout); + resolve(); + }); + killTree(child, signal); + }); +} + +function killTree(child: ChildProcess, signal: NodeJS.Signals): void { + try { + if (isWindows) { + child.kill(signal); + } else { + if (child.pid != null) process.kill(-child.pid, signal); + } + } catch (error) { + if (error?.code !== "ESRCH") child.kill(signal); + } +} + +function forceKill(child: ChildProcess | undefined): void { + if (child == null || child.exitCode != null || child.signalCode != null) + return; + + if (isWindows) { + spawn("taskkill", ["/pid", String(child.pid), "/t", "/f"], { + stdio: "ignore", + windowsHide: true, + }); + return; + } + + try { + if (child.pid != null) process.kill(-child.pid, "SIGKILL"); + } catch (error) { + if (error?.code !== "ESRCH") child.kill("SIGKILL"); + } +} + +function signalExitCode(signal: NodeJS.Signals | null): number | undefined { + if (signal === "SIGINT") return 130; + if (signal === "SIGTERM") return 143; + return undefined; +} + +function sleep(ms: number): Promise { + return new Promise((resolve) => { + setTimeout(resolve, ms); + }); +} From 54570cf7bc607848350fe163e12e0ed02f0df425 Mon Sep 17 00:00:00 2001 From: Hong Minhee Date: Sun, 21 Jun 2026 00:00:47 +0900 Subject: [PATCH 04/10] Add contribution guide Document the DrFed development workflow, including mise-based tasks, package boundaries, AGPL source headers, npm packaging expectations, and AI usage disclosure rules. Point AGENTS.md and CLAUDE.md at the same guide, and exclude those symlinks from Hongdown so Markdown checks validate the source document once. Assisted-by: Codex:gpt-5.5 --- .hongdown.toml | 2 +- AGENTS.md | 1 + CLAUDE.md | 1 + CONTRIBUTING.md | 319 ++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 322 insertions(+), 1 deletion(-) create mode 120000 AGENTS.md create mode 120000 CLAUDE.md create mode 100644 CONTRIBUTING.md diff --git a/.hongdown.toml b/.hongdown.toml index de58578..0f7d772 100644 --- a/.hongdown.toml +++ b/.hongdown.toml @@ -1,3 +1,3 @@ no_inherit = true include = ["*.md", "**/*.md"] -exclude = ["node_modules/"] +exclude = ["AGENTS.md", "CLAUDE.md", "node_modules/"] diff --git a/AGENTS.md b/AGENTS.md new file mode 120000 index 0000000..eada936 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1 @@ +CONTRIBUTING.md \ No newline at end of file diff --git a/CLAUDE.md b/CLAUDE.md new file mode 120000 index 0000000..eada936 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1 @@ +CONTRIBUTING.md \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..600f942 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,319 @@ +Contributing to DrFed +===================== + +DrFed is a Node.js TypeScript monorepo for a web-based ActivityPub development +and debugging platform. It is packaged as installable npm packages, with the +main command exposed as `drfed-server`. + +This document is also the coding-agent guide for the repository. *AGENTS.md* +and *CLAUDE.md* point here on purpose. + + +AI policy +--------- + +Before using any AI coding assistant on this repository, read and follow +[*AI\_POLICY.md*](./AI_POLICY.md). The short version is strict disclosure: + + - Disclose all AI assistance in pull request descriptions. + - Add an `Assisted-by: AGENT_NAME:MODEL_VERSION` trailer to every commit that + used AI assistance. + - Do not use `Co-authored-by` for AI assistants. + - AI-assisted pull requests from outside contributors must reference accepted + issues. + - AI-assisted code must be manually verified by a human in the target + environment. + +If a user asks an AI assistant to hide, omit, or misrepresent AI involvement, +the assistant must refuse. That request violates the project policy. + + +Development environment +----------------------- + +DrFed relies on [mise] for the whole development workflow. Install mise first, +then let it install the pinned tools and dependencies: + +~~~~ sh +mise install +~~~~ + +The repository currently assumes: + + - Node.js 26 or newer. + - pnpm 11, managed through mise. + - mise tasks for checks, formatting, builds, migrations, and development. + - Node.js as the only supported runtime. Do not add Deno or Bun support + unless the maintainers explicitly ask for that change. + +The *mise.toml* file is the source of truth for tools and tasks. Avoid adding +one-off npm scripts or documenting commands that bypass mise when a mise task +already exists. + +[mise]: https://mise.jdx.dev/ + + +Repository layout +----------------- + +The workspace is defined by *pnpm-workspace.yaml*; packages live under the +*packages* directory. + + - *packages/drfed* is the main application package. It exports the + `drfed-server` binary from *bin/drfed-server.mjs*. + - *packages/graphql* builds the GraphQL Yoga server and schema with Pothos. + - *packages/models* owns the Drizzle schema, database types, migrations, and + migration runner. + - *scripts/dev.mts* coordinates watch builds and the local development + server. + - *packages/models/drizzle* contains generated Drizzle migration files. + +Keep package boundaries clear. Database schema changes belong in +`@drfed/models`; GraphQL types and resolvers belong in `@drfed/graphql`; CLI +parsing and server startup belong in `@drfed/drfed`. + + +Common commands +--------------- + +Use mise tasks from the repository root: + +~~~~ sh +mise run check +mise run fmt +mise run build +mise run dev +~~~~ + +`mise run check` runs all checks currently configured in *mise.toml*: + + - TypeScript/JavaScript formatting with `oxfmt --check`. + - Markdown formatting with `hongdown --check`. + - *mise.toml* formatting with `mise fmt --check`. + +`mise run fmt` formats TypeScript/JavaScript, Markdown, and *mise.toml*. + +`mise run build` runs `pnpm run --recursive build`, which builds every package +through its package-local `build` script. + +`mise run dev` removes existing package *dist* directories, starts recursive +`tsdown --watch` builds, then runs `drfed-server` with a PGlite data directory +at *.pgdata*. + + +Runtime and packaging expectations +---------------------------------- + +DrFed is installable software. Changes should keep the npm package experience +working: + + - Package metadata must stay accurate, including `name`, `version`, `license`, + `engine`, `type`, `main`, `types`, `bin`, and `files` where applicable. + - Public package entry points should be built into *dist/* by `tsdown`. + - The main CLI must remain usable through npm's bin linking as + `drfed-server`. + - Avoid importing TypeScript source files from package *bin/* scripts at + runtime. The current binary imports *../dist/index.mjs*. + - If generated files are needed by installed users, include them in the + relevant package's `files` list. `@drfed/models` publishes both *dist/* + and *drizzle/* for this reason. + - Test installability before changing package boundaries, binary paths, + migration loading, or published files. + +Use workspace dependencies for internal packages: + +~~~~ json +"@drfed/models": "workspace:*" +~~~~ + +Do not introduce runtime assumptions that only work from the repository root. +Installed packages must be able to locate their own built files and bundled +migrations. + + +Source license headers +---------------------- + +DrFed is licensed under the [GNU Affero General Public License v3]. New source +files must start with the existing AGPL header. For TypeScript, JavaScript, +*.mjs*, and *.mts* files, use this form before imports: + +~~~~ ts +// DrFed: A web-based platform for developing and debugging ActivityPub apps +// Copyright (C) 2026 DrFed team +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . +~~~~ + +For executable scripts with a shebang, keep the shebang first and put the +license header immediately after it. + +[GNU Affero General Public License v3]: https://www.gnu.org/licenses/agpl-3.0.html + + +Code style +---------- + +The codebase uses ESM TypeScript and explicit *.ts* extensions for local source +imports: + +~~~~ ts +import parser from "./parser.ts"; +~~~~ + +Use existing dependencies and patterns before adding new ones. In particular: + + - Use [Optique] for CLI parsing. + - Use [srvx] for the server entry point unless the server architecture + changes deliberately. + - Use [Drizzle ORM] for database schema and queries. + - Use [Pothos] and [GraphQL Yoga] for GraphQL schema and server work. + - Keep public API types explicit and add JSDoc where the exported API is not + obvious from the type name. + +Formatting is handled by [Oxfmt] and [Hongdown]. Do not hand-align code in a +way that fights those tools. + +[Optique]: https://optique.dev/ +[srvx]: https://srvx.h3.dev/ +[Drizzle ORM]: https://orm.drizzle.team/ +[Pothos]: https://pothos-graphql.dev/ +[GraphQL Yoga]: https://the-guild.dev/graphql/yoga-server +[Oxfmt]: https://oxc.rs/docs/guide/usage/formatter.html +[Hongdown]: https://github.com/dahlia/hongdown + + +Database changes +---------------- + +The database layer lives in *packages/models*. + + - Edit tables in *packages/models/src/schema.ts*. + - Edit Drizzle relations in *packages/models/src/relations.ts*. + - Export public database utilities through *packages/models/src/index.ts*. + - Generate migrations after schema changes. + +Generate a migration from the repository root: + +~~~~ sh +mise run generate:migrate --name your_migration_name +~~~~ + +For an empty custom SQL migration: + +~~~~ sh +mise run generate:migrate --custom --name your_migration_name +~~~~ + +Review generated SQL before committing it. Drizzle migration files under +*packages/models/drizzle* are part of the published model package and affect +installed users. + + +GraphQL changes +--------------- + +The GraphQL layer lives in *packages/graphql*. + + - *src/builder.ts* configures Pothos, Drizzle integration, Relay support, and + scalars. + - Domain files such as *src/account.ts* and *src/instance.ts* register object + types and fields. + - *src/schema.ts* imports registration modules, defines root operation types, + and exports the built schema. + +When adding a new object or field, follow the existing `builder.drizzleNode()` +and `t.drizzleField()` patterns. Keep resolver database access through +`ctx.db`. + + +CLI and server changes +---------------------- + +The CLI parser lives in *packages/drfed/src/parser.ts*, the program metadata in +*packages/drfed/src/program.ts*, and startup logic in +*packages/drfed/src/index.ts*. + +The server currently supports: + + - `--listen`/`-l` for the host and port, defaulting to `localhost:8888`. + - `--pglite-data-path`/`--data-path`/`-d` for local PGlite storage. + - `--postgres-url`/`--database-url`/`-D` for PostgreSQL. + - `--no-migrate`/`-M` to disable automatic migrations. + +Keep CLI options explicit and documented through Optique descriptions, because +those descriptions feed the generated help output. + + +Quality bar +----------- + +Before sending a pull request, run: + +~~~~ sh +mise run check +mise run build +~~~~ + +Run `mise run dev` for changes that affect startup, CLI parsing, migration +execution, the GraphQL server, or package build output. Manually verify the +installed CLI behavior when changing package metadata, `bin` entries, build +configuration, or files published to npm. + +There is no dedicated test suite in this repository yet. When adding tests, +keep them runnable from mise and make the command visible in *mise.toml*. + + +Documentation guidance +---------------------- + +Keep documentation short, specific, and tied to the current codebase. Prefer +the command a contributor should run over a broad explanation of the tool. Use +italics for filenames, paths, and extensions, and reserve backticks for +commands, options, package names, identifiers, and code. Run +`mise run fmt:docs` after editing Markdown. + + +Dependency policy +----------------- + +Use pnpm through mise. The lockfile is *pnpm-lock.yaml*; update it whenever +dependencies change. + +Prefer the catalog in *pnpm-workspace.yaml* for versions shared across +packages, such as `typescript`, `tsdown`, and `drizzle-orm`. Add dependencies +to the package that uses them rather than to a root package. + +Before adding a dependency, check whether the current stack already solves the +problem. Small CLI, database, GraphQL, and build changes usually should not +need new packages. + + +Git and contribution notes +-------------------------- + +Keep changes scoped. Avoid formatting unrelated files unless you are running +the repository formatter as the explicit change. + +Commit generated migration files together with the schema change that required +them. Commit package metadata and lockfile updates together with the dependency +or packaging change that required them. + +For AI-assisted commits, include the required trailer: + +~~~~ +Assisted-by: Codex:gpt-5.5 +~~~~ + +Use the actual assistant name and model version. From a9b7774a2daed070f1f79b730fc20f1947bf931d Mon Sep 17 00:00:00 2001 From: Hong Minhee Date: Sun, 21 Jun 2026 00:24:11 +0900 Subject: [PATCH 05/10] Add strict TypeScript configs Add package-level tsconfig files with strict no-emit type checking and allowImportingTsExtensions enabled for local .ts imports. Wire Node 26 types through the pnpm catalog, add the mise type check, and update imports that must be type-only under verbatimModuleSyntax. Also fix the development script's child-process error result typing and process lookup error handling so it passes the stricter checks. --- CONTRIBUTING.md | 1 + mise.toml | 5 +++++ packages/drfed/package.json | 1 + packages/drfed/src/parser.ts | 2 +- packages/drfed/tsconfig.json | 27 +++++++++++++++++++++++++++ packages/graphql/package.json | 1 + packages/graphql/src/index.ts | 7 ++----- packages/graphql/tsconfig.json | 25 +++++++++++++++++++++++++ packages/models/package.json | 1 + packages/models/src/db.ts | 2 +- packages/models/tsconfig.json | 22 ++++++++++++++++++++++ pnpm-lock.yaml | 28 ++++++++++++++++++++-------- pnpm-workspace.yaml | 1 + scripts/dev.mts | 15 ++++++++++++--- 14 files changed, 120 insertions(+), 18 deletions(-) create mode 100644 packages/drfed/tsconfig.json create mode 100644 packages/graphql/tsconfig.json create mode 100644 packages/models/tsconfig.json diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 600f942..401f615 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -87,6 +87,7 @@ mise run dev `mise run check` runs all checks currently configured in *mise.toml*: + - TypeScript type checking with `tsgo --noEmit`. - TypeScript/JavaScript formatting with `oxfmt --check`. - Markdown formatting with `hongdown --check`. - *mise.toml* formatting with `mise fmt --check`. diff --git a/mise.toml b/mise.toml index f3c07d3..8dad570 100644 --- a/mise.toml +++ b/mise.toml @@ -3,6 +3,7 @@ "aqua:oxc-project/oxc/oxfmt" = "1.70.0" "github:nushell/nushell" = "latest" node = "26" +"npm:@typescript/native-preview" = "7.0.0-dev.20260620.1" "npm:pglite-cli" = "latest" pnpm = "11" @@ -16,6 +17,10 @@ postinstall = ["mise deps", "mise generate git-pre-commit --task check --write"] description = "Check all" depends = ["check:*"] +[tasks."check:types"] +description = "Check TypeScript types" +run = "pnpm --recursive exec tsgo --noEmit" + [tasks."check:fmt"] description = "Check formatting" run = "oxfmt --check" diff --git a/packages/drfed/package.json b/packages/drfed/package.json index 408cce7..66ef810 100644 --- a/packages/drfed/package.json +++ b/packages/drfed/package.json @@ -52,6 +52,7 @@ "build": "tsdown" }, "devDependencies": { + "@types/node": "catalog:", "@types/pg": "^8.20.0", "tsdown": "catalog:", "typescript": "catalog:" diff --git a/packages/drfed/src/parser.ts b/packages/drfed/src/parser.ts index 112836a..0a84674 100644 --- a/packages/drfed/src/parser.ts +++ b/packages/drfed/src/parser.ts @@ -17,7 +17,7 @@ import { relations, schema } from "@drfed/models"; import { merge, object, or } from "@optique/core/constructs"; import { message, optionNames } from "@optique/core/message"; import { map, withDefault } from "@optique/core/modifiers"; -import { InferValue } from "@optique/core/parser"; +import type { InferValue } from "@optique/core/parser"; import { option } from "@optique/core/primitives"; import { socketAddress, url } from "@optique/core/valueparser"; import { path } from "@optique/run/valueparser"; diff --git a/packages/drfed/tsconfig.json b/packages/drfed/tsconfig.json new file mode 100644 index 0000000..79bbafc --- /dev/null +++ b/packages/drfed/tsconfig.json @@ -0,0 +1,27 @@ +{ + "compilerOptions": { + "allowImportingTsExtensions": true, + "exactOptionalPropertyTypes": true, + "forceConsistentCasingInFileNames": true, + "isolatedModules": true, + "lib": ["ES2024"], + "module": "NodeNext", + "moduleResolution": "NodeNext", + "noEmit": true, + "noFallthroughCasesInSwitch": true, + "noImplicitOverride": true, + "noImplicitReturns": true, + "noUncheckedIndexedAccess": true, + "paths": { + "@drfed/graphql": ["../graphql/src/index.ts"], + "@drfed/models": ["../models/src/index.ts"] + }, + "resolveJsonModule": true, + "skipLibCheck": true, + "strict": true, + "target": "ES2024", + "types": ["node"], + "verbatimModuleSyntax": true + }, + "include": ["src/**/*.ts"] +} diff --git a/packages/graphql/package.json b/packages/graphql/package.json index 816af19..72013a1 100644 --- a/packages/graphql/package.json +++ b/packages/graphql/package.json @@ -49,6 +49,7 @@ "build": "tsdown" }, "devDependencies": { + "@types/node": "catalog:", "tsdown": "catalog:", "typescript": "catalog:" }, diff --git a/packages/graphql/src/index.ts b/packages/graphql/src/index.ts index 398d529..81d87ff 100644 --- a/packages/graphql/src/index.ts +++ b/packages/graphql/src/index.ts @@ -14,11 +14,8 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . import type { Database } from "@drfed/models"; -import { - createYoga, - useExecutionCancellation, - YogaServerInstance, -} from "graphql-yoga"; +import { createYoga, useExecutionCancellation } from "graphql-yoga"; +import type { YogaServerInstance } from "graphql-yoga"; import type { ServerContext, UserContext } from "./builder.ts"; import { schema } from "./schema.ts"; diff --git a/packages/graphql/tsconfig.json b/packages/graphql/tsconfig.json new file mode 100644 index 0000000..4f8dc42 --- /dev/null +++ b/packages/graphql/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "allowImportingTsExtensions": true, + "exactOptionalPropertyTypes": true, + "forceConsistentCasingInFileNames": true, + "isolatedModules": true, + "lib": ["ES2024"], + "module": "NodeNext", + "moduleResolution": "NodeNext", + "noEmit": true, + "noFallthroughCasesInSwitch": true, + "noImplicitOverride": true, + "noImplicitReturns": true, + "noUncheckedIndexedAccess": true, + "paths": { + "@drfed/models": ["../models/src/index.ts"] + }, + "skipLibCheck": true, + "strict": true, + "target": "ES2024", + "types": ["node"], + "verbatimModuleSyntax": true + }, + "include": ["src/**/*.ts"] +} diff --git a/packages/models/package.json b/packages/models/package.json index e3ec59e..64afeea 100644 --- a/packages/models/package.json +++ b/packages/models/package.json @@ -53,6 +53,7 @@ "build": "tsdown" }, "devDependencies": { + "@types/node": "catalog:", "@types/pg": "^8.20.0", "drizzle-kit": "1.0.0-beta.22", "tsdown": "catalog:", diff --git a/packages/models/src/db.ts b/packages/models/src/db.ts index 207748b..c033ebb 100644 --- a/packages/models/src/db.ts +++ b/packages/models/src/db.ts @@ -13,7 +13,7 @@ // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -import { RelationsFilter as RelationsFilterImpl } from "drizzle-orm"; +import type { RelationsFilter as RelationsFilterImpl } from "drizzle-orm"; import type { PgAsyncDatabase, PgAsyncTransaction, diff --git a/packages/models/tsconfig.json b/packages/models/tsconfig.json new file mode 100644 index 0000000..6e88b43 --- /dev/null +++ b/packages/models/tsconfig.json @@ -0,0 +1,22 @@ +{ + "compilerOptions": { + "allowImportingTsExtensions": true, + "exactOptionalPropertyTypes": true, + "forceConsistentCasingInFileNames": true, + "isolatedModules": true, + "lib": ["ES2024"], + "module": "NodeNext", + "moduleResolution": "NodeNext", + "noEmit": true, + "noFallthroughCasesInSwitch": true, + "noImplicitOverride": true, + "noImplicitReturns": true, + "noUncheckedIndexedAccess": true, + "skipLibCheck": true, + "strict": true, + "target": "ES2024", + "types": ["node"], + "verbatimModuleSyntax": true + }, + "include": ["src/**/*.ts", "drizzle.config.ts"] +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 89128cd..fdcf570 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -6,6 +6,9 @@ settings: catalogs: default: + '@types/node': + specifier: ^26.0.0 + version: 26.0.0 drizzle-orm: specifier: 1.0.0-beta.22 version: 1.0.0-beta.22 @@ -45,6 +48,9 @@ importers: specifier: ^0.11.16 version: 0.11.16 devDependencies: + '@types/node': + specifier: 'catalog:' + version: 26.0.0 '@types/pg': specifier: ^8.20.0 version: 8.20.0 @@ -82,6 +88,9 @@ importers: specifier: ^5.21.2 version: 5.21.2(graphql@16.14.2) devDependencies: + '@types/node': + specifier: 'catalog:' + version: 26.0.0 tsdown: specifier: 'catalog:' version: 0.22.3(typescript@6.0.3) @@ -101,6 +110,9 @@ importers: specifier: ^8.21.0 version: 8.21.0 devDependencies: + '@types/node': + specifier: 'catalog:' + version: 26.0.0 '@types/pg': specifier: ^8.20.0 version: 8.20.0 @@ -535,8 +547,8 @@ packages: '@types/jsesc@2.5.1': resolution: {integrity: sha512-9VN+6yxLOPLOav+7PwjZbxiID2bVaeq0ED4qSQmdQTdjnXJSaCVKTR58t15oqH1H5t8Ng2ZX1SabJVoN9Q34bw==} - '@types/node@25.9.3': - resolution: {integrity: sha512-603BddQMv3pUcr4U2dhujk83N2tTDVr/34wII2B6bJy6g+8WD6yUb11jszNs0gdi4PesVWl7ABt8nYMVpnLUcg==} + '@types/node@26.0.0': + resolution: {integrity: sha512-vf2YFi1iY9lHGwNJMs01biZFbKJkrZR1T6/MlzjhJLPdntOHLhTrDSnSVcdtvjihi4VQNlrFRIxLsDBlQpAipA==} '@types/pg@8.20.0': resolution: {integrity: sha512-bEPFOaMAHTEP1EzpvHTbmwR8UsFyHSKsRisLIHVMXnpNefSbGA1bD6CVy+qKjGSqmZqNqBDV2azOBo8TgkcVow==} @@ -947,8 +959,8 @@ packages: unconfig-core@7.5.0: resolution: {integrity: sha512-Su3FauozOGP44ZmKdHy2oE6LPjk51M/TRRjHv2HNCWiDvfvCoxC2lno6jevMA91MYAdCdwP05QnWdWpSbncX/w==} - undici-types@7.24.6: - resolution: {integrity: sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==} + undici-types@8.3.0: + resolution: {integrity: sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==} urlpattern-polyfill@10.1.0: resolution: {integrity: sha512-IGjKp/o0NL3Bso1PymYURCJxMPNAf/ILOpendP9f5B6e1rTJgdgiOvgfoT8VxCAdY+Wisb9uhGaJJf3yZ2V9nw==} @@ -1271,13 +1283,13 @@ snapshots: '@types/jsesc@2.5.1': {} - '@types/node@25.9.3': + '@types/node@26.0.0': dependencies: - undici-types: 7.24.6 + undici-types: 8.3.0 '@types/pg@8.20.0': dependencies: - '@types/node': 25.9.3 + '@types/node': 26.0.0 pg-protocol: 1.14.0 pg-types: 2.2.0 @@ -1571,7 +1583,7 @@ snapshots: '@quansync/fs': 1.0.0 quansync: 1.0.0 - undici-types@7.24.6: {} + undici-types@8.3.0: {} urlpattern-polyfill@10.1.0: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 01b41fe..b4b4756 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -5,6 +5,7 @@ allowBuilds: esbuild: true catalog: + "@types/node": ^26.0.0 drizzle-orm: 1.0.0-beta.22 tsdown: ^0.22.3 typescript: ^6.0.3 diff --git a/scripts/dev.mts b/scripts/dev.mts index 97a8d88..29cd906 100644 --- a/scripts/dev.mts +++ b/scripts/dev.mts @@ -157,7 +157,7 @@ async function shutdown( function waitForExit(child: ChildProcess): Promise { return new Promise((resolve) => { child.once("exit", (code, signal) => resolve({ code, signal })); - child.once("error", (error) => resolve({ code: 1, error })); + child.once("error", (error) => resolve({ code: 1, signal: null, error })); }); } @@ -190,7 +190,7 @@ function killTree(child: ChildProcess, signal: NodeJS.Signals): void { if (child.pid != null) process.kill(-child.pid, signal); } } catch (error) { - if (error?.code !== "ESRCH") child.kill(signal); + if (!isProcessLookupError(error)) child.kill(signal); } } @@ -209,10 +209,19 @@ function forceKill(child: ChildProcess | undefined): void { try { if (child.pid != null) process.kill(-child.pid, "SIGKILL"); } catch (error) { - if (error?.code !== "ESRCH") child.kill("SIGKILL"); + if (!isProcessLookupError(error)) child.kill("SIGKILL"); } } +function isProcessLookupError(error: unknown): boolean { + return ( + typeof error === "object" && + error != null && + "code" in error && + error.code === "ESRCH" + ); +} + function signalExitCode(signal: NodeJS.Signals | null): number | undefined { if (signal === "SIGINT") return 130; if (signal === "SIGTERM") return 143; From 26fe6a49c316b74b4242af41d105b433109401d7 Mon Sep 17 00:00:00 2001 From: Hong Minhee Date: Sun, 21 Jun 2026 11:33:07 +0900 Subject: [PATCH 06/10] Specify mise's min_version https://mise.jdx.dev/configuration.html#minimum-mise-version https://github.com/fedify-dev/drfed/pull/14#discussion_r3447385268 https://github.com/fedify-dev/drfed/pull/14#discussion_r3447388127 --- CONTRIBUTING.md | 3 ++- mise.toml | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 401f615..8142fcd 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -40,7 +40,8 @@ mise install The repository currently assumes: - - Node.js 26 or newer. + - mise 2026.6.10 or newer. + - Node.js 26 or newer, managed through mise. - pnpm 11, managed through mise. - mise tasks for checks, formatting, builds, migrations, and development. - Node.js as the only supported runtime. Do not add Deno or Bun support diff --git a/mise.toml b/mise.toml index 8dad570..143a5f4 100644 --- a/mise.toml +++ b/mise.toml @@ -1,3 +1,5 @@ +min_version = "2026.6.10" + [tools] "aqua:dahlia/hongdown" = "0.4.3" "aqua:oxc-project/oxc/oxfmt" = "1.70.0" From 73567a5dbf187d57e2044a82c0574128c342576f Mon Sep 17 00:00:00 2001 From: Hong Minhee Date: Sun, 21 Jun 2026 11:41:09 +0900 Subject: [PATCH 07/10] Add some missing docs https://github.com/fedify-dev/drfed/pull/14#discussion_r3447466421 https://github.com/fedify-dev/drfed/pull/14#discussion_r3447469964 --- packages/graphql/src/account.ts | 1 + packages/graphql/src/instance.ts | 1 + packages/models/src/schema.ts | 9 +++++++++ 3 files changed, 11 insertions(+) diff --git a/packages/graphql/src/account.ts b/packages/graphql/src/account.ts index 68743a4..8bd3821 100644 --- a/packages/graphql/src/account.ts +++ b/packages/graphql/src/account.ts @@ -17,6 +17,7 @@ import builder from "./builder.ts"; export const Account = builder.drizzleNode("accounts", { name: "Account", + description: "Represents an `Account` in the DrFed platform.", id: { column(account) { return account.id; diff --git a/packages/graphql/src/instance.ts b/packages/graphql/src/instance.ts index c74d622..e73bd5c 100644 --- a/packages/graphql/src/instance.ts +++ b/packages/graphql/src/instance.ts @@ -17,6 +17,7 @@ import builder from "./builder.ts"; export const Instance = builder.drizzleNode("instances", { name: "Instance", + description: "Represents an `Instance` in the DrFed platform.", id: { column(instance) { return instance.id; diff --git a/packages/models/src/schema.ts b/packages/models/src/schema.ts index d41b705..2bc4416 100644 --- a/packages/models/src/schema.ts +++ b/packages/models/src/schema.ts @@ -16,6 +16,9 @@ import { sql } from "drizzle-orm"; import { uuid, varchar, pgTable, check, timestamp } from "drizzle-orm/pg-core"; +/** + * The database table to represent accounts. + */ export const accounts = pgTable( "accounts", { @@ -33,6 +36,9 @@ export const accounts = pgTable( ], ); +/** + * The database table to represent instances. + */ export const instances = pgTable( "instances", { @@ -52,6 +58,9 @@ export const instances = pgTable( ], ); +/** + * The association table between instances and its member accounts. + */ export const instanceMembers = pgTable("instance_members", { instanceId: uuid() .notNull() From 737e456224925925a444c04e4d76f6947cf8a876 Mon Sep 17 00:00:00 2001 From: Hong Minhee Date: Sun, 21 Jun 2026 12:08:14 +0900 Subject: [PATCH 08/10] VS Code settings and use proper version of oxfmt https://github.com/fedify-dev/drfed/pull/14#discussion_r3447317179 --- .vscode/extensions.json | 3 +++ .vscode/settings.json | 48 +++++++++++++++++++++++++++++++++++++++++ .zed/settings.json | 18 ++++++++-------- mise.toml | 2 +- 4 files changed, 61 insertions(+), 10 deletions(-) create mode 100644 .vscode/extensions.json create mode 100755 .vscode/settings.json diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..99e2f7d --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,3 @@ +{ + "recommendations": ["oxc.oxc-vscode"] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100755 index 0000000..377d71c --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,48 @@ +{ + "biome.enabled": false, + "editor.detectIndentation": false, + "editor.indentSize": 2, + "editor.insertSpaces": true, + "editor.rulers": [80], + "files.eol": "\n", + "files.insertFinalNewline": true, + "files.trimFinalNewlines": true, + "oxc.enable.oxfmt": true, + "prettier.enable": false, + "[css]": { + "editor.defaultFormatter": "oxc.oxc-vscode", + "editor.formatOnSave": true + }, + "[graphql]": { + "editor.defaultFormatter": "oxc.oxc-vscode", + "editor.formatOnSave": true + }, + "[html]": { + "editor.defaultFormatter": "oxc.oxc-vscode", + "editor.formatOnSave": true + }, + "[javascript]": { + "editor.defaultFormatter": "oxc.oxc-vscode", + "editor.formatOnSave": true + }, + "[json]": { + "editor.defaultFormatter": "oxc.oxc-vscode", + "editor.formatOnSave": true + }, + "[jsonc]": { + "editor.defaultFormatter": "oxc.oxc-vscode", + "editor.formatOnSave": true + }, + "[typescript]": { + "editor.defaultFormatter": "oxc.oxc-vscode", + "editor.formatOnSave": true + }, + "[tsx]": { + "editor.defaultFormatter": "oxc.oxc-vscode", + "editor.formatOnSave": true + }, + "[yaml]": { + "editor.defaultFormatter": "oxc.oxc-vscode", + "editor.formatOnSave": true + } +} diff --git a/.zed/settings.json b/.zed/settings.json index 91c31e2..7c42f8b 100644 --- a/.zed/settings.json +++ b/.zed/settings.json @@ -89,6 +89,15 @@ } ] }, + "Markdown": { + "format_on_save": "on", + "formatter": { + "external": { + "command": "hongdown", + "arguments": ["-"] + } + } + }, "TypeScript": { "format_on_save": "on", "prettier": { @@ -115,15 +124,6 @@ } ] }, - "Markdown": { - "format_on_save": "on", - "formatter": { - "external": { - "command": "hongdown", - "arguments": ["-"] - } - } - }, "YAML": { "format_on_save": "on", "prettier": { diff --git a/mise.toml b/mise.toml index 143a5f4..cc1bc26 100644 --- a/mise.toml +++ b/mise.toml @@ -2,11 +2,11 @@ min_version = "2026.6.10" [tools] "aqua:dahlia/hongdown" = "0.4.3" -"aqua:oxc-project/oxc/oxfmt" = "1.70.0" "github:nushell/nushell" = "latest" node = "26" "npm:@typescript/native-preview" = "7.0.0-dev.20260620.1" "npm:pglite-cli" = "latest" +oxfmt = "0.55.0" pnpm = "11" [deps.pnpm] From 6205338705d1a848e6fad8ab192529b4d9a7af0f Mon Sep 17 00:00:00 2001 From: Hong Minhee Date: Sun, 21 Jun 2026 12:18:24 +0900 Subject: [PATCH 09/10] Add README files and Packages section to CONTRIBUTING.md Added a concise README.md to each of the three packages: - packages/drfed/README.md: documents the CLI options, server startup behavior, and source file layout for @drfed/drfed. - packages/graphql/README.md: documents the custom scalars, source layout, and createYogaServer usage for @drfed/graphql. - packages/models/README.md: documents the database schema, source layout, and migration generation for @drfed/models. Added a Packages section to CONTRIBUTING.md with a table summarising each package's directory path, npm name, and one-line description. Also added README.md to the files list in each package.json so the documentation is included in published npm packages. Assisted-by: Claude Code:claude-sonnet-4-6 --- CONTRIBUTING.md | 12 ++++++++++++ packages/drfed/README.md | 37 +++++++++++++++++++++++++++++++++++ packages/drfed/package.json | 5 +++++ packages/graphql/README.md | 33 +++++++++++++++++++++++++++++++ packages/graphql/package.json | 4 ++++ packages/models/README.md | 30 ++++++++++++++++++++++++++++ packages/models/package.json | 3 ++- 7 files changed, 123 insertions(+), 1 deletion(-) create mode 100644 packages/drfed/README.md create mode 100644 packages/graphql/README.md create mode 100644 packages/models/README.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8142fcd..b2bcde5 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -74,6 +74,18 @@ Keep package boundaries clear. Database schema changes belong in parsing and server startup belong in `@drfed/drfed`. +Packages +-------- + +| Package | npm name | Description | +| ------------------ | ---------------- | ----------------------------------------------- | +| *packages/drfed* | `@drfed/drfed` | CLI binary, server startup, and HTTP serving | +| *packages/graphql* | `@drfed/graphql` | GraphQL schema and Yoga server (Pothos + Relay) | +| *packages/models* | `@drfed/models` | Drizzle schema, relations, and migration runner | + +Each package has its own *README.md* with a more detailed breakdown. + + Common commands --------------- diff --git a/packages/drfed/README.md b/packages/drfed/README.md new file mode 100644 index 0000000..82d95f9 --- /dev/null +++ b/packages/drfed/README.md @@ -0,0 +1,37 @@ +@drfed/drfed +============ + +The main application package for [DrFed], a web-based platform for developing +and debugging ActivityPub apps. It wires together the database layer, GraphQL +server, and HTTP server, and exposes the `drfed-server` CLI binary. + +[DrFed]: https://drfed.org/ + + +Usage +----- + +~~~~ sh +drfed-server --data-path .pgdata +drfed-server --database-url postgres://localhost/drfed +~~~~ + +The server listens on `localhost:8888` by default. Pass `--listen HOST:PORT` +to override. Automatic database migrations run on startup unless `--no-migrate` +is given. + + +Options +------- + +| Option | Short | Description | +| ------------------------- | ----- | ------------------------------------------------ | +| `--listen HOST:PORT` | `-l` | Address to listen on (default: `localhost:8888)` | +| `--pglite-data-path PATH` | `-d` | Directory for PGlite storage | +| `--postgres-url URL` | `-D` | PostgreSQL connection URL | +| `--no-migrate` | `-M` | Skip automatic migrations | +| `--help` | | Show help | +| `--version` | | Show version | + +`--pglite-data-path` and `--postgres-url` are mutually exclusive. One of them +must be provided. diff --git a/packages/drfed/package.json b/packages/drfed/package.json index 66ef810..ea51029 100644 --- a/packages/drfed/package.json +++ b/packages/drfed/package.json @@ -41,6 +41,11 @@ "type": "module", "main": "dist/index.mjs", "types": "dist/index.d.mts", + "files": [ + "bin/", + "dist/", + "README.md" + ], "bin": { "drfed-server": "bin/drfed-server.mjs" }, diff --git a/packages/graphql/README.md b/packages/graphql/README.md new file mode 100644 index 0000000..f41999c --- /dev/null +++ b/packages/graphql/README.md @@ -0,0 +1,33 @@ +@drfed/graphql +============== + +GraphQL server for [DrFed], built with [Pothos] and [GraphQL Yoga]. Exposes +a Relay-compatible schema backed by Drizzle ORM. + +[DrFed]: https://drfed.org/ +[Pothos]: https://pothos-graphql.dev/ +[GraphQL Yoga]: https://the-guild.dev/graphql/yoga-server + + +Scalars +------- + +| Scalar | Description | +| ---------- | ------------------------- | +| `DateTime` | ISO 8601 timestamp | +| `Email` | Normalized e-mail address | +| `UUID` | RFC 4122 UUID | + + +Usage +----- + +~~~~ ts +import { createYogaServer } from "@drfed/graphql"; + +const yoga = createYogaServer(db); +serve({ fetch: yoga.fetch.bind(yoga) }); +~~~~ + +`createYogaServer` accepts a Drizzle database instance and returns a +GraphQL Yoga server ready to handle HTTP requests. diff --git a/packages/graphql/package.json b/packages/graphql/package.json index 72013a1..7019fad 100644 --- a/packages/graphql/package.json +++ b/packages/graphql/package.json @@ -41,6 +41,10 @@ "type": "module", "main": "dist/index.mjs", "types": "dist/index.d.mts", + "files": [ + "dist/", + "README.md" + ], "tsdown": { "dts": true, "sourcemap": true diff --git a/packages/models/README.md b/packages/models/README.md new file mode 100644 index 0000000..645949a --- /dev/null +++ b/packages/models/README.md @@ -0,0 +1,30 @@ +@drfed/models +============= + +Database schema, Drizzle ORM types, and migration runner for [DrFed]. Supports +both PGlite (embedded) and PostgreSQL. + +[DrFed]: https://drfed.org/ + + +Schema +------ + +| Table | Key columns | +| ------------------ | ---------------------------------------------------- | +| `accounts` | `id` (UUID), `email`, `created` | +| `instances` | `id` (UUID), `slug`, `expires`, `created` | +| `instance_members` | `instanceId` → `instances`, `accountId` → `accounts` | + + +Migrations +---------- + +Generate a migration after changing *src/schema.ts*: + +~~~~ sh +mise run generate:migrate --name your_migration_name +~~~~ + +The *drizzle/* directory is included in the published npm package so that +installed users can run migrations without access to the repository source. diff --git a/packages/models/package.json b/packages/models/package.json index 64afeea..553de0b 100644 --- a/packages/models/package.json +++ b/packages/models/package.json @@ -43,7 +43,8 @@ "types": "dist/index.d.mts", "files": [ "dist/", - "drizzle/" + "drizzle/", + "README.md" ], "tsdown": { "dts": true, From 93625ffbfa21c747b898068c7fb3367a97a17861 Mon Sep 17 00:00:00 2001 From: Hong Minhee Date: Sun, 21 Jun 2026 21:36:32 +0900 Subject: [PATCH 10/10] Clarify the difference between accounts and actors https://github.com/fedify-dev/drfed/pull/14#discussion_r3448306590 --- packages/graphql/src/account.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/graphql/src/account.ts b/packages/graphql/src/account.ts index 8bd3821..36d4f8a 100644 --- a/packages/graphql/src/account.ts +++ b/packages/graphql/src/account.ts @@ -17,7 +17,9 @@ import builder from "./builder.ts"; export const Account = builder.drizzleNode("accounts", { name: "Account", - description: "Represents an `Account` in the DrFed platform.", + description: + "Represents an `Account` in the DrFed platform. " + + "Note that it differs from the ActivityPub `Actor`s that belong to `Instance`s.", id: { column(account) { return account.id;