diff --git a/.oxlintrc.json b/.oxlintrc.json index b027de3..1ff122b 100644 --- a/.oxlintrc.json +++ b/.oxlintrc.json @@ -24,9 +24,9 @@ { "ignoreConsecutiveComments": true } ], "eslint/curly": ["error", "multi-line"], - "node/no-top-level-await": "off", "oxc/no-optional-chaining": "off", "oxc/no-async-await": "off", + "jsdoc/require-param": "off", "eslint/eqeqeq": ["off", "smart"], "eslint/func-style": ["off"], "eslint/max-lines-per-function": "off", @@ -42,6 +42,7 @@ "eslint/no-eq-null": "off", "eslint/no-magic-numbers": "off", "eslint/no-ternary": "off", + "eslint/no-negated-condition": "off", "eslint/no-nested-ternary": "off", "eslint/no-undefined": "off", "eslint/no-void": "off", diff --git a/mise.toml b/mise.toml index 9b51256..54fcbb0 100644 --- a/mise.toml +++ b/mise.toml @@ -7,6 +7,7 @@ experimental = true "aqua:dahlia/hongdown" = "0.4.3" "github:nushell/nushell" = "0.114.1" node = "26" +"npm:@fedify/cli" = "2.4.0-dev.1758" "npm:oxlint" = "1.75.0" "npm:oxlint-tsgolint" = "7.0.2001" "npm:pglite-cli" = "0.0.1" diff --git a/packages/drfed/package.json b/packages/drfed/package.json index 5a440fc..bcb540c 100644 --- a/packages/drfed/package.json +++ b/packages/drfed/package.json @@ -62,7 +62,6 @@ "devDependencies": { "@logtape/testing-node": "catalog:", "@types/node": "catalog:", - "@types/pg": "catalog:", "tsdown": "catalog:", "typescript": "catalog:" }, @@ -70,16 +69,19 @@ "@drfed/graphql": "workspace:*", "@drfed/models": "workspace:*", "@electric-sql/pglite": "catalog:", + "@fedify/fedify": "catalog:", + "@fedify/pglite": "catalog:", + "@fedify/postgres": "catalog:", "@logtape/drizzle-orm": "catalog:", "@logtape/logtape": "catalog:", "@optique/core": "catalog:", "@optique/logtape": "catalog:", "@optique/run": "catalog:", - "@upyo/smtp": "catalog:", "@upyo/logtape": "catalog:", + "@upyo/smtp": "catalog:", "drizzle-orm": "catalog:", "graphql": "catalog:", - "pg": "catalog:", + "postgres": "catalog:", "srvx": "^0.11.16" } } diff --git a/packages/drfed/src/index.ts b/packages/drfed/src/index.ts index 9b633cd..00242d8 100644 --- a/packages/drfed/src/index.ts +++ b/packages/drfed/src/index.ts @@ -18,8 +18,11 @@ import { writeFile } from "node:fs/promises"; import process from "node:process"; import { createYogaServer } from "@drfed/graphql"; +import createFederation from "@drfed/graphql/federation"; import { schema } from "@drfed/graphql/schema"; import { migrate } from "@drfed/models"; +import { PgliteKvStore } from "@fedify/pglite"; +import { PostgresKvStore } from "@fedify/postgres"; import { configure, getConsoleSink } from "@logtape/logtape"; import { createLoggingConfig } from "@optique/logtape"; import { run } from "@optique/run"; @@ -27,7 +30,6 @@ import { SmtpTransport } from "@upyo/smtp"; import { printSchema } from "graphql"; import { serve } from "srvx"; -// oxlint-disable-next-line import/no-relative-parent-imports import metadata from "../package.json" with { type: "json" }; import type { Options, @@ -38,16 +40,26 @@ import program from "./program.ts"; import seedData from "./seed.ts"; async function runServer(options: ServerOptions) { - if (options.drizzle.migrate) { - await migrate({ credentials: options.drizzle.credentials }); - } - if (options.seed) { - await seedData(options.drizzle.db); - } + const { credentials } = options.drizzle; + if (options.drizzle.migrate) await migrate({ credentials }); + if (options.seed) await seedData(options.drizzle.db); + const kv = + "driver" in credentials + ? new PgliteKvStore(credentials.client) + : new PostgresKvStore(credentials.client); + const federation = await createFederation(options.drizzle.db, { kv }); const { mailer, root } = options; - const yogaServer = createYogaServer(options.drizzle.db, { root, mailer }); + const yogaServer = createYogaServer(options.drizzle.db, federation, { + root, + mailer, + }); const server = serve({ - fetch: yogaServer.fetch.bind(yogaServer), + fetch: (req) => + federation.fetch(req, { + onNotFound: yogaServer.fetch, + onNotAcceptable: yogaServer.fetch, + contextData: undefined, + }), hostname: options.address.host, manual: true, port: options.address.port, @@ -56,8 +68,13 @@ async function runServer(options: ServerOptions) { if (mailer instanceof SmtpTransport) { mailer.closeAllConnections(); } - // oxlint-disable-next-line promise/catch-or-return promise/prefer-await-to-then no-magic-numbers - server.close().then(() => process.exit(0)); + // oxlint-disable-next-line promise/catch-or-return promise/prefer-await-to-then + server.close().then(async () => { + await ("driver" in credentials + ? credentials.client.close() + : credentials.client.end()); + process.exit(0); + }); } process.once("SIGINT", shutdown); process.once("SIGTERM", shutdown); diff --git a/packages/drfed/src/parser.ts b/packages/drfed/src/parser.ts index b7beff0..ba66bb7 100644 --- a/packages/drfed/src/parser.ts +++ b/packages/drfed/src/parser.ts @@ -27,8 +27,9 @@ import { loggingOptions } from "@optique/logtape"; import { path } from "@optique/run/valueparser"; import { LogTapeTransport } from "@upyo/logtape"; import { SmtpTransport } from "@upyo/smtp"; -import { drizzle as drizzlePostgres } from "drizzle-orm/node-postgres"; import { drizzle as drizzlePglite } from "drizzle-orm/pglite"; +import { drizzle as drizzlePostgres } from "drizzle-orm/postgres-js"; +import postgres from "postgres"; const pgliteParser = map( option( @@ -67,19 +68,20 @@ const postgresParser = map( description: message`The URL of the PostgreSQL database to connect to. Mutually exclusive with ${optionNames(["--pglite-data-path", "--data-path", "-d"])}.`, }, ), - (dbUrl) => ({ - credentials: { - url: dbUrl.href, - }, - db: drizzlePostgres({ - connection: { - connectionString: dbUrl.href, + (dbUrl) => { + const client = postgres(dbUrl.href); + return { + credentials: { + client, }, - relations, - schema, - logger: getLogger(), - }), - }), + db: drizzlePostgres({ + client, + relations, + schema, + logger: getLogger(), + }), + }; + }, ); const smtpParser = map( diff --git a/packages/graphql/README.md b/packages/graphql/README.md index f41999c..1424e39 100644 --- a/packages/graphql/README.md +++ b/packages/graphql/README.md @@ -24,10 +24,18 @@ Usage ~~~~ ts import { createYogaServer } from "@drfed/graphql"; - -const yoga = createYogaServer(db); -serve({ fetch: yoga.fetch.bind(yoga) }); +import createFederation from "@drfed/graphql/federation"; + +const federation = await createFederation(db, { kv }); +const yoga = createYogaServer(db, federation); +serve({ + fetch: (request) => + federation.fetch(request, { onNotFound: yoga.fetch, contextData: undefined }), +}); ~~~~ -`createYogaServer` accepts a Drizzle database instance and returns a -GraphQL Yoga server ready to handle HTTP requests. +`createFederation` builds a Fedify `Federation` with every DrFed dispatcher +registered. `createYogaServer` accepts a Drizzle database instance, that +federation, and optional server options, and returns a GraphQL Yoga server +ready to handle HTTP requests. The federation is stored in the resolver +context as is; `createYogaServer` never registers anything on it. diff --git a/packages/graphql/package.json b/packages/graphql/package.json index cc5e926..ce60ce6 100644 --- a/packages/graphql/package.json +++ b/packages/graphql/package.json @@ -50,10 +50,18 @@ "types": "./dist/account.d.mts", "default": "./dist/account.mjs" }, + "./actor": { + "types": "./dist/actor.d.mts", + "default": "./dist/actor.mjs" + }, "./builder": { "types": "./dist/builder.d.mts", "default": "./dist/builder.mjs" }, + "./federation": { + "types": "./dist/federation.d.mts", + "default": "./dist/federation.mjs" + }, "./instance": { "types": "./dist/instance.d.mts", "default": "./dist/instance.mjs" @@ -71,7 +79,9 @@ "entry": [ "src/index.ts", "src/account.ts", + "src/actor.ts", "src/builder.ts", + "src/federation.ts", "src/instance.ts", "src/schema.ts" ], @@ -95,6 +105,8 @@ "dependencies": { "@drfed/models": "workspace:*", "@fedify/uri-template": "^2.3.1", + "@fedify/fedify": "catalog:", + "@fedify/vocab": "catalog:", "@logtape/graphql-yoga": "catalog:", "@logtape/logtape": "catalog:", "@pothos/core": "^4.13.0", diff --git a/packages/graphql/src/account.ts b/packages/graphql/src/account.ts index d6ec689..7a7865c 100644 --- a/packages/graphql/src/account.ts +++ b/packages/graphql/src/account.ts @@ -18,7 +18,6 @@ import { drizzleConnectionHelpers } from "@pothos/plugin-drizzle"; import { and, eq, isNotNull } from "drizzle-orm/sql/expressions"; import builder, { type DrFedObjectRef } from "./builder.ts"; -// oxlint-disable-next-line import/no-cycle import { Instance } from "./instance.ts"; /** @@ -204,3 +203,100 @@ builder.queryFields((t) => ({ type: AccountRef, }), })); + +const instanceMembersConnection = drizzleConnectionHelpers( + builder, + "instanceMembers", + { + query: { + orderBy: { created: "desc" }, + }, + select(nestedSelection) { + return { + with: { + account: nestedSelection(), + }, + where: { + accepted: { isNotNull: true }, + }, + }; + }, + resolveNode(instanceMember) { + return instanceMember.account; + }, + }, +); + +builder.drizzleObjectField("instances", "members", (t) => + t.connection( + { + type: Account, + description: "The `Account`s that belong to the `Instance`.", + select(args, ctx, nestedSelection) { + return { + with: { + instanceMembers: instanceMembersConnection.getQuery( + args, + ctx, + nestedSelection, + ), + }, + }; + }, + resolve(instance, args, ctx) { + return { + ...instanceMembersConnection.resolve( + instance.instanceMembers, + args, + ctx, + instance, + ), + totalCount() { + return ctx.db.$count( + instanceMembers, + and( + eq(instanceMembers.instanceId, instance.id), + isNotNull(instanceMembers.accepted), + ), + ); + }, + }; + }, + }, + { + fields(fb) { + return { + totalCount: fb.int({ + description: + "The total number of `Account`s that belong to the `Instance`. " + + "Note that pending members are not counted.", + resolve(connection) { + return connection.totalCount(); + }, + }), + }; + }, + }, + { + fields(fb) { + return { + created: fb.expose("created", { + type: "DateTime", + description: + "The date/time when the `Account` was added to the `Instance`.", + }), + accepted: fb.expose("accepted", { + type: "DateTime", + nullable: true, + description: + "The date/time when the `Account` accepted membership in the `Instance`.", + }), + admin: fb.exposeBoolean("admin", { + description: + "Whether the `Account` has administrator privileges in the `Instance`.", + }), + }; + }, + }, + ), +); diff --git a/packages/graphql/src/actor.test.ts b/packages/graphql/src/actor.test.ts new file mode 100644 index 0000000..4deae76 --- /dev/null +++ b/packages/graphql/src/actor.test.ts @@ -0,0 +1,335 @@ +// 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 . + +// oxlint-disable max-lines + +import assert from "node:assert/strict"; + +import { type Database, schema } from "@drfed/models"; +import { describe, it } from "@logtape/testing-node/autoload"; + +import { hashSecret } from "./auth/hash.ts"; +import { withTestHarness } from "./harness.test.ts"; + +const accepted = new Date("2026-08-04T00:00:00.000Z"); +const created = new Date("2026-08-04T00:00:00.000Z"); +const expires = new Date("2030-08-04T00:00:00.000Z"); +const ok = 200; + +const accountId = "00000000-0000-4000-8000-000000000001"; +const localInstanceId = "00000000-0000-4000-8000-000000000101"; +const remoteInstanceId = "00000000-0000-4000-8000-000000000102"; +const localActorId = "00000000-0000-4000-8000-000000000201"; +const remoteActorId = "00000000-0000-4000-8000-000000000202"; +const sessionId = "00000000-0000-4000-8000-000000000301"; +const accessToken = "test-access-token"; + +const generateActorsMutation = ` + mutation GenerateActors($instance: ID!, $size: Int!) { + generateActors(instance: $instance, size: $size) { + resultType: __typename + ... on CreateActorsSuccess { + actors { + uuid + iri + username + local { + uuid + } + } + } + ... on CreateActorsError { + type + message + } + } + } +`; + +const actorQuery = ` + query Actor($id: ID!) { + node(id: $id) { + ... on Actor { + id + uuid + iri + handle + type + username + instance { + uuid + host + } + local { + avatar + header + } + inboxUrl + outboxUrl + avatarUrl + followersUrl + followingUrl + headerUrl + profileUrl + featuredUrl + created + } + } + } +`; + +describe("Mutation.generateActors", () => { + it("creates local actors", async () => { + await withTestHarness(async ({ db, post }) => { + const auth = await seedAuthenticatedLocalInstance(db); + + const response = await post( + { + query: generateActorsMutation, + variables: { + instance: globalId("Instance", localInstanceId), + size: 2, + }, + }, + auth, + ); + + assert.equal(response.status, ok); + const body = await response.json(); + assert.equal(body.errors, undefined); + assert.equal(body.data.generateActors.resultType, "CreateActorsSuccess"); + assert.equal(body.data.generateActors.actors.length, 2); + assert.ok( + body.data.generateActors.actors.every( + (actor: { + iri: unknown; + local: { uuid: unknown } | null; + username: unknown; + uuid: unknown; + }) => + typeof actor.uuid === "string" && + typeof actor.username === "string" && + typeof actor.iri === "string" && + typeof actor.local?.uuid === "string", + ), + ); + assert.deepStrictEqual( + body.data.generateActors.actors.map( + (actor: { iri: string }) => actor.iri, + ), + body.data.generateActors.actors.map( + (actor: { uuid: string }) => + `https://test-instance.drfed.org/users/${actor.uuid}`, + ), + ); + + const actors = await db.select().from(schema.actors); + assert.equal(actors.length, 2); + assert.equal( + actors.every( + (actor) => + actor.instanceId === localInstanceId && + actor.localId != null && + actor.type === "Person", + ), + true, + ); + + const localActors = await db.select().from(schema.localActors); + assert.equal(localActors.length, 2); + assert.deepEqual( + new Set(localActors.map(({ id }) => id)), + new Set(actors.map(({ localId }) => localId)), + ); + }); + }); +}); + +describe("Actor", () => { + it("returns a local actor", async () => { + await withTestHarness(async ({ db, post }) => { + await seedLocalActor(db); + + const response = await post({ + query: actorQuery, + variables: { id: globalId("Actor", localActorId) }, + }); + + assert.equal(response.status, ok); + assert.deepEqual(await response.json(), { + data: { + node: { + id: globalId("Actor", localActorId), + uuid: localActorId, + iri: `https://test-instance.drfed.org/users/${localActorId}`, + handle: "@alice@test-instance.drfed.org", + type: "Person", + username: "alice", + instance: { + uuid: localInstanceId, + host: "test-instance.drfed.org", + }, + local: { + avatar: "avatar.png", + header: "header.png", + }, + inboxUrl: `https://test-instance.drfed.org/users/${localActorId}/inbox`, + outboxUrl: `https://test-instance.drfed.org/users/${localActorId}/outbox`, + avatarUrl: `https://test-instance.drfed.org/users/${localActorId}/avatar/avatar.png`, + followersUrl: `https://test-instance.drfed.org/users/${localActorId}/followers`, + followingUrl: `https://test-instance.drfed.org/users/${localActorId}/following`, + headerUrl: `https://test-instance.drfed.org/users/${localActorId}/header/header.png`, + profileUrl: "https://test-instance.drfed.org/@alice", + featuredUrl: `https://test-instance.drfed.org/users/${localActorId}/featured`, + created: created.toISOString(), + }, + }, + }); + }); + }); + + it("returns a remote actor", async () => { + await withTestHarness(async ({ db, post }) => { + await seedRemoteActor(db); + + const response = await post({ + query: actorQuery, + variables: { id: globalId("Actor", remoteActorId) }, + }); + + assert.equal(response.status, ok); + assert.deepEqual(await response.json(), { + data: { + node: { + id: globalId("Actor", remoteActorId), + uuid: remoteActorId, + iri: "https://remote.example.com/users/bob", + handle: "@bob@remote.example.com", + type: "Service", + username: "bob", + instance: { + uuid: remoteInstanceId, + host: "remote.example.com", + }, + local: null, + inboxUrl: "https://remote.example.com/users/bob/inbox", + outboxUrl: "https://remote.example.com/users/bob/outbox", + avatarUrl: "https://remote.example.com/users/bob/avatar.png", + followersUrl: "https://remote.example.com/users/bob/followers", + followingUrl: "https://remote.example.com/users/bob/following", + headerUrl: "https://remote.example.com/users/bob/header.png", + profileUrl: "https://remote.example.com/@bob", + featuredUrl: "https://remote.example.com/users/bob/featured", + created: created.toISOString(), + }, + }, + }); + }); + }); +}); + +function globalId(type: "Actor" | "Instance", id: string): string { + return Buffer.from(`${type}:${id}`).toString("base64"); +} + +async function seedAuthenticatedLocalInstance( + db: Database, +): Promise { + await db.insert(schema.accounts).values({ + id: accountId, + email: "owner@example.com", + name: "Owner", + created, + }); + await db.insert(schema.sessions).values({ + id: sessionId, + accountId, + tokenHash: await hashSecret(accessToken), + }); + await seedLocalInstance(db); + await db.insert(schema.instanceMembers).values({ + accountId, + instanceId: localInstanceId, + admin: true, + accepted, + created, + }); + return { headers: { authorization: `Bearer ${accessToken}` } }; +} + +async function seedLocalActor(db: Database): Promise { + await seedLocalInstance(db); + await db.insert(schema.localActors).values({ + id: localActorId, + avatar: "avatar.png", + header: "header.png", + }); + await db.insert(schema.actors).values({ + id: localActorId, + localId: localActorId, + instanceId: localInstanceId, + type: "Person", + username: "alice", + iri: `https://test-instance.drfed.org/users/${localActorId}`, + inboxUrl: `https://test-instance.drfed.org/users/${localActorId}/inbox`, + outboxUrl: `https://test-instance.drfed.org/users/${localActorId}/outbox`, + avatarUrl: `https://test-instance.drfed.org/users/${localActorId}/avatar/avatar.png`, + followersUrl: `https://test-instance.drfed.org/users/${localActorId}/followers`, + followingUrl: `https://test-instance.drfed.org/users/${localActorId}/following`, + headerUrl: `https://test-instance.drfed.org/users/${localActorId}/header/header.png`, + profileUrl: "https://test-instance.drfed.org/@alice", + featuredUrl: `https://test-instance.drfed.org/users/${localActorId}/featured`, + created, + }); +} + +async function seedLocalInstance(db: Database): Promise { + await db.insert(schema.localInstances).values({ + id: localInstanceId, + slug: "test-instance", + expires, + }); + await db.insert(schema.instances).values({ + id: localInstanceId, + localId: localInstanceId, + created, + host: "test-instance.drfed.org", + }); +} + +async function seedRemoteActor(db: Database): Promise { + await db.insert(schema.instances).values({ + id: remoteInstanceId, + created, + host: "remote.example.com", + }); + await db.insert(schema.actors).values({ + id: remoteActorId, + instanceId: remoteInstanceId, + type: "Service", + username: "bob", + iri: "https://remote.example.com/users/bob", + inboxUrl: "https://remote.example.com/users/bob/inbox", + outboxUrl: "https://remote.example.com/users/bob/outbox", + avatarUrl: "https://remote.example.com/users/bob/avatar.png", + followersUrl: "https://remote.example.com/users/bob/followers", + followingUrl: "https://remote.example.com/users/bob/following", + headerUrl: "https://remote.example.com/users/bob/header.png", + profileUrl: "https://remote.example.com/@bob", + featuredUrl: "https://remote.example.com/users/bob/featured", + created, + }); +} diff --git a/packages/graphql/src/actor.ts b/packages/graphql/src/actor.ts new file mode 100644 index 0000000..ff1af4a --- /dev/null +++ b/packages/graphql/src/actor.ts @@ -0,0 +1,390 @@ +// 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 . + +// oxlint-disable max-lines-per-function eslint/max-lines + +import { schema } from "@drfed/models"; +import { actorTypeEnum } from "@drfed/models/schema"; +import type { Context } from "@fedify/fedify"; +import { drizzleConnectionHelpers } from "@pothos/plugin-drizzle"; +import type { PgInsertValue } from "drizzle-orm/pg-core"; +import { and, eq, gt, isNotNull } from "drizzle-orm/sql/expressions"; +import { v7 as uuid } from "uuid"; + +import builder, { type DrFedObjectRef } from "./builder.ts"; +import { Instance } from "./instance.ts"; + +const ActorType = builder.enumType("ActorType", { + values: actorTypeEnum.enumValues, +}); + +const ACTOR_TYPES_DOC = actorTypeEnum.enumValues + .map((t) => `\`${t}\``) + .join(" | "); + +const ActorRef = builder.drizzleNode("actors", { + name: "Actor", + description: "Represents an `Actor` in the DrFed platform.", + id: { + column: ({ id }) => id, + description: "The unique identifier of the `Actor`.", + }, + fields: (t) => ({ + uuid: t.expose("id", { + type: "UUID", + description: "The UUID of the `Actor`.", + }), + iri: t.exposeString("iri", { + description: "The Internationalized Resource Identifier of the `Actor`", + }), + handle: t.field({ + type: "String", + description: "The handle of the `Actor`.", + select: { + columns: { username: true }, + with: { instance: { columns: { host: true } } }, + }, + resolve: ({ instance, username }) => `@${username}@${instance.host}`, + }), + type: t.expose("type", { + type: ActorType, + description: `The type of the \`Actor\`: ${ACTOR_TYPES_DOC}`, + }), + username: t.exposeString("username", { + description: "The username of the `Actor`.", + }), + instance: t.relation("instance", { + description: "The `Instance` that the `Actor` belongs to.", + }), + local: t.relation("localActor", { + nullable: true, + description: "The local details of the `Actor`, or null if it is remote.", + }), + inboxUrl: t.expose("inboxUrl", { + type: "URL", + description: "The inbox URL of the `Actor`.", + }), + outboxUrl: t.expose("outboxUrl", { + type: "URL", + description: "The outbox URL of the `Actor`.", + }), + avatarUrl: t.expose("avatarUrl", { + type: "URL", + description: "The avatar URL of the `Actor`.", + nullable: true, + }), + followersUrl: t.expose("followersUrl", { + type: "URL", + description: "The followers URL of the `Actor`.", + nullable: true, + }), + followingUrl: t.expose("followingUrl", { + type: "URL", + description: "The following URL of the `Actor`.", + nullable: true, + }), + headerUrl: t.expose("headerUrl", { + type: "URL", + description: "The header URL of the `Actor`.", + nullable: true, + }), + profileUrl: t.expose("profileUrl", { + type: "URL", + description: "The profile URL of the `Actor`.", + nullable: true, + }), + featuredUrl: t.expose("featuredUrl", { + type: "URL", + description: "The featured URL of the `Actor`.", + nullable: true, + }), + created: t.expose("created", { + type: "DateTime", + description: "The creation date/time of the `Actor`.", + }), + }), +}); + +export const Actor: DrFedObjectRef = ActorRef; + +const LocalActorRef = builder.drizzleNode("localActors", { + name: "LocalActor", + description: "Represents the local details of an `Actor`.", + id: { + column: ({ id }) => id, + description: "The unique identifier of the local actor details.", + }, + fields: (t) => ({ + uuid: t.expose("id", { + type: "UUID", + description: "The UUID of the local actor details.", + }), + avatar: t.exposeString("avatar", { + nullable: true, + description: "The profile image of the actor.", + }), + header: t.exposeString("header", { + nullable: true, + description: "The profile banner image of the actor.", + }), + }), +}); + +export const LocalActor: DrFedObjectRef = LocalActorRef; + +interface CreateActorsSuccess { + readonly actors: readonly (typeof ActorRef.$inferType)[]; +} + +const CreateActorsSuccessRef = builder.objectRef( + "CreateActorsSuccess", +); + +CreateActorsSuccessRef.implement({ + fields: (t) => ({ + actors: t.field({ + type: [ActorRef], + resolve: ({ actors }) => actors, + }), + }), +}); + +const INVALID_SIZE = "InvalidSize" as const; +const INSTANCE_NOT_FOUND = "InstanceNotFound" as const; +const TOO_MANY_ACTORS = "TooManyActors" as const; +const CreateActorsErrors = [ + INVALID_SIZE, + INSTANCE_NOT_FOUND, + TOO_MANY_ACTORS, +] as const; + +const CreateActorsErrorType = builder.enumType("CreateActorsErrorType", { + values: CreateActorsErrors, +}); + +interface CreateActorsError { + readonly type: typeof CreateActorsErrorType.$inferType; + readonly message: string; +} + +const CreateActorsErrorRef = + builder.objectRef("CreateActorsError"); + +CreateActorsErrorRef.implement({ + description: "Represents an error that occurred while creating an `Actor`.", + fields: (t) => ({ + type: t.expose("type", { + type: CreateActorsErrorType, + description: + "The type of the error. Use this for programmatic error handling.", + }), + message: t.exposeString("message", { + description: + "A human-readable message describing the error. " + + "Don't use this for programmatic error handling, " + + "use the `type` field instead.", + }), + }), +}); + +const CreateActorsResult = builder.unionType("CreateActorsResult", { + types: [CreateActorsSuccessRef, CreateActorsErrorRef], + resolveType(value) { + if ("message" in value) return CreateActorsErrorRef; + return CreateActorsSuccessRef; + }, +}); + +builder.mutationFields((t) => ({ + generateActors: t.field({ + type: CreateActorsResult, + description: "Create actors.", + authScopes: { authenticated: true }, + args: { + instance: t.arg.globalID({ + for: Instance, + required: true, + description: "The ID of the target instance", + }), + size: t.arg({ + type: "Int", + required: true, + description: "How many actors to generate", + }), + }, + async resolve(_query, { instance: { id: instanceId }, size }, ctx) { + if (size < 1) { + return { + type: INVALID_SIZE, + message: `${size} is too small. At least 1 or more.`, + }; + } + if (ctx.account == null) { + // Note that the following error is not expected to be thrown, + // because the `authScopes` option above should prevent this resolver + throw new Error("You must be authenticated to create actors."); + } + const { account } = ctx; + + return await ctx.db.transaction(async (tx) => { + // Find the instance that the account is included + const [instance] = await tx + .select({ + slug: schema.localInstances.slug, + maxActors: schema.localInstances.maxActors, + }) + .from(schema.instanceMembers) + .for("update", { of: schema.localInstances }) + .innerJoin( + schema.instances, + eq(schema.instanceMembers.instanceId, schema.instances.id), + ) + .innerJoin( + schema.localInstances, + eq(schema.instances.localId, schema.localInstances.id), + ) + .where( + and( + eq(schema.instanceMembers.accountId, account.id), + gt(schema.localInstances.expires, new Date()), + eq(schema.instances.id, instanceId), + isNotNull(schema.instanceMembers.accepted), + ), + ) + .limit(1); + if (instance == null) { + return { + type: INSTANCE_NOT_FOUND, + message: "Can't find the instance.", + }; + } + const { slug, maxActors } = instance; + const host = `${slug}.${ctx.root}`; + const currActors = await tx.$count( + schema.actors, + eq(schema.actors.instanceId, instanceId), + ); + + if (size + currActors > maxActors) { + return { + type: TOO_MANY_ACTORS, + message: `${size} is too big. The maximum number of actors of ${ + host + } is ${maxActors} and the current number of actors is ${ + currActors + }.`, + }; + } + // Create actors + const fedCtx = ctx.federation.createContext( + new URL(`https://${host}`), + undefined, + ); + const ids = Array.from({ length: size }, () => ({ id: uuid() })); + await tx.insert(schema.localActors).values(ids); + const createdActors = await tx + .insert(schema.actors) + .values(ids.map(({ id }) => generateActor(id, instanceId, fedCtx))) + .returning(); + return { actors: createdActors }; + }); + }, + }), +})); + +function generateActor( + id: string, + instanceId: string, + fedCtx: Context, +): PgInsertValue { + return { + id, + localId: id, + // FIXME: Generate handle using Faker.js or something + username: id, + instanceId, + type: "Person", + iri: fedCtx.getActorUri(id).href, + inboxUrl: fedCtx.getInboxUri(id).href, + outboxUrl: fedCtx.getOutboxUri(id).href, + followersUrl: fedCtx.getFollowersUri(id).href, + followingUrl: fedCtx.getFollowingUri(id).href, + featuredUrl: fedCtx.getFeaturedUri(id).href, + profileUrl: new URL(`/@${id}`, fedCtx.origin).href, + }; +} + +const actorsConnection = drizzleConnectionHelpers(builder, "actors", { + query: { orderBy: { created: "desc" } }, +}); + +builder.drizzleObjectField("instances", "actors", (t) => + t.connection( + { + type: Actor, + description: "The `Actor`s that belong to the `Instance`.", + select(args, ctx, nestedSelection) { + return { + with: { + actors: actorsConnection.getQuery(args, ctx, nestedSelection), + }, + }; + }, + resolve(instance, args, ctx) { + return { + ...actorsConnection.resolve(instance.actors, args, ctx, instance), + totalCount() { + return ctx.db.$count( + schema.actors, + eq(schema.actors.instanceId, instance.id), + ); + }, + }; + }, + }, + { + fields(fb) { + return { + totalCount: fb.int({ + description: + "The total number of `Actor`s that belong to the `Instance`.", + resolve(connection) { + return connection.totalCount(); + }, + }), + }; + }, + }, + { + fields(fb) { + return { + created: fb.expose("created", { + type: "DateTime", + description: + "The date/time when the `Actor` was added to the `Instance`.", + }), + type: fb.expose("type", { + type: ActorType, + description: `The type of the \`Actor\`: ${ACTOR_TYPES_DOC}`, + }), + username: fb.exposeString("username", { + description: "The username of the `Actor`.", + }), + }; + }, + }, + ), +); diff --git a/packages/graphql/src/builder.ts b/packages/graphql/src/builder.ts index d13edbf..49063f9 100644 --- a/packages/graphql/src/builder.ts +++ b/packages/graphql/src/builder.ts @@ -21,6 +21,7 @@ import { instanceMembers, instances, } from "@drfed/models/schema"; +import type { Federation } from "@fedify/fedify"; import { Template } from "@fedify/uri-template"; import SchemaBuilder, { type ObjectRef } from "@pothos/core"; import DrizzlePlugin from "@pothos/plugin-drizzle"; @@ -30,7 +31,7 @@ import ScopeAuthPlugin from "@pothos/plugin-scope-auth"; import type { Transport } from "@upyo/core"; import { getTableConfig } from "drizzle-orm/pg-core"; import { and, eq, isNotNull } from "drizzle-orm/sql/expressions"; -import { DateTimeResolver, UUIDResolver } from "graphql-scalars"; +import { DateTimeResolver, URLResolver, UUIDResolver } from "graphql-scalars"; /** * The context data for the GraphQL server, which includes the incoming request @@ -67,6 +68,11 @@ export interface ServerContext { * Root domain. */ readonly root: string; + + /** + * The federation instance. + */ + readonly federation: Federation; } /** @@ -104,6 +110,10 @@ export interface SchemaTypes { Input: Template; Output: Template; }; + URL: { + Input: URL; + Output: string; + }; }; DefaultFieldNullability: false; DrizzleRelations: typeof relations; @@ -196,6 +206,7 @@ async function isLocalInstanceMember( } builder.addScalarType("DateTime", DateTimeResolver); +builder.addScalarType("URL", URLResolver); builder.scalarType("Email", { parseValue: (v) => normalizeEmail(String(v)), diff --git a/packages/graphql/src/federation.test.ts b/packages/graphql/src/federation.test.ts new file mode 100644 index 0000000..054acff --- /dev/null +++ b/packages/graphql/src/federation.test.ts @@ -0,0 +1,79 @@ +// 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 assert from "node:assert/strict"; + +import { createYogaServer } from "@drfed/graphql"; +import createFederation, { buildFederation } from "@drfed/graphql/federation"; +import { MemoryKvStore } from "@fedify/fedify"; +import { describe, it } from "@logtape/testing-node/autoload"; + +import { withTemporaryDatabase, withTestHarness } from "./harness.test.ts"; + +const origin = new URL("https://drfed.test"); + +describe("createFederation()", () => { + it("registers the actor URI layout", async () => { + await withTemporaryDatabase(async (db) => { + const federation = await createFederation(db, { + kv: new MemoryKvStore(), + }); + const ctx = federation.createContext(origin, undefined); + assert.equal( + ctx.getActorUri("identifier").href, + "https://drfed.test/users/identifier", + ); + assert.equal( + ctx.getInboxUri("identifier").href, + "https://drfed.test/users/identifier/inbox", + ); + assert.equal(ctx.getInboxUri().href, "https://drfed.test/inbox"); + assert.equal( + ctx.getOutboxUri("identifier").href, + "https://drfed.test/users/identifier/outbox", + ); + assert.equal( + ctx.getFollowersUri("identifier").href, + "https://drfed.test/users/identifier/followers", + ); + assert.equal( + ctx.getFollowingUri("identifier").href, + "https://drfed.test/users/identifier/following", + ); + assert.equal( + ctx.getFeaturedUri("identifier").href, + "https://drfed.test/users/identifier/featured", + ); + }); + }); + + it("builds independent instances from one builder", async () => { + await withTemporaryDatabase(async (db) => { + const builder = buildFederation(db); + const first = await builder.build({ kv: new MemoryKvStore() }); + const second = await builder.build({ kv: new MemoryKvStore() }); + assert.notEqual(first, second); + }); + }); +}); + +describe("createYogaServer()", () => { + it("does not mutate the federation instance", async () => { + await withTestHarness(({ db, mailer, federation }) => { + assert.doesNotThrow(() => createYogaServer(db, federation, { mailer })); + }); + }); +}); diff --git a/packages/graphql/src/federation.ts b/packages/graphql/src/federation.ts new file mode 100644 index 0000000..91589dc --- /dev/null +++ b/packages/graphql/src/federation.ts @@ -0,0 +1,242 @@ +// 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 } from "@drfed/models"; +import type { Actor } from "@drfed/models/schema"; +import { + type Context, + type Federation, + type FederationBuilder, + type FederationOptions, + createFederationBuilder, +} from "@fedify/fedify"; +import { + Activity, + Application, + Endpoints, + Group, + Image, + Organization, + Person, + Service, + Tombstone, +} from "@fedify/vocab"; +import { getLogger } from "@logtape/logtape"; +import { validate as validateUuid } from "uuid"; + +/** + * The vocabulary object types that DrFed serves as actors. + */ +type ActorObject = Application | Group | Organization | Person | Service; + +type ActorProps = ConstructorParameters[0]; + +const actorConstructors: Record< + Actor["type"], + (props: ActorProps) => ActorObject +> = { + Application: (props) => new Application(props), + Group: (props) => new Group(props), + Organization: (props) => new Organization(props), + Person: (props) => new Person(props), + Service: (props) => new Service(props), +}; + +async function findLocalActor( + db: Database, + ctx: Context, + identifier: string, +): Promise { + if (!validateUuid(identifier)) return null; + const actor = await db.query.actors.findFirst({ + where: { + id: identifier, + localId: { isNotNull: true }, + instance: { host: ctx.host }, + }, + }); + return actor ?? null; +} + +async function findActiveActor( + db: Database, + ctx: Context, + identifier: string, +): Promise { + const actor = await findLocalActor(db, ctx, identifier); + return actor == null || actor.deleted != null ? null : actor; +} + +/** + * Creates a `FederationBuilder` with every ActivityPub dispatcher and + * listener that DrFed serves registered on it. The registered paths define + * the URI layout of the federated objects, which makes the object URI getters + * (e.g. `Context.getActorUri()`) available once the builder is built. + * @param db The database to resolve local actors from. + * @returns A builder that has not been built yet. + */ +export function buildFederation(db: Database): FederationBuilder { + const builder = createFederationBuilder(); + builder + .setActorDispatcher("/users/{identifier}", async (ctx, identifier) => { + const actor = await findLocalActor(db, ctx, identifier); + if (actor == null) return null; + // Deleted actors are served as `Tombstone`s (HTTP 410) so that remote + // peers purge them instead of retrying on 404. + if (actor.deleted != null) { + return new Tombstone({ id: ctx.getActorUri(identifier) }); + } + return toActorObject(ctx, identifier, actor); + }) + .mapHandle(async (ctx, username) => { + const actor = await db.query.actors.findFirst({ + where: { + username, + localId: { isNotNull: true }, + instance: { host: ctx.host }, + deleted: { isNull: true }, + }, + }); + return actor?.id ?? null; + }); + // FIXME: Provide actor key pairs via setKeyPairsDispatcher() once the + // data model stores signing keys. + + builder + .setInboxListeners("/users/{identifier}/inbox", "/inbox") + // FIXME: Record incoming activities once the data model can store them; + // until then the catch-all below only surfaces them in the logs so that + // deliveries are not silently discarded. + .on(Activity, (_ctx, activity) => { + logger.debug("Received an activity: {activity}", { activity }); + }) + .onError((_ctx, error) => { + logger.error("An error occurred while processing an inbox: {error}", { + error, + }); + }); + + builder.setOutboxDispatcher( + "/users/{identifier}/outbox", + async (ctx, identifier) => + // FIXME: Return the actual activities once the data model stores them + (await findActiveActor(db, ctx, identifier)) == null + ? null + : { items: [] }, + ); + + builder + .setFollowersDispatcher( + "/users/{identifier}/followers", + async (ctx, identifier) => + // FIXME: Return the actual followers once the data model stores + // follows + (await findActiveActor(db, ctx, identifier)) == null + ? null + : { items: [] }, + ) + .setCounter( + async (ctx, identifier) => + (await findActiveActor(db, ctx, identifier))?.followersCount ?? null, + ); + + builder + .setFollowingDispatcher( + "/users/{identifier}/following", + async (ctx, identifier) => + // FIXME: Return the actual following once the data model stores + // follows + (await findActiveActor(db, ctx, identifier)) == null + ? null + : { items: [] }, + ) + .setCounter( + async (ctx, identifier) => + (await findActiveActor(db, ctx, identifier))?.followingCount ?? null, + ); + + builder.setFeaturedDispatcher( + "/users/{identifier}/featured", + async (ctx, identifier) => + // FIXME: Return the actual pinned objects once the data model stores + // them + (await findActiveActor(db, ctx, identifier)) == null + ? null + : { items: [] }, + ); + return builder; +} + +/** + * Creates a `Federation` instance with every DrFed dispatcher registered. + * Every registration happens on a fresh builder inside this function, so the + * returned instance is complete and must not be mutated further. + * @param db The database to resolve local actors from. + * @param options Options for the underlying Fedify `Federation`, such as + * the `kv` store. + * @returns The built `Federation` instance. + */ +export default async function createFederation( + db: Database, + options: FederationOptions, +): Promise> { + return await buildFederation(db).build(options); +} + +// Whether a sanction is *currently* active is always determined by comparing +// against the current time (lazy expiry; no cron); see the actors table. +function isSuspended({ suspended, suspendedUntil }: Actor): boolean { + const now = new Date(); + return ( + suspended != null && + suspended <= now && + (suspendedUntil == null || suspendedUntil > now) + ); +} + +function toActorObject( + ctx: Context, + identifier: string, + actor: Actor, +): ActorObject { + return actorConstructors[actor.type]({ + id: ctx.getActorUri(identifier), + preferredUsername: actor.username, + name: actor.name, + summary: actor.bioHtml, + url: actor.profileUrl == null ? null : new URL(actor.profileUrl), + icon: + actor.avatarUrl == null + ? null + : new Image({ url: new URL(actor.avatarUrl) }), + image: + actor.headerUrl == null + ? null + : new Image({ url: new URL(actor.headerUrl) }), + manuallyApprovesFollowers: !actor.automaticallyApprovesFollowers, + sensitive: actor.sensitive, + suspended: isSuspended(actor), + aliases: actor.aliases.map((alias) => new URL(alias)), + inbox: ctx.getInboxUri(identifier), + outbox: ctx.getOutboxUri(identifier), + followers: ctx.getFollowersUri(identifier), + following: ctx.getFollowingUri(identifier), + featured: ctx.getFeaturedUri(identifier), + endpoints: new Endpoints({ sharedInbox: ctx.getInboxUri() }), + }); +} + +const logger = getLogger(["drfed", "graphql", "federation"]); diff --git a/packages/graphql/src/harness.test.ts b/packages/graphql/src/harness.test.ts index 48dba70..7d1c619 100644 --- a/packages/graphql/src/harness.test.ts +++ b/packages/graphql/src/harness.test.ts @@ -15,8 +15,10 @@ // along with this program. If not, see . import { createYogaServer } from "@drfed/graphql"; import type { ServerContext, UserContext } from "@drfed/graphql/builder"; +import createFederation from "@drfed/graphql/federation"; import { type Database, migrate, relations, schema } from "@drfed/models"; import { PGlite } from "@electric-sql/pglite"; +import { type Federation, MemoryKvStore } from "@fedify/fedify"; import { getLogger } from "@logtape/logtape"; import { MockTransport } from "@upyo/mock"; import { drizzle } from "drizzle-orm/pglite"; @@ -24,7 +26,7 @@ import type { YogaServerInstance } from "graphql-yoga"; const logger = getLogger(["drfed", "graphql", "test"]); -const testEndpoint = "http://drfed.test/graphql"; +const testEndpoint = "https://drfed.test/graphql"; /** * The `fetch()` function exposed by the test Yoga server. @@ -65,6 +67,11 @@ export interface TestHarness { */ readonly mailer: MockTransport; + /** + * The federation instance the Yoga server was created with. + */ + readonly federation: Federation; + /** * The test server's `fetch()` function, bound to the Yoga server instance. */ @@ -162,12 +169,14 @@ export async function withTestHarness( ): Promise> { return await withTemporaryDatabase(async (db) => { const mailer = new MockTransport(); - const yoga = createYogaServer(db, { mailer }); + const federation = await createFederation(db, { kv: new MemoryKvStore() }); + const yoga = createYogaServer(db, federation, { mailer }); const fetch: TestFetch = yoga.fetch.bind(yoga); const harness: TestHarness = { db, mailer, + federation, fetch, yoga, async post(body, init) { diff --git a/packages/graphql/src/index.ts b/packages/graphql/src/index.ts index afd8ddd..4fff0de 100644 --- a/packages/graphql/src/index.ts +++ b/packages/graphql/src/index.ts @@ -15,6 +15,7 @@ // along with this program. If not, see . import type { Database } from "@drfed/models"; +import type { Federation } from "@fedify/fedify"; import { getYogaLogger } from "@logtape/graphql-yoga"; import { getLogger } from "@logtape/logtape"; import type { Transport } from "@upyo/core"; @@ -28,7 +29,6 @@ import { import { hashSecret } from "./auth/hash.ts"; import type { ServerContext, UserContext } from "./builder.ts"; import { schema } from "./schema.ts"; - /** * Options for Yoga server. */ @@ -57,12 +57,18 @@ export interface YogaServerOptions { /** * Creates a Yoga server instance with the provided schema and context. * @param {Database} db The database instance. + * @param {Federation} federation The federation instance. It must + * already have every dispatcher registered (see `createFederation()` + * in `@drfed/graphql/federation`); this function only stores it in + * the resolver context and never mutates it, so the same instance can + * be shared by several servers. * @param {YogaServerOptions} _options Options for server. * @returns A `YogaServerInstance` configured with the schema and context for * handling GraphQL requests. */ export function createYogaServer( db: Database, + federation: Federation, _options: YogaServerOptions = {}, ): YogaServerInstance { const options = fillOptions(_options); @@ -72,7 +78,7 @@ export function createYogaServer( credentials: true, }, async context(ctx) { - const anonymous = { db, request: ctx.request, ...options }; + const anonymous = { db, federation, request: ctx.request, ...options }; const accessToken = getAccessToken(ctx.request.headers); if (accessToken == null) { return anonymous; @@ -98,12 +104,14 @@ function mockTransport() { return new MockTransport(); } -const fillOptions = (opt: YogaServerOptions): Required => ({ - mailer: opt?.mailer ?? mockTransport(), - emailFrom: opt?.emailFrom ?? "noreply@drfed.org", +const fillOptions = ( + opt: YogaServerOptions, +): Omit => ({ + mailer: opt.mailer ?? mockTransport(), + emailFrom: opt.emailFrom ?? "noreply@drfed.org", // FIXME: Properly parametrize the following allowlist: - origins: opt?.origins ?? new Set(["https://drfed.org"]), - root: opt?.root ?? "drfed.org", + origins: opt.origins ?? new Set(["https://drfed.org"]), + root: opt.root ?? "drfed.org", }); const getAccessToken = (headers: Headers) => @@ -114,7 +122,7 @@ const findSession = async (accessToken: string, db: Database) => await db.query.sessions.findFirst({ where: { tokenHash: await hashSecret(accessToken), - expires: { gt: new Date(Temporal.Now.instant().toString()) }, + expires: { gt: new Date() }, }, with: { account: true }, }); diff --git a/packages/graphql/src/instance.test.ts b/packages/graphql/src/instance.test.ts index 59977d3..db2d9ab 100644 --- a/packages/graphql/src/instance.test.ts +++ b/packages/graphql/src/instance.test.ts @@ -21,6 +21,7 @@ import { type Database, schema } from "@drfed/models"; import { describe, it } from "@logtape/testing-node/autoload"; import { DrizzleQueryError } from "drizzle-orm"; +import { hashSecret } from "./auth/hash.ts"; import { withTestHarness } from "./harness.test.ts"; const accepted = new Date("2026-06-24T00:00:00.000Z"); @@ -904,19 +905,6 @@ async function createSession( return { headers: { authorization: `Bearer ${token}` } }; } -/** - * Computes the SHA-256 hex digest the server stores for a bearer token, - * mirroring `hashSecret` in *auth/hash.ts*. - * - * @param raw The raw access token. - * @returns The lowercase hex-encoded SHA-256 digest. - */ -async function hashSecret(raw: string): Promise { - return new Uint8Array( - await crypto.subtle.digest("SHA-256", new TextEncoder().encode(raw)), - ).toHex(); -} - async function seedInstanceMembers(db: Database): Promise { await db.insert(schema.accounts).values([ { diff --git a/packages/graphql/src/instance.ts b/packages/graphql/src/instance.ts index 73321d3..f9b282d 100644 --- a/packages/graphql/src/instance.ts +++ b/packages/graphql/src/instance.ts @@ -15,14 +15,10 @@ // along with this program. If not, see . import { schema } from "@drfed/models"; -import { instanceMembers } from "@drfed/models/schema"; -import { drizzleConnectionHelpers } from "@pothos/plugin-drizzle"; import { DrizzleQueryError } from "drizzle-orm"; -import { and, eq, isNotNull } from "drizzle-orm/sql/expressions"; +import { eq } from "drizzle-orm/sql/expressions"; import { v7 as uuid } from "uuid"; -// oxlint-disable-next-line import/no-cycle -import { Account } from "./account.ts"; import builder, { type DrFedObjectRef } from "./builder.ts"; const InstanceRef = builder.drizzleNode("instances", { @@ -122,103 +118,6 @@ builder.drizzleObjectField(LocalInstanceRef, "instance", (t) => }), ); -const instanceMembersConnection = drizzleConnectionHelpers( - builder, - "instanceMembers", - { - query: { - orderBy: { created: "desc" }, - }, - select(nestedSelection) { - return { - with: { - account: nestedSelection(), - }, - where: { - accepted: { isNotNull: true }, - }, - }; - }, - resolveNode(instanceMember) { - return instanceMember.account; - }, - }, -); - -builder.drizzleObjectField(InstanceRef, "members", (t) => - t.connection( - { - type: Account, - description: "The `Account`s that belong to the `Instance`.", - select(args, ctx, nestedSelection) { - return { - with: { - instanceMembers: instanceMembersConnection.getQuery( - args, - ctx, - nestedSelection, - ), - }, - }; - }, - resolve(instance, args, ctx) { - return { - ...instanceMembersConnection.resolve( - instance.instanceMembers, - args, - ctx, - instance, - ), - totalCount() { - return ctx.db.$count( - instanceMembers, - and( - eq(instanceMembers.instanceId, instance.id), - isNotNull(instanceMembers.accepted), - ), - ); - }, - }; - }, - }, - { - fields(fb) { - return { - totalCount: fb.int({ - description: - "The total number of `Account`s that belong to the `Instance`." + - "Note that pending members are not counted.", - resolve(connection) { - return connection.totalCount(); - }, - }), - }; - }, - }, - { - fields(fb) { - return { - created: fb.expose("created", { - type: "DateTime", - description: - "The date/time when the `Account` was added to the `Instance`.", - }), - accepted: fb.expose("accepted", { - type: "DateTime", - nullable: true, - description: - "The date/time when the `Account` accepted membership in the `Instance`.", - }), - admin: fb.exposeBoolean("admin", { - description: - "Whether the `Account` has administrator privileges in the `Instance`.", - }), - }; - }, - }, - ), -); - builder.queryFields((t) => ({ localInstanceBySlug: t.drizzleField({ type: LocalInstanceRef, diff --git a/packages/graphql/src/schema.ts b/packages/graphql/src/schema.ts index 84bfa4f..f5af295 100644 --- a/packages/graphql/src/schema.ts +++ b/packages/graphql/src/schema.ts @@ -17,6 +17,7 @@ import "./account.ts"; import "./instance.ts"; import "./auth/entry.ts"; +import "./actor.ts"; import builder from "./builder.ts"; builder.queryType({}); diff --git a/packages/models/drizzle/20260821072150_merge_remote_actors/migration.sql b/packages/models/drizzle/20260821072150_merge_remote_actors/migration.sql new file mode 100644 index 0000000..ac731f6 --- /dev/null +++ b/packages/models/drizzle/20260821072150_merge_remote_actors/migration.sql @@ -0,0 +1,54 @@ +CREATE TYPE "actor_type" AS ENUM('Application', 'Group', 'Organization', 'Person', 'Service');--> statement-breakpoint +CREATE TABLE "actors" ( + "id" uuid PRIMARY KEY, + "localId" uuid UNIQUE, + "type" "actor_type" NOT NULL, + "username" text NOT NULL, + "instanceId" uuid NOT NULL, + "iri" text NOT NULL UNIQUE, + "inboxUrl" text NOT NULL, + "outboxUrl" text NOT NULL, + "followersUrl" text, + "followeesUrl" text, + "featuredUrl" text, + "profileUrl" text, + "avatarUrl" text, + "headerUrl" text, + "name" text, + "bioHtml" text, + "automaticallyApprovesFollowers" boolean DEFAULT false NOT NULL, + "fieldHtmls" jsonb DEFAULT '{}' NOT NULL, + "emojis" jsonb DEFAULT '{}' NOT NULL, + "tags" jsonb DEFAULT '{}' NOT NULL, + "sensitive" boolean DEFAULT false NOT NULL, + "suspended" timestamp with time zone, + "suspendedUntil" timestamp with time zone, + "successorId" uuid, + "aliases" text[] DEFAULT (ARRAY[]::text[])::text[] NOT NULL, + "followeesCount" integer DEFAULT 0 NOT NULL, + "followersCount" integer DEFAULT 0 NOT NULL, + "postsCount" integer DEFAULT 0 NOT NULL, + "updated" timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL, + "published" timestamp with time zone, + "created" timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL, + "deleted" timestamp with time zone, + CONSTRAINT "username_key" UNIQUE("username","instanceId"), + CONSTRAINT "actors_username_check" CHECK ("username" NOT LIKE '%@%'), + CONSTRAINT "actors_suspended_check" CHECK ( + "suspendedUntil" IS NULL OR ( + "suspended" IS NOT NULL AND + "suspendedUntil" > "suspended" + ) + ) +); +--> statement-breakpoint +CREATE TABLE "local_actors" ( + "id" uuid PRIMARY KEY, + "avatar" text, + "header" text +); +--> statement-breakpoint +CREATE INDEX "actor_instance_index" ON "actors" ("instanceId");--> statement-breakpoint +ALTER TABLE "actors" ADD CONSTRAINT "actors_localId_local_actors_id_fkey" FOREIGN KEY ("localId") REFERENCES "local_actors"("id") ON DELETE CASCADE;--> statement-breakpoint +ALTER TABLE "actors" ADD CONSTRAINT "actors_instanceId_instances_id_fkey" FOREIGN KEY ("instanceId") REFERENCES "instances"("id") ON DELETE CASCADE;--> statement-breakpoint +ALTER TABLE "actors" ADD CONSTRAINT "actors_successorId_actors_id_fkey" FOREIGN KEY ("successorId") REFERENCES "actors"("id") ON DELETE SET NULL; \ No newline at end of file diff --git a/packages/models/drizzle/20260821072150_merge_remote_actors/snapshot.json b/packages/models/drizzle/20260821072150_merge_remote_actors/snapshot.json new file mode 100644 index 0000000..1caf8f4 --- /dev/null +++ b/packages/models/drizzle/20260821072150_merge_remote_actors/snapshot.json @@ -0,0 +1,1312 @@ +{ + "version": "8", + "dialect": "postgres", + "id": "ac4fc4e2-2217-4c4c-9c8a-0ec2a8162819", + "prevIds": ["d8ac955f-1e74-4493-ade9-a25c40b01c20"], + "ddl": [ + { + "values": ["Application", "Group", "Organization", "Person", "Service"], + "name": "actor_type", + "entityType": "enums", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "accounts", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "actors", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "instance_members", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "instances", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "local_actors", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "local_instances", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "login_tokens", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "sessions", + "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": "varchar(100)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "name", + "entityType": "columns", + "schema": "public", + "table": "accounts" + }, + { + "type": "integer", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "10", + "generated": null, + "identity": null, + "name": "max_instances", + "entityType": "columns", + "schema": "public", + "table": "accounts" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "false", + "generated": null, + "identity": null, + "name": "admin", + "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": "id", + "entityType": "columns", + "schema": "public", + "table": "actors" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "localId", + "entityType": "columns", + "schema": "public", + "table": "actors" + }, + { + "type": "actor_type", + "typeSchema": "public", + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "type", + "entityType": "columns", + "schema": "public", + "table": "actors" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "username", + "entityType": "columns", + "schema": "public", + "table": "actors" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "instanceId", + "entityType": "columns", + "schema": "public", + "table": "actors" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "iri", + "entityType": "columns", + "schema": "public", + "table": "actors" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "inboxUrl", + "entityType": "columns", + "schema": "public", + "table": "actors" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "outboxUrl", + "entityType": "columns", + "schema": "public", + "table": "actors" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "followersUrl", + "entityType": "columns", + "schema": "public", + "table": "actors" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "followeesUrl", + "entityType": "columns", + "schema": "public", + "table": "actors" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "featuredUrl", + "entityType": "columns", + "schema": "public", + "table": "actors" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "profileUrl", + "entityType": "columns", + "schema": "public", + "table": "actors" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "avatarUrl", + "entityType": "columns", + "schema": "public", + "table": "actors" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "headerUrl", + "entityType": "columns", + "schema": "public", + "table": "actors" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "name", + "entityType": "columns", + "schema": "public", + "table": "actors" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "bioHtml", + "entityType": "columns", + "schema": "public", + "table": "actors" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "false", + "generated": null, + "identity": null, + "name": "automaticallyApprovesFollowers", + "entityType": "columns", + "schema": "public", + "table": "actors" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'{}'", + "generated": null, + "identity": null, + "name": "fieldHtmls", + "entityType": "columns", + "schema": "public", + "table": "actors" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'{}'", + "generated": null, + "identity": null, + "name": "emojis", + "entityType": "columns", + "schema": "public", + "table": "actors" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'{}'", + "generated": null, + "identity": null, + "name": "tags", + "entityType": "columns", + "schema": "public", + "table": "actors" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "false", + "generated": null, + "identity": null, + "name": "sensitive", + "entityType": "columns", + "schema": "public", + "table": "actors" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "suspended", + "entityType": "columns", + "schema": "public", + "table": "actors" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "suspendedUntil", + "entityType": "columns", + "schema": "public", + "table": "actors" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "successorId", + "entityType": "columns", + "schema": "public", + "table": "actors" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 1, + "default": "(ARRAY[]::text[])", + "generated": null, + "identity": null, + "name": "aliases", + "entityType": "columns", + "schema": "public", + "table": "actors" + }, + { + "type": "integer", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "0", + "generated": null, + "identity": null, + "name": "followeesCount", + "entityType": "columns", + "schema": "public", + "table": "actors" + }, + { + "type": "integer", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "0", + "generated": null, + "identity": null, + "name": "followersCount", + "entityType": "columns", + "schema": "public", + "table": "actors" + }, + { + "type": "integer", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "0", + "generated": null, + "identity": null, + "name": "postsCount", + "entityType": "columns", + "schema": "public", + "table": "actors" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "CURRENT_TIMESTAMP", + "generated": null, + "identity": null, + "name": "updated", + "entityType": "columns", + "schema": "public", + "table": "actors" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "published", + "entityType": "columns", + "schema": "public", + "table": "actors" + }, + { + "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": "actors" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "deleted", + "entityType": "columns", + "schema": "public", + "table": "actors" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "accountId", + "entityType": "columns", + "schema": "public", + "table": "instance_members" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "instanceId", + "entityType": "columns", + "schema": "public", + "table": "instance_members" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "false", + "generated": null, + "identity": null, + "name": "admin", + "entityType": "columns", + "schema": "public", + "table": "instance_members" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "accepted", + "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": "uuid", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "localId", + "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" + }, + { + "type": "varchar(100)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "host", + "entityType": "columns", + "schema": "public", + "table": "instances" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "nodeInfoUrl", + "entityType": "columns", + "schema": "public", + "table": "instances" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "software", + "entityType": "columns", + "schema": "public", + "table": "instances" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "softwareVersion", + "entityType": "columns", + "schema": "public", + "table": "instances" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "local_actors" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "avatar", + "entityType": "columns", + "schema": "public", + "table": "local_actors" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "header", + "entityType": "columns", + "schema": "public", + "table": "local_actors" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "local_instances" + }, + { + "type": "varchar(63)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "slug", + "entityType": "columns", + "schema": "public", + "table": "local_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": "local_instances" + }, + { + "type": "integer", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "10", + "generated": null, + "identity": null, + "name": "maxActors", + "entityType": "columns", + "schema": "public", + "table": "local_instances" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "login_tokens" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "accountId", + "entityType": "columns", + "schema": "public", + "table": "login_tokens" + }, + { + "type": "varchar(64)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "tokenHash", + "entityType": "columns", + "schema": "public", + "table": "login_tokens" + }, + { + "type": "varchar(64)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "codeHash", + "entityType": "columns", + "schema": "public", + "table": "login_tokens" + }, + { + "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": "login_tokens" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "CURRENT_TIMESTAMP + INTERVAL '15 minutes'", + "generated": null, + "identity": null, + "name": "expires", + "entityType": "columns", + "schema": "public", + "table": "login_tokens" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "consumed", + "entityType": "columns", + "schema": "public", + "table": "login_tokens" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "sessions" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "accountId", + "entityType": "columns", + "schema": "public", + "table": "sessions" + }, + { + "type": "varchar(64)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "tokenHash", + "entityType": "columns", + "schema": "public", + "table": "sessions" + }, + { + "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": "sessions" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "CURRENT_TIMESTAMP + INTERVAL '1 month'", + "generated": null, + "identity": null, + "name": "expires", + "entityType": "columns", + "schema": "public", + "table": "sessions" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "instanceId", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "actor_instance_index", + "entityType": "indexes", + "schema": "public", + "table": "actors" + }, + { + "nameExplicit": false, + "columns": [ + { + "value": "accountId", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": "\"accepted\" IS NOT NULL", + "with": "", + "method": "btree", + "concurrently": false, + "name": "instance_members_accountId_index", + "entityType": "indexes", + "schema": "public", + "table": "instance_members" + }, + { + "nameExplicit": false, + "columns": [ + { + "value": "instanceId", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": "\"accepted\" IS NOT NULL", + "with": "", + "method": "btree", + "concurrently": false, + "name": "instance_members_instanceId_index", + "entityType": "indexes", + "schema": "public", + "table": "instance_members" + }, + { + "nameExplicit": false, + "columns": ["localId"], + "schemaTo": "public", + "tableTo": "local_actors", + "columnsTo": ["id"], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "actors_localId_local_actors_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "actors" + }, + { + "nameExplicit": false, + "columns": ["instanceId"], + "schemaTo": "public", + "tableTo": "instances", + "columnsTo": ["id"], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "actors_instanceId_instances_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "actors" + }, + { + "nameExplicit": false, + "columns": ["successorId"], + "schemaTo": "public", + "tableTo": "actors", + "columnsTo": ["id"], + "onUpdate": "NO ACTION", + "onDelete": "SET NULL", + "name": "actors_successorId_actors_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "actors" + }, + { + "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" + }, + { + "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": ["localId"], + "schemaTo": "public", + "tableTo": "local_instances", + "columnsTo": ["id"], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "instances_localId_local_instances_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "instances" + }, + { + "nameExplicit": false, + "columns": ["accountId"], + "schemaTo": "public", + "tableTo": "accounts", + "columnsTo": ["id"], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "login_tokens_accountId_accounts_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "login_tokens" + }, + { + "nameExplicit": false, + "columns": ["accountId"], + "schemaTo": "public", + "tableTo": "accounts", + "columnsTo": ["id"], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "sessions_accountId_accounts_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "sessions" + }, + { + "columns": ["instanceId", "accountId"], + "nameExplicit": false, + "name": "instance_members_pkey", + "entityType": "pks", + "schema": "public", + "table": "instance_members" + }, + { + "columns": ["id"], + "nameExplicit": false, + "name": "accounts_pkey", + "schema": "public", + "table": "accounts", + "entityType": "pks" + }, + { + "columns": ["id"], + "nameExplicit": false, + "name": "actors_pkey", + "schema": "public", + "table": "actors", + "entityType": "pks" + }, + { + "columns": ["id"], + "nameExplicit": false, + "name": "instances_pkey", + "schema": "public", + "table": "instances", + "entityType": "pks" + }, + { + "columns": ["id"], + "nameExplicit": false, + "name": "local_actors_pkey", + "schema": "public", + "table": "local_actors", + "entityType": "pks" + }, + { + "columns": ["id"], + "nameExplicit": false, + "name": "local_instances_pkey", + "schema": "public", + "table": "local_instances", + "entityType": "pks" + }, + { + "columns": ["id"], + "nameExplicit": false, + "name": "login_tokens_pkey", + "schema": "public", + "table": "login_tokens", + "entityType": "pks" + }, + { + "columns": ["id"], + "nameExplicit": false, + "name": "sessions_pkey", + "schema": "public", + "table": "sessions", + "entityType": "pks" + }, + { + "nameExplicit": true, + "columns": ["username", "instanceId"], + "nullsNotDistinct": false, + "name": "username_key", + "entityType": "uniques", + "schema": "public", + "table": "actors" + }, + { + "nameExplicit": false, + "columns": ["email"], + "nullsNotDistinct": false, + "name": "accounts_email_key", + "schema": "public", + "table": "accounts", + "entityType": "uniques" + }, + { + "nameExplicit": false, + "columns": ["localId"], + "nullsNotDistinct": false, + "name": "actors_localId_key", + "schema": "public", + "table": "actors", + "entityType": "uniques" + }, + { + "nameExplicit": false, + "columns": ["iri"], + "nullsNotDistinct": false, + "name": "actors_iri_key", + "schema": "public", + "table": "actors", + "entityType": "uniques" + }, + { + "nameExplicit": false, + "columns": ["host"], + "nullsNotDistinct": false, + "name": "instances_host_key", + "schema": "public", + "table": "instances", + "entityType": "uniques" + }, + { + "nameExplicit": false, + "columns": ["slug"], + "nullsNotDistinct": false, + "name": "local_instances_slug_key", + "schema": "public", + "table": "local_instances", + "entityType": "uniques" + }, + { + "nameExplicit": false, + "columns": ["tokenHash"], + "nullsNotDistinct": false, + "name": "login_tokens_tokenHash_key", + "schema": "public", + "table": "login_tokens", + "entityType": "uniques" + }, + { + "nameExplicit": false, + "columns": ["tokenHash"], + "nullsNotDistinct": false, + "name": "sessions_tokenHash_key", + "schema": "public", + "table": "sessions", + "entityType": "uniques" + }, + { + "value": "\"email\" ~ '^[^@]+@[^@]+\\.[^@]+$'", + "name": "accounts_email_check", + "entityType": "checks", + "schema": "public", + "table": "accounts" + }, + { + "value": "\"max_instances\" >= 0", + "name": "accounts_max_instances_check", + "entityType": "checks", + "schema": "public", + "table": "accounts" + }, + { + "value": "trim(both from \"name\") <> ''", + "name": "accounts_name_check", + "entityType": "checks", + "schema": "public", + "table": "accounts" + }, + { + "value": "\"username\" NOT LIKE '%@%'", + "name": "actors_username_check", + "entityType": "checks", + "schema": "public", + "table": "actors" + }, + { + "value": "\n \"suspendedUntil\" IS NULL OR (\n \"suspended\" IS NOT NULL AND\n \"suspendedUntil\" > \"suspended\"\n )\n ", + "name": "actors_suspended_check", + "entityType": "checks", + "schema": "public", + "table": "actors" + }, + { + "value": "\"slug\" ~ '^[a-z0-9-]{4,63}$'", + "name": "instances_slug_check", + "entityType": "checks", + "schema": "public", + "table": "local_instances" + }, + { + "value": "\"maxActors\" > 0", + "name": "instances_max_actors_check", + "entityType": "checks", + "schema": "public", + "table": "local_instances" + } + ], + "renames": [] +} diff --git a/packages/models/drizzle/20260906052949_rename_followees_to_following/migration.sql b/packages/models/drizzle/20260906052949_rename_followees_to_following/migration.sql new file mode 100644 index 0000000..52f797b --- /dev/null +++ b/packages/models/drizzle/20260906052949_rename_followees_to_following/migration.sql @@ -0,0 +1,2 @@ +ALTER TABLE "actors" RENAME COLUMN "followeesUrl" TO "followingUrl";--> statement-breakpoint +ALTER TABLE "actors" RENAME COLUMN "followeesCount" TO "followingCount"; \ No newline at end of file diff --git a/packages/models/drizzle/20260906052949_rename_followees_to_following/snapshot.json b/packages/models/drizzle/20260906052949_rename_followees_to_following/snapshot.json new file mode 100644 index 0000000..732f941 --- /dev/null +++ b/packages/models/drizzle/20260906052949_rename_followees_to_following/snapshot.json @@ -0,0 +1,1315 @@ +{ + "version": "8", + "dialect": "postgres", + "id": "6b70d7c3-f645-4130-8f85-073c3204c78d", + "prevIds": ["ac4fc4e2-2217-4c4c-9c8a-0ec2a8162819"], + "ddl": [ + { + "values": ["Application", "Group", "Organization", "Person", "Service"], + "name": "actor_type", + "entityType": "enums", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "accounts", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "actors", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "instance_members", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "instances", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "local_actors", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "local_instances", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "login_tokens", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "sessions", + "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": "varchar(100)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "name", + "entityType": "columns", + "schema": "public", + "table": "accounts" + }, + { + "type": "integer", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "10", + "generated": null, + "identity": null, + "name": "max_instances", + "entityType": "columns", + "schema": "public", + "table": "accounts" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "false", + "generated": null, + "identity": null, + "name": "admin", + "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": "id", + "entityType": "columns", + "schema": "public", + "table": "actors" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "localId", + "entityType": "columns", + "schema": "public", + "table": "actors" + }, + { + "type": "actor_type", + "typeSchema": "public", + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "type", + "entityType": "columns", + "schema": "public", + "table": "actors" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "username", + "entityType": "columns", + "schema": "public", + "table": "actors" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "instanceId", + "entityType": "columns", + "schema": "public", + "table": "actors" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "iri", + "entityType": "columns", + "schema": "public", + "table": "actors" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "inboxUrl", + "entityType": "columns", + "schema": "public", + "table": "actors" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "outboxUrl", + "entityType": "columns", + "schema": "public", + "table": "actors" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "followersUrl", + "entityType": "columns", + "schema": "public", + "table": "actors" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "followingUrl", + "entityType": "columns", + "schema": "public", + "table": "actors" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "featuredUrl", + "entityType": "columns", + "schema": "public", + "table": "actors" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "profileUrl", + "entityType": "columns", + "schema": "public", + "table": "actors" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "avatarUrl", + "entityType": "columns", + "schema": "public", + "table": "actors" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "headerUrl", + "entityType": "columns", + "schema": "public", + "table": "actors" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "name", + "entityType": "columns", + "schema": "public", + "table": "actors" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "bioHtml", + "entityType": "columns", + "schema": "public", + "table": "actors" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "false", + "generated": null, + "identity": null, + "name": "automaticallyApprovesFollowers", + "entityType": "columns", + "schema": "public", + "table": "actors" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'{}'", + "generated": null, + "identity": null, + "name": "fieldHtmls", + "entityType": "columns", + "schema": "public", + "table": "actors" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'{}'", + "generated": null, + "identity": null, + "name": "emojis", + "entityType": "columns", + "schema": "public", + "table": "actors" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'{}'", + "generated": null, + "identity": null, + "name": "tags", + "entityType": "columns", + "schema": "public", + "table": "actors" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "false", + "generated": null, + "identity": null, + "name": "sensitive", + "entityType": "columns", + "schema": "public", + "table": "actors" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "suspended", + "entityType": "columns", + "schema": "public", + "table": "actors" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "suspendedUntil", + "entityType": "columns", + "schema": "public", + "table": "actors" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "successorId", + "entityType": "columns", + "schema": "public", + "table": "actors" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 1, + "default": "(ARRAY[]::text[])", + "generated": null, + "identity": null, + "name": "aliases", + "entityType": "columns", + "schema": "public", + "table": "actors" + }, + { + "type": "integer", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "0", + "generated": null, + "identity": null, + "name": "followingCount", + "entityType": "columns", + "schema": "public", + "table": "actors" + }, + { + "type": "integer", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "0", + "generated": null, + "identity": null, + "name": "followersCount", + "entityType": "columns", + "schema": "public", + "table": "actors" + }, + { + "type": "integer", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "0", + "generated": null, + "identity": null, + "name": "postsCount", + "entityType": "columns", + "schema": "public", + "table": "actors" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "CURRENT_TIMESTAMP", + "generated": null, + "identity": null, + "name": "updated", + "entityType": "columns", + "schema": "public", + "table": "actors" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "published", + "entityType": "columns", + "schema": "public", + "table": "actors" + }, + { + "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": "actors" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "deleted", + "entityType": "columns", + "schema": "public", + "table": "actors" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "accountId", + "entityType": "columns", + "schema": "public", + "table": "instance_members" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "instanceId", + "entityType": "columns", + "schema": "public", + "table": "instance_members" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "false", + "generated": null, + "identity": null, + "name": "admin", + "entityType": "columns", + "schema": "public", + "table": "instance_members" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "accepted", + "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": "uuid", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "localId", + "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" + }, + { + "type": "varchar(100)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "host", + "entityType": "columns", + "schema": "public", + "table": "instances" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "nodeInfoUrl", + "entityType": "columns", + "schema": "public", + "table": "instances" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "software", + "entityType": "columns", + "schema": "public", + "table": "instances" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "softwareVersion", + "entityType": "columns", + "schema": "public", + "table": "instances" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "local_actors" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "avatar", + "entityType": "columns", + "schema": "public", + "table": "local_actors" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "header", + "entityType": "columns", + "schema": "public", + "table": "local_actors" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "local_instances" + }, + { + "type": "varchar(63)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "slug", + "entityType": "columns", + "schema": "public", + "table": "local_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": "local_instances" + }, + { + "type": "integer", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "10", + "generated": null, + "identity": null, + "name": "maxActors", + "entityType": "columns", + "schema": "public", + "table": "local_instances" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "login_tokens" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "accountId", + "entityType": "columns", + "schema": "public", + "table": "login_tokens" + }, + { + "type": "varchar(64)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "tokenHash", + "entityType": "columns", + "schema": "public", + "table": "login_tokens" + }, + { + "type": "varchar(64)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "codeHash", + "entityType": "columns", + "schema": "public", + "table": "login_tokens" + }, + { + "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": "login_tokens" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "CURRENT_TIMESTAMP + INTERVAL '15 minutes'", + "generated": null, + "identity": null, + "name": "expires", + "entityType": "columns", + "schema": "public", + "table": "login_tokens" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "consumed", + "entityType": "columns", + "schema": "public", + "table": "login_tokens" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "sessions" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "accountId", + "entityType": "columns", + "schema": "public", + "table": "sessions" + }, + { + "type": "varchar(64)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "tokenHash", + "entityType": "columns", + "schema": "public", + "table": "sessions" + }, + { + "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": "sessions" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "CURRENT_TIMESTAMP + INTERVAL '1 month'", + "generated": null, + "identity": null, + "name": "expires", + "entityType": "columns", + "schema": "public", + "table": "sessions" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "instanceId", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "actor_instance_index", + "entityType": "indexes", + "schema": "public", + "table": "actors" + }, + { + "nameExplicit": false, + "columns": [ + { + "value": "accountId", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": "\"accepted\" IS NOT NULL", + "with": "", + "method": "btree", + "concurrently": false, + "name": "instance_members_accountId_index", + "entityType": "indexes", + "schema": "public", + "table": "instance_members" + }, + { + "nameExplicit": false, + "columns": [ + { + "value": "instanceId", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": "\"accepted\" IS NOT NULL", + "with": "", + "method": "btree", + "concurrently": false, + "name": "instance_members_instanceId_index", + "entityType": "indexes", + "schema": "public", + "table": "instance_members" + }, + { + "nameExplicit": false, + "columns": ["localId"], + "schemaTo": "public", + "tableTo": "local_actors", + "columnsTo": ["id"], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "actors_localId_local_actors_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "actors" + }, + { + "nameExplicit": false, + "columns": ["instanceId"], + "schemaTo": "public", + "tableTo": "instances", + "columnsTo": ["id"], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "actors_instanceId_instances_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "actors" + }, + { + "nameExplicit": false, + "columns": ["successorId"], + "schemaTo": "public", + "tableTo": "actors", + "columnsTo": ["id"], + "onUpdate": "NO ACTION", + "onDelete": "SET NULL", + "name": "actors_successorId_actors_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "actors" + }, + { + "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" + }, + { + "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": ["localId"], + "schemaTo": "public", + "tableTo": "local_instances", + "columnsTo": ["id"], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "instances_localId_local_instances_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "instances" + }, + { + "nameExplicit": false, + "columns": ["accountId"], + "schemaTo": "public", + "tableTo": "accounts", + "columnsTo": ["id"], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "login_tokens_accountId_accounts_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "login_tokens" + }, + { + "nameExplicit": false, + "columns": ["accountId"], + "schemaTo": "public", + "tableTo": "accounts", + "columnsTo": ["id"], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "sessions_accountId_accounts_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "sessions" + }, + { + "columns": ["instanceId", "accountId"], + "nameExplicit": false, + "name": "instance_members_pkey", + "entityType": "pks", + "schema": "public", + "table": "instance_members" + }, + { + "columns": ["id"], + "nameExplicit": false, + "name": "accounts_pkey", + "schema": "public", + "table": "accounts", + "entityType": "pks" + }, + { + "columns": ["id"], + "nameExplicit": false, + "name": "actors_pkey", + "schema": "public", + "table": "actors", + "entityType": "pks" + }, + { + "columns": ["id"], + "nameExplicit": false, + "name": "instances_pkey", + "schema": "public", + "table": "instances", + "entityType": "pks" + }, + { + "columns": ["id"], + "nameExplicit": false, + "name": "local_actors_pkey", + "schema": "public", + "table": "local_actors", + "entityType": "pks" + }, + { + "columns": ["id"], + "nameExplicit": false, + "name": "local_instances_pkey", + "schema": "public", + "table": "local_instances", + "entityType": "pks" + }, + { + "columns": ["id"], + "nameExplicit": false, + "name": "login_tokens_pkey", + "schema": "public", + "table": "login_tokens", + "entityType": "pks" + }, + { + "columns": ["id"], + "nameExplicit": false, + "name": "sessions_pkey", + "schema": "public", + "table": "sessions", + "entityType": "pks" + }, + { + "nameExplicit": true, + "columns": ["username", "instanceId"], + "nullsNotDistinct": false, + "name": "username_key", + "entityType": "uniques", + "schema": "public", + "table": "actors" + }, + { + "nameExplicit": false, + "columns": ["email"], + "nullsNotDistinct": false, + "name": "accounts_email_key", + "schema": "public", + "table": "accounts", + "entityType": "uniques" + }, + { + "nameExplicit": false, + "columns": ["localId"], + "nullsNotDistinct": false, + "name": "actors_localId_key", + "schema": "public", + "table": "actors", + "entityType": "uniques" + }, + { + "nameExplicit": false, + "columns": ["iri"], + "nullsNotDistinct": false, + "name": "actors_iri_key", + "schema": "public", + "table": "actors", + "entityType": "uniques" + }, + { + "nameExplicit": false, + "columns": ["host"], + "nullsNotDistinct": false, + "name": "instances_host_key", + "schema": "public", + "table": "instances", + "entityType": "uniques" + }, + { + "nameExplicit": false, + "columns": ["slug"], + "nullsNotDistinct": false, + "name": "local_instances_slug_key", + "schema": "public", + "table": "local_instances", + "entityType": "uniques" + }, + { + "nameExplicit": false, + "columns": ["tokenHash"], + "nullsNotDistinct": false, + "name": "login_tokens_tokenHash_key", + "schema": "public", + "table": "login_tokens", + "entityType": "uniques" + }, + { + "nameExplicit": false, + "columns": ["tokenHash"], + "nullsNotDistinct": false, + "name": "sessions_tokenHash_key", + "schema": "public", + "table": "sessions", + "entityType": "uniques" + }, + { + "value": "\"email\" ~ '^[^@]+@[^@]+\\.[^@]+$'", + "name": "accounts_email_check", + "entityType": "checks", + "schema": "public", + "table": "accounts" + }, + { + "value": "\"max_instances\" >= 0", + "name": "accounts_max_instances_check", + "entityType": "checks", + "schema": "public", + "table": "accounts" + }, + { + "value": "trim(both from \"name\") <> ''", + "name": "accounts_name_check", + "entityType": "checks", + "schema": "public", + "table": "accounts" + }, + { + "value": "\"username\" NOT LIKE '%@%'", + "name": "actors_username_check", + "entityType": "checks", + "schema": "public", + "table": "actors" + }, + { + "value": "\n \"suspendedUntil\" IS NULL OR (\n \"suspended\" IS NOT NULL AND\n \"suspendedUntil\" > \"suspended\"\n )\n ", + "name": "actors_suspended_check", + "entityType": "checks", + "schema": "public", + "table": "actors" + }, + { + "value": "\"slug\" ~ '^[a-z0-9-]{4,63}$'", + "name": "instances_slug_check", + "entityType": "checks", + "schema": "public", + "table": "local_instances" + }, + { + "value": "\"maxActors\" > 0", + "name": "instances_max_actors_check", + "entityType": "checks", + "schema": "public", + "table": "local_instances" + } + ], + "renames": [ + "public.actors.followeesUrl->public.actors.followingUrl", + "public.actors.followeesCount->public.actors.followingCount" + ] +} diff --git a/packages/models/package.json b/packages/models/package.json index 4d2ccc3..51b9ec6 100644 --- a/packages/models/package.json +++ b/packages/models/package.json @@ -94,7 +94,6 @@ "devDependencies": { "@logtape/testing-node": "catalog:", "@types/node": "catalog:", - "@types/pg": "catalog:", "drizzle-kit": "1.0.0-beta.22", "tsdown": "catalog:", "typescript": "catalog:" @@ -103,6 +102,6 @@ "@electric-sql/pglite": "catalog:", "@logtape/logtape": "catalog:", "drizzle-orm": "catalog:", - "pg": "catalog:" + "postgres": "catalog:" } } diff --git a/packages/models/src/migrate.ts b/packages/models/src/migrate.ts index 4d7f93b..b8c5135 100644 --- a/packages/models/src/migrate.ts +++ b/packages/models/src/migrate.ts @@ -18,25 +18,26 @@ 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"; +import { drizzle as drizzlePostgres } from "drizzle-orm/postgres-js"; +import { migrate as migratePostgres } from "drizzle-orm/postgres-js/migrator"; +import postgres, { type Options, type Sql } from "postgres"; export type MigrateCredentials = | PostgresMigrateCredentials | PGliteMigrateCredentials; export type PostgresMigrateCredentials = - | (Omit & { - readonly url: string; + | { readonly driver?: never; - }) - | (PoolConfig & { - readonly url?: never; + readonly url: string; + readonly options?: Omit>, "max">; + } + | { readonly driver?: never; - }); + readonly client: Sql; + }; export type PGliteMigrateCredentials = | { @@ -143,22 +144,18 @@ 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, - }); + const client = + "client" in credentials + ? credentials.client + : postgres(credentials.url, { ...credentials.options, max: 1 }); + const shouldCloseClient = !("client" in credentials); try { - await migratePostgres(drizzlePostgres({ client: pool }), config); + await migratePostgres(drizzlePostgres({ client }), config); } finally { - await pool.end(); + if (shouldCloseClient) { + await client.end(); + } } } diff --git a/packages/models/src/relations.ts b/packages/models/src/relations.ts index 1b8a8f3..ebb6c76 100644 --- a/packages/models/src/relations.ts +++ b/packages/models/src/relations.ts @@ -72,6 +72,7 @@ export const relations = defineRelations(schema, (r) => ({ accepted: { isNotNull: true }, }, }), + actors: r.many.actors({ from: r.instances.id, to: r.actors.instanceId }), localInstance: r.one.localInstances({ from: r.instances.localId, to: r.localInstances.id, @@ -97,6 +98,20 @@ export const relations = defineRelations(schema, (r) => ({ optional: false, }), }, + actors: { + instance: r.one.instances({ + from: r.actors.instanceId, + to: r.instances.id, + optional: false, + }), + localActor: r.one.localActors({ + from: r.actors.localId, + to: r.localActors.id, + }), + }, + localActors: { + actor: r.one.actors({ from: r.localActors.id, to: r.actors.localId }), + }, })); export default relations; diff --git a/packages/models/src/schema.ts b/packages/models/src/schema.ts index d68bc1a..105ce8d 100644 --- a/packages/models/src/schema.ts +++ b/packages/models/src/schema.ts @@ -16,14 +16,18 @@ import { sql } from "drizzle-orm"; import { + type AnyPgColumn, boolean, check, index, integer, + jsonb, + pgEnum, pgTable, primaryKey, text, timestamp, + unique, uuid, varchar, } from "drizzle-orm/pg-core"; @@ -174,3 +178,100 @@ export const sessions = pgTable("sessions", { export type Session = typeof sessions.$inferSelect; export type NewSession = typeof sessions.$inferInsert; + +export const actorTypeEnum = pgEnum("actor_type", [ + "Application", + "Group", + "Organization", + "Person", + "Service", +]); + +export type ActorType = (typeof actorTypeEnum.enumValues)[number]; + +export const actors = pgTable( + "actors", + { + id: uuid().primaryKey(), + localId: uuid() + .unique() + .references(() => localActors.id, { onDelete: "cascade" }), + type: actorTypeEnum().notNull(), + username: text().notNull(), + instanceId: uuid() + .notNull() + .references(() => instances.id, { onDelete: "cascade" }), + iri: text().notNull().unique(), + inboxUrl: text().notNull(), + outboxUrl: text().notNull(), + followersUrl: text(), + followingUrl: text(), + featuredUrl: text(), + profileUrl: text(), + avatarUrl: text(), + headerUrl: text(), + name: text(), + bioHtml: text(), + automaticallyApprovesFollowers: boolean().notNull().default(false), + fieldHtmls: jsonb().$type>().notNull().default({}), + emojis: jsonb().$type>().notNull().default({}), + tags: jsonb().$type>().notNull().default({}), + sensitive: boolean().notNull().default(false), + // Moderation sanction state, denormalized from flag_action records + // (which remain the audit source of truth): + // - Not sanctioned: suspended IS NULL + // - Temporary suspension: suspended = start, suspendedUntil = end + // - Permanent suspension (ban) for local actors, or permanent federation + // block for remote actors: suspended set, suspendedUntil IS NULL + // Whether a sanction is *currently* active is always determined by + // comparing against the current time (lazy expiry; no cron): + // suspended <= now AND (suspendedUntil IS NULL OR suspendedUntil > now). + suspended: timestamp({ withTimezone: true }), + suspendedUntil: timestamp({ withTimezone: true }), + successorId: uuid().references((): AnyPgColumn => actors.id, { + onDelete: "set null", + }), + aliases: text() + .array() + .notNull() + .default(sql`(ARRAY[]::text[])`), + followingCount: integer().notNull().default(0), + followersCount: integer().notNull().default(0), + postsCount: integer().notNull().default(0), + updated: timestamp({ withTimezone: true }) + .notNull() + .default(currentTimestamp) + .$onUpdate(() => currentTimestamp), + published: timestamp({ withTimezone: true }), + created: timestamp({ withTimezone: true }) + .notNull() + .default(currentTimestamp), + deleted: timestamp({ withTimezone: true }), + }, + (t) => [ + unique("username_key").on(t.username, t.instanceId), + check("actors_username_check", sql`${t.username} NOT LIKE '%@%'`), + check( + "actors_suspended_check", + sql` + ${t.suspendedUntil} IS NULL OR ( + ${t.suspended} IS NOT NULL AND + ${t.suspendedUntil} > ${t.suspended} + ) + `, + ), + index("actor_instance_index").on(t.instanceId), + ], +); + +export type Actor = typeof actors.$inferSelect; +export type NewActor = typeof actors.$inferInsert; + +export const localActors = pgTable("local_actors", { + id: uuid().primaryKey(), + avatar: text(), + header: text(), +}); + +export type LocalActor = typeof localActors.$inferSelect; +export type NewLocalActor = typeof localActors.$inferInsert; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index cbc7c8e..adee240 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -9,6 +9,18 @@ catalogs: '@electric-sql/pglite': specifier: ^0.5.3 version: 0.5.3 + '@fedify/fedify': + specifier: 2.4.0-pr.1020.43+41cebe9c + version: 2.4.0-pr.1020.43 + '@fedify/pglite': + specifier: 2.4.0-pr.1020.43+41cebe9c + version: 2.4.0-pr.1020.43 + '@fedify/postgres': + specifier: 2.4.0-pr.1020.43+41cebe9c + version: 2.4.0-pr.1020.43 + '@fedify/vocab': + specifier: 2.4.0-pr.1020.43+41cebe9c + version: 2.4.0-pr.1020.43 '@logtape/drizzle-orm': specifier: ^2.2.2 version: 2.2.2 @@ -33,9 +45,6 @@ catalogs: '@types/node': specifier: ^26.0.0 version: 26.0.0 - '@types/pg': - specifier: ^8.20.0 - version: 8.20.0 '@upyo/core': specifier: 0.6.0-dev.263+e633e1e6 version: 0.6.0-dev.263 @@ -54,9 +63,9 @@ catalogs: graphql: specifier: ^16.14.2 version: 16.14.2 - pg: - specifier: ^8.21.0 - version: 8.21.0 + postgres: + specifier: ^3.4.9 + version: 3.4.9 skills-npm: specifier: ^1.2.0 version: 1.2.0 @@ -89,6 +98,15 @@ importers: '@electric-sql/pglite': specifier: 'catalog:' version: 0.5.3 + '@fedify/fedify': + specifier: 'catalog:' + version: 2.4.0-pr.1020.43 + '@fedify/pglite': + specifier: 'catalog:' + version: 2.4.0-pr.1020.43(@electric-sql/pglite@0.5.3)(@fedify/fedify@2.4.0-pr.1020.43) + '@fedify/postgres': + specifier: 'catalog:' + version: 2.4.0-pr.1020.43(@fedify/fedify@2.4.0-pr.1020.43)(postgres@3.4.9) '@logtape/drizzle-orm': specifier: 'catalog:' version: 2.2.2(@logtape/logtape@2.3.0-dev.840) @@ -112,13 +130,13 @@ importers: version: 0.6.0-dev.263(@upyo/core@0.6.0-dev.263) 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) + version: 1.0.0-beta.22(@electric-sql/pglite@0.5.3)(@opentelemetry/api@1.9.1)(@types/pg@8.20.0)(pg@8.21.0)(postgres@3.4.9) graphql: specifier: 'catalog:' version: 16.14.2 - pg: + postgres: specifier: 'catalog:' - version: 8.21.0 + version: 3.4.9 srvx: specifier: ^0.11.16 version: 0.11.16 @@ -129,9 +147,6 @@ importers: '@types/node': specifier: 'catalog:' version: 26.0.0 - '@types/pg': - specifier: 'catalog:' - version: 8.20.0 tsdown: specifier: 'catalog:' version: 0.22.14(typescript@7.0.2) @@ -144,9 +159,15 @@ importers: '@drfed/models': specifier: workspace:* version: link:../models + '@fedify/fedify': + specifier: 'catalog:' + version: 2.4.0-pr.1020.43 '@fedify/uri-template': specifier: ^2.3.1 version: 2.3.1 + '@fedify/vocab': + specifier: 'catalog:' + version: 2.4.0-pr.1020.43 '@logtape/graphql-yoga': specifier: 'catalog:' version: 2.3.0-dev.840(@logtape/logtape@2.3.0-dev.840)(graphql-yoga@5.21.2(graphql@16.14.2))(graphql@16.14.2) @@ -158,7 +179,7 @@ importers: 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) + 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)(@opentelemetry/api@1.9.1)(@types/pg@8.20.0)(pg@8.21.0)(postgres@3.4.9))(graphql@16.14.2) '@pothos/plugin-errors': specifier: ^4.9.1 version: 4.9.1(@pothos/core@4.13.0(graphql@16.14.2))(graphql@16.14.2) @@ -176,7 +197,7 @@ importers: version: 0.6.0-dev.263(@upyo/core@0.6.0-dev.263) 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) + version: 1.0.0-beta.22(@electric-sql/pglite@0.5.3)(@opentelemetry/api@1.9.1)(@types/pg@8.20.0)(pg@8.21.0)(postgres@3.4.9) graphql: specifier: 'catalog:' version: 16.14.2 @@ -216,10 +237,10 @@ importers: version: 2.3.0-dev.840 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: + version: 1.0.0-beta.22(@electric-sql/pglite@0.5.3)(@opentelemetry/api@1.9.1)(@types/pg@8.20.0)(pg@8.21.0)(postgres@3.4.9) + postgres: specifier: 'catalog:' - version: 8.21.0 + version: 3.4.9 devDependencies: '@logtape/testing-node': specifier: 'catalog:' @@ -227,9 +248,6 @@ importers: '@types/node': specifier: 'catalog:' version: 26.0.0 - '@types/pg': - specifier: 'catalog:' - version: 8.20.0 drizzle-kit: specifier: 1.0.0-beta.22 version: 1.0.0-beta.22 @@ -253,10 +271,10 @@ importers: version: 1.0.0(solid-js@1.9.14) '@solidjs/start': specifier: ^2.0.0 - version: 2.0.0(@solidjs/router@1.0.0(solid-js@1.9.14))(crossws@0.4.10(srvx@0.11.16))(supports-color@10.2.2)(vite@8.2.0(@types/node@26.0.0)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.0)) + version: 2.0.0(@solidjs/router@1.0.0(solid-js@1.9.14))(crossws@0.4.10(srvx@0.11.16))(vite@8.2.0(@types/node@26.0.0)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.0)) nitro: specifier: 3.0.260610-beta - version: 3.0.260610-beta(@electric-sql/pglite@0.5.3)(chokidar@5.0.0)(dotenv@17.4.2)(giget@3.3.0)(ioredis@5.11.1(supports-color@10.2.2))(jiti@2.7.0)(lru-cache@11.5.1)(rollup@4.62.2)(vite@8.2.0(@types/node@26.0.0)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.0)) + version: 3.0.260610-beta(@electric-sql/pglite@0.5.3)(chokidar@5.0.0)(dotenv@17.4.2)(giget@3.3.0)(ioredis@5.11.1)(jiti@2.7.0)(lru-cache@11.5.1)(rollup@4.62.2)(vite@8.2.0(@types/node@26.0.0)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.0)) relay-runtime: specifier: ^21.0.1 version: 21.0.1 @@ -278,7 +296,7 @@ importers: version: 20.1.1 eslint-plugin-solid: specifier: ^0.14.5 - version: 0.14.5(eslint@9.39.4(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@7.0.2) + version: 0.14.5(eslint@9.39.4(jiti@2.7.0))(typescript@7.0.2) relay-compiler: specifier: ^21.0.1 version: 21.0.1 @@ -379,6 +397,9 @@ packages: resolution: {integrity: sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==} engines: {node: '>=6.9.0'} + '@cfworker/json-schema@4.1.1': + resolution: {integrity: sha512-gAmrUZSGtKc3AiBL71iNWxDsyUC5uMaKKGdvzYsBoTW/xi42JQHl7eKV2OYzCUqvc+D2RCcf7EXY2iCyFIk6og==} + '@clack/core@1.4.3': resolution: {integrity: sha512-/kr3UWNtdJfxZtPgDqUOmG2pvwlmcLGheex5yiZKdwbzZJxhV+HMNR9QNmyY5cGwTNV6LrR7Jtp+KjhUAP1qBQ==} engines: {node: '>= 20.12.0'} @@ -392,6 +413,10 @@ packages: peerDependencies: solid-js: ^1.8 + '@digitalbazaar/http-client@4.4.0': + resolution: {integrity: sha512-ODhCGmElUPmR3IR+KZmBNkRAFyjJ01rxvk2E+/qQ2h2EGPJH5k6bz3N24ympGc5+i4YCGk/ipIpmkwc0+iSmRg==} + engines: {node: '>=18.0'} + '@drizzle-team/brocli@0.11.0': resolution: {integrity: sha512-hD3pekGiPg0WPCCGAZmusBBJsDqGUR66Y452YgQsZOnkdQ7ViEPKuyP4huUGEZQefp8g34RRodXYmJ2TbCH+tg==} @@ -785,10 +810,46 @@ packages: '@fastify/busboy@3.2.0': resolution: {integrity: sha512-m9FVDXU3GT2ITSe0UaMA5rU3QkfC/UXtCU8y0gSN/GugTqtVldOBWIB5V6V3sbmenVZUIpU6f+mPEO2+m5iTaA==} + '@fedify/fedify@2.4.0-pr.1020.43': + resolution: {integrity: sha512-uZvxgWklmBNZ3VQ6YJR7W+zZamwX8+2c1O4h/TZfjRm17ycVcFOHXNcdAFS2TjsVSQpo/axoGt25PI8Wb/+oQA==} + engines: {bun: '>=1.1.0', deno: '>=2.0.0', node: '>=22.0.0'} + + '@fedify/pglite@2.4.0-pr.1020.43': + resolution: {integrity: sha512-R3meizlecC1EXHwiNgwCJ6LwtpCvqee46eK7HrCoxA/ll9+/goCgBY0QIYJdhS/EqknrYF1koSKoO8LxposnvQ==} + peerDependencies: + '@electric-sql/pglite': ^0.5.8 + '@fedify/fedify': ^2.4.0-pr.1020.43+41cebe9c + + '@fedify/postgres@2.4.0-pr.1020.43': + resolution: {integrity: sha512-EGhZXyRFf5oS4fdDdAVUxn1MkAbSxcV99AXZQ8PkH/ejpFNmgcHmsQabefpTl9wqEyg8l8t1M1C0EPvQ5N6t1Q==} + peerDependencies: + '@fedify/fedify': ^2.4.0-pr.1020.43+41cebe9c + postgres: ^3.4.7 + '@fedify/uri-template@2.3.1': resolution: {integrity: sha512-322xch1WhasP/vjH4yDPA1RnYwI6tlG72aH7fCpdFge8IC845urKEMET9LzJ2G5HfeCKAXPAcaZZEx9FXnTNrg==} engines: {bun: '>=1.1.0', deno: '>=2.0.0', node: '>=22.0.0'} + '@fedify/uri-template@2.4.0-pr.1020.43': + resolution: {integrity: sha512-2JsU5q+pDNMuoD3ZcJLwubpZWgixwcy0H4yVY/bpz/vEEEWu+g22pa6r52rloWvn5ARJXAkMxcyHDcBlZojpBg==} + engines: {bun: '>=1.1.0', deno: '>=2.0.0', node: '>=22.0.0'} + + '@fedify/vocab-runtime@2.4.0-pr.1020.43': + resolution: {integrity: sha512-97ctqwEghTbyCU24VuZ14yS9Fs+Wa2dhA9orVV/6hZgalaz8LRUv2mV5wgR2BKddfKmJG5m5i7icV3BkVVwD/w==} + engines: {bun: '>=1.1.0', deno: '>=2.0.0', node: '>=22.0.0'} + + '@fedify/vocab-tools@2.4.0-pr.1020.43': + resolution: {integrity: sha512-QL6t5PT/1sTNt8zG/TtggM7XJPKy3/Mem8l+unrt/rtCTTFtK5IO+VhnJz+PXfd61HEeZY680IYt71GSntgU8g==} + engines: {bun: '>=1.1.0', deno: '>=2.0.0', node: '>=22.0.0'} + + '@fedify/vocab@2.4.0-pr.1020.43': + resolution: {integrity: sha512-fcvwaGXw5BJN28BIyDXQMSr4zS5F/ZWtTN3PFqVNtqGc5B5OdWLP+dwgq6CxAGW+vcC6t+8I84vSZbUML3Jq8A==} + engines: {bun: '>=1.1.0', deno: '>=2.0.0', node: '>=22.0.0'} + + '@fedify/webfinger@2.4.0-pr.1020.43': + resolution: {integrity: sha512-tEWdomxujooqZzxXgd1UKGkny+DtObN2eYAFLrQfmy2M1hxEmuyphNNB/ECOWDM9QScDE2P72lJItCDuyWDOiw==} + engines: {bun: '>=1.1.0', deno: '>=2.0.0', node: '>=22.0.0'} + '@floating-ui/core@1.8.0': resolution: {integrity: sha512-0CIZ5itps/8x7BG8dEIhs53BvCUH2PCoogtakwRTut+Arm58sJooJ0AuZhLw2HJYIR5cMLNPBSS728sPho2khQ==} @@ -919,6 +980,9 @@ packages: '@logtape/logtape@2.3.0-dev.840': resolution: {integrity: sha512-ozdG01RAKtEbDO9iDZDQOyUX9/4p1AcuHaxJtarDK22ngFU7vSxXyPGn9bf89pX358v2QXY+3+CqXWMlT6tiaQ==} + '@logtape/logtape@2.3.2': + resolution: {integrity: sha512-SfxHfSdDlTp6tLpYP5uezNitSCTQUB3FZ1MXDbgDBN9t9kvF+XL0rOGTIa/pJcDMy1o9FELaZ25E/Ypmsf5fNQ==} + '@logtape/testing-node@2.3.0-dev.840': resolution: {integrity: sha512-85UWXs1LCVKbL6nrcR7tPeh5/stQYwrBCkEOuUOJ0ufkTG7l6TP+eNWlCr0P0hrG4NzjjP9GhOpIamrLXbvMtg==} peerDependencies: @@ -929,12 +993,19 @@ packages: peerDependencies: '@logtape/logtape': ^2.3.0-dev.840+34e837bf + '@multiformats/base-x@4.0.1': + resolution: {integrity: sha512-eMk0b9ReBbV23xXU693TAIrLyeO5iTgBZGSJfpqriG8UkYvr/hC9u9pyMlAakDNHWmbhMZCDs6KQO0jzKD8OTw==} + '@napi-rs/wasm-runtime@1.1.6': resolution: {integrity: sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg==} peerDependencies: '@emnapi/core': ^1.7.1 '@emnapi/runtime': ^1.7.1 + '@noble/hashes@1.4.0': + resolution: {integrity: sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==} + engines: {node: '>= 16'} + '@nodelib/fs.scandir@2.1.5': resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} engines: {node: '>= 8'} @@ -947,6 +1018,56 @@ packages: resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} engines: {node: '>= 8'} + '@opentelemetry/api@1.9.1': + resolution: {integrity: sha512-gLyJlPHPZYdAk1JENA9LeHejZe1Ti77/pTeFm/nMXmQH/HFZlcS/O2XJB+L8fkbrNSqhdtlvjBVjxwUYanNH5Q==} + engines: {node: '>=8.0.0'} + + '@opentelemetry/core@2.11.0': + resolution: {integrity: sha512-7YP44XH0tV6+Mb54x2YGf84i7yi+31MBZlE8JwvozkxyTvXbSp10X7cI7YE49ChJ3shMJoBmCJF3+1QFBJctGA==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.10.0' + + '@opentelemetry/core@2.7.1': + resolution: {integrity: sha512-QAqIj32AtK6+pEVNG7EOVxHdE06RP+FM5qpiEJ4RtDcFIqKUZHYhl7/7UY5efhwmwNAg7j8QbJVBLxMerc0+gw==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.10.0' + + '@opentelemetry/resources@2.11.0': + resolution: {integrity: sha512-Ie7+8q8MDF4FAEQCKVMTx3ReUvxiIAgIiiW3c9JdmP8+HMcDy20puT+AHjexnExgnbvBxjQ9fjkFDWrikJ2jQA==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.3.0 <1.10.0' + + '@opentelemetry/resources@2.7.1': + resolution: {integrity: sha512-DeT6KKolmC4e/dRQvMQ/RwlnzhaqeiFOXY5ngoOPJ07GgVVKxZOg9EcrNZb5aTzUn+iCrJldAgOfQm1O/QfPAQ==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.3.0 <1.10.0' + + '@opentelemetry/sdk-metrics@2.7.1': + resolution: {integrity: sha512-MpDJdkiFDs3Pm1RHO3KByuZbuBdJEXEAkiC0+yJdsZGVCdf1RpHR6n+LHDcS7ffmfrt5kVCzJSCfm4z2C7v0uQ==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.9.0 <1.10.0' + + '@opentelemetry/sdk-trace-base@2.11.0': + resolution: {integrity: sha512-H19x/TX/LZdqiYOjM7fqtSxwlplC5pgelavqbQdHbhdq0q/AI/TGkM2dfGuuynTXmJPeF2HoZVoPDu+TGoW78A==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.3.0 <1.10.0' + + '@opentelemetry/sdk-trace@2.11.0': + resolution: {integrity: sha512-fFnTqGm8/G73GQVnxYi7LXa1ZVYEUvgL6XI1LpvV0bPC7WQ/ZGgKxCSl8FnlZBKto9JHHEFTO6s6CUpvvtwFrA==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.3.0 <1.10.0' + + '@opentelemetry/semantic-conventions@1.43.0': + resolution: {integrity: sha512-eSYWTm620tTk45EKSedaUL8MFYI8hW164hIXsgIHyxu3VobUB3fFCu5t0hQby6OoWRPsG1KkKUG2M5UadiLiVg==} + engines: {node: '>=14'} + '@optique/core@1.2.0': resolution: {integrity: sha512-h3gHGe8BCo5iVpOt4CcWCmTnPDYdyys0XqOCCc5ZPA2A2S+GmupMkqYNeI57+SildteuGEgBrGP+MlwRlWdzPA==} engines: {bun: '>=1.2.0', deno: '>=2.3.0', node: '>=20.0.0'} @@ -1606,6 +1727,9 @@ packages: '@solidjs/router': optional: true + '@standard-schema/spec@1.1.0': + resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} + '@swc/helpers@0.5.23': resolution: {integrity: sha512-5lSsMOTXURePglDfvuAQUqkGek9Hg2kksOYay2m0+XR++b2NWYL/4sWyuvVBIs8oKnJaxkdi9whaL/sqN13afw==} @@ -1627,6 +1751,9 @@ packages: '@types/braces@3.0.5': resolution: {integrity: sha512-SQFof9H+LXeWNz8wDe7oN5zu7ket0qwMu5vZubW4GCJ8Kkeh6nBWUz87+KTz/G3Kqsrp0j/W253XJb3KMEeg3w==} + '@types/emscripten@1.41.6': + resolution: {integrity: sha512-uN+9i8bFT5CUcZfyIEYDrSueACEyKGbUs5kC/72DGlZZoinh84sJfVV0i8UOJD1asdzkvLPBRrKs41kZ8MdEXg==} + '@types/estree@1.0.9': resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==} @@ -2016,6 +2143,10 @@ packages: asap@2.0.6: resolution: {integrity: sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==} + asn1js@3.0.10: + resolution: {integrity: sha512-S2s3aOytiKdFRdulw2qPE51MzjzVOisppcVv7jVFR+Kw0kxwvFrDcYA0h7Ndqbmj0HkMIXYWaoj7fli8kgx1eg==} + engines: {node: '>=12.0.0'} + babel-plugin-jsx-dom-expressions@0.40.7: resolution: {integrity: sha512-/O6JWUmjv03OI9lL2ry9bUjpD5S3PclM55RRJEyCdcFZ5W2SEA/59d+l2hNsk3gI6kiWRdRPdOtqZmsQzFN1pQ==} peerDependencies: @@ -2061,6 +2192,13 @@ packages: buffer-from@1.1.2: resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} + byte-encodings@1.0.11: + resolution: {integrity: sha512-+/xR2+ySc2yKGtud3DGkGSH1DNwHfRVK0KTnMhoeH36/KwG+tHQ4d9B3jxJFq7dW27YcfudkywaYJRPA2dmxzg==} + + bytestreamjs@2.0.1: + resolution: {integrity: sha512-U1Z/ob71V/bXfVABvNr/Kumf5VyeQRBEm6Txb0PQ6S7V5GpBM3w4Cbqz/xPDicR5tN0uvDifng8C+5qECeGwyQ==} + engines: {node: '>=6.0.0'} + cac@7.0.0: resolution: {integrity: sha512-tixWYgm5ZoOD+3g6UTea91eow5z6AAHaho3g0V9CNSNb45gM8SmflpAc+GRd1InC4AqN/07Unrgp56Y94N9hJQ==} engines: {node: '>=20.19.0'} @@ -2072,6 +2210,10 @@ packages: caniuse-lite@1.0.30001806: resolution: {integrity: sha512-72Cuvd95zbSYPKq6Fhg8eDJRlzgWDf7/mtoZv6Qe/DYNCEBdNxoA3+rZAU2ZhGCpZlns3EssFavaZomckT5Uuw==} + canonicalize@2.1.0: + resolution: {integrity: sha512-F705O3xrsUtgt98j7leetNhTWPe+5S72rlL5O4jA1pKqBVQ/dT1O1D6PFxmSXvc0SUOinWS57DKx0I3CHrXJHQ==} + hasBin: true + ccount@2.0.1: resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} @@ -2200,6 +2342,9 @@ packages: resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} engines: {node: '>=8'} + devalue@5.9.2: + resolution: {integrity: sha512-po4PAY5c53tw5XMocSnf8A/5OHhbbUftpr93aEN6BBoAdntUmK7vu7wOATqvt7cXO7m1Cl4gMVn6p7n6n4mj0w==} + devlop@1.1.0: resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} @@ -2380,6 +2525,9 @@ packages: error-stack-parser@2.1.4: resolution: {integrity: sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ==} + es-toolkit@1.46.1: + resolution: {integrity: sha512-5eNtXOs3tbfxXOj04tjjseeWkRWaoCjdEI+96DgwzZoe6c9juL49pXlzAFTI72aWC9Y8p7168g6XIKjh7k6pyQ==} + esbuild@0.25.12: resolution: {integrity: sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==} engines: {node: '>=18'} @@ -2709,6 +2857,9 @@ packages: json-buffer@3.0.1: resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + json-canon@1.0.1: + resolution: {integrity: sha512-PQcj4PFOTAQxE8PgoQ4KrM0DcKWZd7S3ELOON8rmysl9I8JuFMgxu1H9v+oZsTPjjkpeS3IHPwLjr7d+gKygnw==} + json-parse-even-better-errors@2.3.1: resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} @@ -2723,6 +2874,10 @@ packages: engines: {node: '>=6'} hasBin: true + jsonld@9.0.0: + resolution: {integrity: sha512-pjMIdkXfC1T2wrX9B9i2uXhGdyCmgec3qgMht+TDj+S0qX3bjWMQUfL7NeqEhuRTi8G5ESzmL9uGlST7nzSEWg==} + engines: {node: '>=18'} + kebab-case@1.0.2: resolution: {integrity: sha512-7n6wXq4gNgBELfDCpzKc+mRrZFs7D+wgfF5WRFLNAr4DA/qtr9Js8uOAVAfHhuLMfAcQ0pRKqbpjx+TcJVdE1Q==} @@ -2740,6 +2895,10 @@ packages: known-css-properties@0.30.0: resolution: {integrity: sha512-VSWXYUnsPu9+WYKkfmJyLKtIvaRJi1kXUqVmBACORXZQxT5oZDsoZ2vQP+bQFDnWtpI/4eq3MLoRMjI2fnLzTQ==} + ky@1.14.3: + resolution: {integrity: sha512-9zy9lkjac+TR1c2tG+mkNSVlyOpInnWdSMiue4F+kq8TwJSgv6o8jhLRg8Ho6SnZ9wOYUq/yozts9qQCfk7bIw==} + engines: {node: '>=18'} + levn@0.4.1: resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} engines: {node: '>= 0.8.0'} @@ -2842,6 +3001,10 @@ packages: lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + lru-cache@6.0.0: + resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} + engines: {node: '>=10'} + magic-string@0.30.21: resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} @@ -3053,6 +3216,10 @@ packages: resolution: {integrity: sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==} engines: {node: '>=12'} + pkijs@3.4.0: + resolution: {integrity: sha512-emEcLuomt2j03vxD54giVB4SxTjnsqkU692xZOZXHDVoYyypEm+b3jpiTcc+Cf+myooc+/Ly0z01jqeNHVgJGw==} + engines: {node: '>=16.0.0'} + postcss@8.5.25: resolution: {integrity: sha512-DTPx3RWSSnWyzLxQnlH0rJP+EW5ekl16ZU4/psbIhA0e53kJfdgaN5vKM+xP7yJtXVu+nfdVFmlgFDEKAe4Pyw==} engines: {node: ^10 || ^12 || >=14} @@ -3073,6 +3240,10 @@ packages: resolution: {integrity: sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==} engines: {node: '>=0.10.0'} + postgres@3.4.9: + resolution: {integrity: sha512-GD3qdB0x1z9xgFI6cdRD6xu2Sp2WCOEoe3mtnyB5Ee0XrrL5Pe+e4CCnJrRMnL1zYtRDZmQQVbvOttLnKDLnaw==} + engines: {node: '>=12'} + prelude-ls@1.2.1: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} @@ -3087,6 +3258,13 @@ packages: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} + pvtsutils@1.3.6: + resolution: {integrity: sha512-PLgQXQ6H2FWCaeRak8vvk1GW462lMxB5s3Jm673N82zI4vqtVUPuZdffdZbPDFRoU8kAhItWFtPCWiPpp4/EDg==} + + pvutils@1.2.0: + resolution: {integrity: sha512-BbubeCEyTuQjVMakvJQ/Sxbc93F2pwmbsxONT/ZRrwU7Ua38d8unYTwXpTVLAKJ4BDuH9IGztCjQcd/N/39Dvg==} + engines: {node: '>=16.0.0'} + quansync@1.0.0: resolution: {integrity: sha512-5xZacEEufv3HSTPQuchrvV6soaiACMFnq1H8wkVioctoH3TRha9Sz66lOxRwPK/qZj7HPiSveih9yAyh98gvqA==} @@ -3096,6 +3274,10 @@ packages: radix3@1.1.2: resolution: {integrity: sha512-b484I/7b8rDEdSDKckSSBA8knMpcdsXudlE/LNL639wFoHKwLbEkQFZHWEYwDC0wa0FKUcCY+GAF73Z7wxNVFA==} + rdf-canonize@5.0.0: + resolution: {integrity: sha512-g8OUrgMXAR9ys/ZuJVfBr05sPPoMA7nHIVs8VEvg9QwM5W4GR2qSFEEHjsyHF1eWlBaf8Ev40WNjQFQ+nJTO3w==} + engines: {node: '>=18'} + readdirp@5.0.0: resolution: {integrity: sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==} engines: {node: '>= 20.19.0'} @@ -3312,17 +3494,25 @@ packages: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} + structured-field-values@2.0.4: + resolution: {integrity: sha512-5zpJXYLPwW3WYUD/D58tQjIBs10l3Yx64jZfcKGs/RH79E2t9Xm/b9+ydwdMNVSksnsIY+HR/2IlQmgo0AcTAg==} + style-to-object@1.0.14: resolution: {integrity: sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==} - supports-color@10.2.2: - resolution: {integrity: sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==} - engines: {node: '>=18'} - supports-color@7.2.0: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} + temporal-polyfill@1.0.4: + resolution: {integrity: sha512-MLEU0qOD2uXlz24oINNtdLZQl8RgmMxSnRtKEGZGGetTJjlRwQJjk+VsJ4EREaUoiaTb8NJOcUiKtoAiWn9EBg==} + + temporal-spec@1.0.1: + resolution: {integrity: sha512-wxVoanmDeavXie1vu2JaQ3WIc3JZnWAOYFBsJyATaVsXsycKYUflGsyBmrRSnoCpZJpwPyr38VpgSUlQ8CbFxg==} + + temporal-utils@1.0.2: + resolution: {integrity: sha512-1B8Dl4KzrOvsNUlpoWGno2VLQlxroLjDgc5NBjVC9ax9ymdo+ezfyvhyjEMx+IVfhINr6CIG2gcnlP95iRrybQ==} + terracotta@1.1.1: resolution: {integrity: sha512-Sa6wnvkGMFmPq8N/wQb2aKkIW2eXH0LJvUOEk6QZLBEjqy+LsbzAOpDuqEfOmfA/hexssE6eQve2i1IIOeOh4g==} engines: {node: '>=10'} @@ -3424,6 +3614,10 @@ packages: undici-types@8.3.0: resolution: {integrity: sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==} + undici@6.28.0: + resolution: {integrity: sha512-LIY910g9TI13YS95lrMFrs8Rm/u/irgHeTWoKCoteeJ04CUJ92eEfj0rVn+7VKMPBpUPiUoBKfhNyLI23EE/KA==} + engines: {node: '>=18.17'} + unenv@2.0.0-rc.24: resolution: {integrity: sha512-i7qRCmY42zmCwnYlh9H2SvLEypEFGye5iRmEMKjcGi7zk9UquigRjFtTLz0TYqr0ZGLZhaMHl/foy1bZR+Cwlw==} @@ -3642,6 +3836,9 @@ packages: yallist@3.1.1: resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + yallist@4.0.0: + resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} + yaml@2.9.0: resolution: {integrity: sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==} engines: {node: '>= 14.6'} @@ -3673,20 +3870,20 @@ snapshots: '@babel/compat-data@7.29.7': {} - '@babel/core@7.29.7(supports-color@10.2.2)': + '@babel/core@7.29.7': dependencies: '@babel/code-frame': 7.29.7 '@babel/generator': 7.29.8 '@babel/helper-compilation-targets': 7.29.7 - '@babel/helper-module-transforms': 7.29.7(@babel/core@7.29.7(supports-color@10.2.2))(supports-color@10.2.2) + '@babel/helper-module-transforms': 7.29.7(@babel/core@7.29.7) '@babel/helpers': 7.29.7 '@babel/parser': 7.29.8 '@babel/template': 7.29.7 - '@babel/traverse': 7.29.8(supports-color@10.2.2) + '@babel/traverse': 7.29.8 '@babel/types': 7.29.8 '@jridgewell/remapping': 2.3.5 convert-source-map: 2.0.0 - debug: 4.4.3(supports-color@10.2.2) + debug: 4.4.3 gensync: 1.0.0-beta.2 json5: 2.2.3 semver: 6.3.1 @@ -3715,19 +3912,19 @@ snapshots: dependencies: '@babel/types': 7.29.8 - '@babel/helper-module-imports@7.29.7(supports-color@10.2.2)': + '@babel/helper-module-imports@7.29.7': dependencies: - '@babel/traverse': 7.29.8(supports-color@10.2.2) + '@babel/traverse': 7.29.8 '@babel/types': 7.29.8 transitivePeerDependencies: - supports-color - '@babel/helper-module-transforms@7.29.7(@babel/core@7.29.7(supports-color@10.2.2))(supports-color@10.2.2)': + '@babel/helper-module-transforms@7.29.7(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.29.7(supports-color@10.2.2) - '@babel/helper-module-imports': 7.29.7(supports-color@10.2.2) + '@babel/core': 7.29.7 + '@babel/helper-module-imports': 7.29.7 '@babel/helper-validator-identifier': 7.29.7 - '@babel/traverse': 7.29.8(supports-color@10.2.2) + '@babel/traverse': 7.29.8 transitivePeerDependencies: - supports-color @@ -3748,9 +3945,9 @@ snapshots: dependencies: '@babel/types': 7.29.8 - '@babel/plugin-syntax-jsx@7.29.7(@babel/core@7.29.7(supports-color@10.2.2))': + '@babel/plugin-syntax-jsx@7.29.7(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.29.7(supports-color@10.2.2) + '@babel/core': 7.29.7 '@babel/helper-plugin-utils': 7.29.7 '@babel/runtime@7.29.7': {} @@ -3761,7 +3958,7 @@ snapshots: '@babel/parser': 7.29.8 '@babel/types': 7.29.8 - '@babel/traverse@7.29.8(supports-color@10.2.2)': + '@babel/traverse@7.29.8': dependencies: '@babel/code-frame': 7.29.7 '@babel/generator': 7.29.8 @@ -3769,7 +3966,7 @@ snapshots: '@babel/parser': 7.29.8 '@babel/template': 7.29.7 '@babel/types': 7.29.8 - debug: 4.4.3(supports-color@10.2.2) + debug: 4.4.3 transitivePeerDependencies: - supports-color @@ -3778,6 +3975,8 @@ snapshots: '@babel/helper-string-parser': 7.29.7 '@babel/helper-validator-identifier': 7.29.7 + '@cfworker/json-schema@4.1.1': {} + '@clack/core@1.4.3': dependencies: fast-wrap-ansi: 0.2.2 @@ -3795,6 +3994,11 @@ snapshots: '@floating-ui/dom': 1.8.0 solid-js: 1.9.14 + '@digitalbazaar/http-client@4.4.0': + dependencies: + ky: 1.14.3 + undici: 6.28.0 + '@drizzle-team/brocli@0.11.0': {} '@electric-sql/pglite@0.5.3': {} @@ -4004,17 +4208,17 @@ snapshots: '@esbuild/win32-x64@0.28.1': optional: true - '@eslint-community/eslint-utils@4.9.1(eslint@9.39.4(jiti@2.7.0)(supports-color@10.2.2))': + '@eslint-community/eslint-utils@4.9.1(eslint@9.39.4(jiti@2.7.0))': dependencies: - eslint: 9.39.4(jiti@2.7.0)(supports-color@10.2.2) + eslint: 9.39.4(jiti@2.7.0) eslint-visitor-keys: 3.4.3 '@eslint-community/regexpp@4.12.2': {} - '@eslint/config-array@0.21.2(supports-color@10.2.2)': + '@eslint/config-array@0.21.2': dependencies: '@eslint/object-schema': 2.1.7 - debug: 4.4.3(supports-color@10.2.2) + debug: 4.4.3 minimatch: 3.1.5 transitivePeerDependencies: - supports-color @@ -4027,10 +4231,10 @@ snapshots: dependencies: '@types/json-schema': 7.0.15 - '@eslint/eslintrc@3.3.5(supports-color@10.2.2)': + '@eslint/eslintrc@3.3.5': dependencies: ajv: 6.15.0 - debug: 4.4.3(supports-color@10.2.2) + debug: 4.4.3 espree: 10.4.0 globals: 14.0.0 ignore: 5.3.2 @@ -4054,8 +4258,86 @@ snapshots: '@fastify/busboy@3.2.0': {} + '@fedify/fedify@2.4.0-pr.1020.43': + dependencies: + '@fedify/uri-template': 2.4.0-pr.1020.43 + '@fedify/vocab': 2.4.0-pr.1020.43 + '@fedify/vocab-runtime': 2.4.0-pr.1020.43 + '@fedify/webfinger': 2.4.0-pr.1020.43 + '@logtape/logtape': 2.3.2 + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.11.0(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-metrics': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-trace-base': 2.11.0(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.43.0 + '@standard-schema/spec': 1.1.0 + byte-encodings: 1.0.11 + devalue: 5.9.2 + es-toolkit: 1.46.1 + json-canon: 1.0.1 + jsonld: 9.0.0 + structured-field-values: 2.0.4 + temporal-polyfill: 1.0.4 + urlpattern-polyfill: 10.1.0 + + '@fedify/pglite@2.4.0-pr.1020.43(@electric-sql/pglite@0.5.3)(@fedify/fedify@2.4.0-pr.1020.43)': + dependencies: + '@electric-sql/pglite': 0.5.3 + '@fedify/fedify': 2.4.0-pr.1020.43 + '@logtape/logtape': 2.3.2 + '@types/emscripten': 1.41.6 + temporal-polyfill: 1.0.4 + + '@fedify/postgres@2.4.0-pr.1020.43(@fedify/fedify@2.4.0-pr.1020.43)(postgres@3.4.9)': + dependencies: + '@fedify/fedify': 2.4.0-pr.1020.43 + '@logtape/logtape': 2.3.2 + postgres: 3.4.9 + temporal-polyfill: 1.0.4 + '@fedify/uri-template@2.3.1': {} + '@fedify/uri-template@2.4.0-pr.1020.43': {} + + '@fedify/vocab-runtime@2.4.0-pr.1020.43': + dependencies: + '@js-temporal/polyfill': 0.5.1 + '@logtape/logtape': 2.3.2 + '@multiformats/base-x': 4.0.1 + '@opentelemetry/api': 1.9.1 + asn1js: 3.0.10 + byte-encodings: 1.0.11 + jsonld: 9.0.0 + pkijs: 3.4.0 + + '@fedify/vocab-tools@2.4.0-pr.1020.43': + dependencies: + '@cfworker/json-schema': 4.1.1 + byte-encodings: 1.0.11 + es-toolkit: 1.46.1 + yaml: 2.9.0 + + '@fedify/vocab@2.4.0-pr.1020.43': + dependencies: + '@fedify/vocab-runtime': 2.4.0-pr.1020.43 + '@fedify/vocab-tools': 2.4.0-pr.1020.43 + '@fedify/webfinger': 2.4.0-pr.1020.43 + '@logtape/logtape': 2.3.2 + '@multiformats/base-x': 4.0.1 + '@opentelemetry/api': 1.9.1 + asn1js: 3.0.10 + es-toolkit: 1.46.1 + jsonld: 9.0.0 + pkijs: 3.4.0 + temporal-polyfill: 1.0.4 + + '@fedify/webfinger@2.4.0-pr.1020.43': + dependencies: + '@fedify/vocab-runtime': 2.4.0-pr.1020.43 + '@logtape/logtape': 2.3.2 + '@opentelemetry/api': 1.9.1 + es-toolkit: 1.46.1 + '@floating-ui/core@1.8.0': dependencies: '@floating-ui/utils': 0.2.12 @@ -4212,6 +4494,8 @@ snapshots: '@logtape/logtape@2.3.0-dev.840': {} + '@logtape/logtape@2.3.2': {} + '@logtape/testing-node@2.3.0-dev.840(@logtape/logtape@2.3.0-dev.840)': dependencies: '@logtape/logtape': 2.3.0-dev.840 @@ -4221,6 +4505,8 @@ snapshots: dependencies: '@logtape/logtape': 2.3.0-dev.840 + '@multiformats/base-x@4.0.1': {} + '@napi-rs/wasm-runtime@1.1.6(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)': dependencies: '@emnapi/core': 1.10.0 @@ -4235,6 +4521,8 @@ snapshots: '@tybys/wasm-util': 0.10.3 optional: true + '@noble/hashes@1.4.0': {} + '@nodelib/fs.scandir@2.1.5': dependencies: '@nodelib/fs.stat': 2.0.5 @@ -4247,6 +4535,53 @@ snapshots: '@nodelib/fs.scandir': 2.1.5 fastq: 1.20.1 + '@opentelemetry/api@1.9.1': {} + + '@opentelemetry/core@2.11.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/semantic-conventions': 1.43.0 + + '@opentelemetry/core@2.7.1(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/semantic-conventions': 1.43.0 + + '@opentelemetry/resources@2.11.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.11.0(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.43.0 + + '@opentelemetry/resources@2.7.1(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.43.0 + + '@opentelemetry/sdk-metrics@2.7.1(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 2.7.1(@opentelemetry/api@1.9.1) + + '@opentelemetry/sdk-trace-base@2.11.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.11.0(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 2.11.0(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-trace': 2.11.0(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.43.0 + + '@opentelemetry/sdk-trace@2.11.0(@opentelemetry/api@1.9.1)': + dependencies: + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.11.0(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 2.11.0(@opentelemetry/api@1.9.1) + '@opentelemetry/semantic-conventions': 1.43.0 + + '@opentelemetry/semantic-conventions@1.43.0': {} + '@optique/core@1.2.0': {} '@optique/logtape@1.2.0(@logtape/logtape@2.3.0-dev.840)': @@ -4396,10 +4731,10 @@ snapshots: 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)': + '@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)(@opentelemetry/api@1.9.1)(@types/pg@8.20.0)(pg@8.21.0)(postgres@3.4.9))(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) + drizzle-orm: 1.0.0-beta.22(@electric-sql/pglite@0.5.3)(@opentelemetry/api@1.9.1)(@types/pg@8.20.0)(pg@8.21.0)(postgres@3.4.9) graphql: 16.14.2 '@pothos/plugin-errors@4.9.1(@pothos/core@4.13.0(graphql@16.14.2))(graphql@16.14.2)': @@ -4656,10 +4991,10 @@ snapshots: dependencies: solid-js: 1.9.14 - '@solidjs/start@2.0.0(@solidjs/router@1.0.0(solid-js@1.9.14))(crossws@0.4.10(srvx@0.11.16))(supports-color@10.2.2)(vite@8.2.0(@types/node@26.0.0)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.0))': + '@solidjs/start@2.0.0(@solidjs/router@1.0.0(solid-js@1.9.14))(crossws@0.4.10(srvx@0.11.16))(vite@8.2.0(@types/node@26.0.0)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.0))': dependencies: - '@babel/core': 7.29.7(supports-color@10.2.2) - '@babel/traverse': 7.29.8(supports-color@10.2.2) + '@babel/core': 7.29.7 + '@babel/traverse': 7.29.8 '@babel/types': 7.29.8 '@solidjs/meta': 0.29.4(solid-js@1.9.14) '@types/babel__traverse': 7.28.0 @@ -4683,7 +5018,7 @@ snapshots: srvx: 0.12.5 terracotta: 1.1.1(solid-js@1.9.14) vite: 8.2.0(@types/node@26.0.0)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.0) - vite-plugin-solid: 2.11.14(solid-js@1.9.14)(supports-color@10.2.2)(vite@8.2.0(@types/node@26.0.0)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.0)) + vite-plugin-solid: 2.11.14(solid-js@1.9.14)(vite@8.2.0(@types/node@26.0.0)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.0)) optionalDependencies: '@solidjs/router': 1.0.0(solid-js@1.9.14) transitivePeerDependencies: @@ -4691,6 +5026,8 @@ snapshots: - crossws - supports-color + '@standard-schema/spec@1.1.0': {} + '@swc/helpers@0.5.23': dependencies: tslib: 2.8.1 @@ -4723,6 +5060,8 @@ snapshots: '@types/braces@3.0.5': {} + '@types/emscripten@1.41.6': {} + '@types/estree@1.0.9': {} '@types/hast@3.0.5': @@ -4748,16 +5087,17 @@ snapshots: '@types/node': 26.0.0 pg-protocol: 1.14.0 pg-types: 2.2.0 + optional: true '@types/relay-runtime@20.1.1': {} '@types/unist@3.0.3': {} - '@typescript-eslint/project-service@8.62.1(supports-color@10.2.2)(typescript@7.0.2)': + '@typescript-eslint/project-service@8.62.1(typescript@7.0.2)': dependencies: '@typescript-eslint/tsconfig-utils': 8.62.1(typescript@7.0.2) '@typescript-eslint/types': 8.62.1 - debug: 4.4.3(supports-color@10.2.2) + debug: 4.4.3 typescript: 7.0.2 transitivePeerDependencies: - supports-color @@ -4773,13 +5113,13 @@ snapshots: '@typescript-eslint/types@8.62.1': {} - '@typescript-eslint/typescript-estree@8.62.1(supports-color@10.2.2)(typescript@7.0.2)': + '@typescript-eslint/typescript-estree@8.62.1(typescript@7.0.2)': dependencies: - '@typescript-eslint/project-service': 8.62.1(supports-color@10.2.2)(typescript@7.0.2) + '@typescript-eslint/project-service': 8.62.1(typescript@7.0.2) '@typescript-eslint/tsconfig-utils': 8.62.1(typescript@7.0.2) '@typescript-eslint/types': 8.62.1 '@typescript-eslint/visitor-keys': 8.62.1 - debug: 4.4.3(supports-color@10.2.2) + debug: 4.4.3 minimatch: 10.2.5 semver: 7.8.4 tinyglobby: 0.2.17 @@ -4788,13 +5128,13 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.62.1(eslint@9.39.4(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@7.0.2)': + '@typescript-eslint/utils@8.62.1(eslint@9.39.4(jiti@2.7.0))(typescript@7.0.2)': dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.4(jiti@2.7.0)(supports-color@10.2.2)) + '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.4(jiti@2.7.0)) '@typescript-eslint/scope-manager': 8.62.1 '@typescript-eslint/types': 8.62.1 - '@typescript-eslint/typescript-estree': 8.62.1(supports-color@10.2.2)(typescript@7.0.2) - eslint: 9.39.4(jiti@2.7.0)(supports-color@10.2.2) + '@typescript-eslint/typescript-estree': 8.62.1(typescript@7.0.2) + eslint: 9.39.4(jiti@2.7.0) typescript: 7.0.2 transitivePeerDependencies: - supports-color @@ -5009,19 +5349,25 @@ snapshots: asap@2.0.6: {} - babel-plugin-jsx-dom-expressions@0.40.7(@babel/core@7.29.7(supports-color@10.2.2)): + asn1js@3.0.10: + dependencies: + pvtsutils: 1.3.6 + pvutils: 1.2.0 + tslib: 2.8.1 + + babel-plugin-jsx-dom-expressions@0.40.7(@babel/core@7.29.7): dependencies: - '@babel/core': 7.29.7(supports-color@10.2.2) + '@babel/core': 7.29.7 '@babel/helper-module-imports': 7.18.6 - '@babel/plugin-syntax-jsx': 7.29.7(@babel/core@7.29.7(supports-color@10.2.2)) + '@babel/plugin-syntax-jsx': 7.29.7(@babel/core@7.29.7) '@babel/types': 7.29.8 html-entities: 2.3.3 parse5: 7.3.0 - babel-preset-solid@1.9.12(@babel/core@7.29.7(supports-color@10.2.2))(solid-js@1.9.14): + babel-preset-solid@1.9.12(@babel/core@7.29.7)(solid-js@1.9.14): dependencies: - '@babel/core': 7.29.7(supports-color@10.2.2) - babel-plugin-jsx-dom-expressions: 0.40.7(@babel/core@7.29.7(supports-color@10.2.2)) + '@babel/core': 7.29.7 + babel-plugin-jsx-dom-expressions: 0.40.7(@babel/core@7.29.7) optionalDependencies: solid-js: 1.9.14 @@ -5055,12 +5401,18 @@ snapshots: buffer-from@1.1.2: optional: true + byte-encodings@1.0.11: {} + + bytestreamjs@2.0.1: {} + cac@7.0.0: {} callsites@3.1.0: {} caniuse-lite@1.0.30001806: {} + canonicalize@2.1.0: {} + ccount@2.0.1: {} chalk@4.1.2: @@ -5138,11 +5490,9 @@ snapshots: optionalDependencies: '@electric-sql/pglite': 0.5.3 - debug@4.4.3(supports-color@10.2.2): + debug@4.4.3: dependencies: ms: 2.1.3 - optionalDependencies: - supports-color: 10.2.2 deep-is@0.1.4: {} @@ -5155,6 +5505,8 @@ snapshots: detect-libc@2.1.2: {} + devalue@5.9.2: {} + devlop@1.1.0: dependencies: dequal: 2.0.3 @@ -5170,11 +5522,13 @@ snapshots: 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): + drizzle-orm@1.0.0-beta.22(@electric-sql/pglite@0.5.3)(@opentelemetry/api@1.9.1)(@types/pg@8.20.0)(pg@8.21.0)(postgres@3.4.9): optionalDependencies: '@electric-sql/pglite': 0.5.3 + '@opentelemetry/api': 1.9.1 '@types/pg': 8.20.0 pg: 8.21.0 + postgres: 3.4.9 dts-resolver@3.0.0: {} @@ -5201,6 +5555,8 @@ snapshots: dependencies: stackframe: 1.3.4 + es-toolkit@1.46.1: {} + esbuild@0.25.12: optionalDependencies: '@esbuild/aix-ppc64': 0.25.12 @@ -5264,10 +5620,10 @@ snapshots: escape-string-regexp@4.0.0: {} - eslint-plugin-solid@0.14.5(eslint@9.39.4(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@7.0.2): + eslint-plugin-solid@0.14.5(eslint@9.39.4(jiti@2.7.0))(typescript@7.0.2): dependencies: - '@typescript-eslint/utils': 8.62.1(eslint@9.39.4(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@7.0.2) - eslint: 9.39.4(jiti@2.7.0)(supports-color@10.2.2) + '@typescript-eslint/utils': 8.62.1(eslint@9.39.4(jiti@2.7.0))(typescript@7.0.2) + eslint: 9.39.4(jiti@2.7.0) estraverse: 5.3.0 is-html: 2.0.0 kebab-case: 1.0.2 @@ -5288,14 +5644,14 @@ snapshots: eslint-visitor-keys@5.0.1: {} - eslint@9.39.4(jiti@2.7.0)(supports-color@10.2.2): + eslint@9.39.4(jiti@2.7.0): dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.4(jiti@2.7.0)(supports-color@10.2.2)) + '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.4(jiti@2.7.0)) '@eslint-community/regexpp': 4.12.2 - '@eslint/config-array': 0.21.2(supports-color@10.2.2) + '@eslint/config-array': 0.21.2 '@eslint/config-helpers': 0.4.2 '@eslint/core': 0.17.0 - '@eslint/eslintrc': 3.3.5(supports-color@10.2.2) + '@eslint/eslintrc': 3.3.5 '@eslint/js': 9.39.4 '@eslint/plugin-kit': 0.4.1 '@humanfs/node': 0.16.8 @@ -5305,7 +5661,7 @@ snapshots: ajv: 6.15.0 chalk: 4.1.2 cross-spawn: 7.0.6 - debug: 4.4.3(supports-color@10.2.2) + debug: 4.4.3 escape-string-regexp: 4.0.0 eslint-scope: 8.4.0 eslint-visitor-keys: 4.2.1 @@ -5544,11 +5900,11 @@ snapshots: dependencies: loose-envify: 1.4.0 - ioredis@5.11.1(supports-color@10.2.2): + ioredis@5.11.1: dependencies: '@ioredis/commands': 1.10.0 cluster-key-slot: 1.1.1 - debug: 4.4.3(supports-color@10.2.2) + debug: 4.4.3 denque: 2.1.0 redis-errors: 1.2.0 redis-parser: 3.0.0 @@ -5596,6 +5952,8 @@ snapshots: json-buffer@3.0.1: {} + json-canon@1.0.1: {} + json-parse-even-better-errors@2.3.1: {} json-schema-traverse@0.4.1: {} @@ -5604,6 +5962,13 @@ snapshots: json5@2.2.3: {} + jsonld@9.0.0: + dependencies: + '@digitalbazaar/http-client': 4.4.0 + canonicalize: 2.1.0 + lru-cache: 6.0.0 + rdf-canonize: 5.0.0 + kebab-case@1.0.2: {} keyv@4.5.4: @@ -5616,6 +5981,8 @@ snapshots: known-css-properties@0.30.0: {} + ky@1.14.3: {} + levn@0.4.1: dependencies: prelude-ls: 1.2.1 @@ -5691,6 +6058,10 @@ snapshots: dependencies: yallist: 3.1.1 + lru-cache@6.0.0: + dependencies: + yallist: 4.0.0 + magic-string@0.30.21: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 @@ -5751,7 +6122,7 @@ snapshots: nf3@0.3.23: {} - nitro@3.0.260610-beta(@electric-sql/pglite@0.5.3)(chokidar@5.0.0)(dotenv@17.4.2)(giget@3.3.0)(ioredis@5.11.1(supports-color@10.2.2))(jiti@2.7.0)(lru-cache@11.5.1)(rollup@4.62.2)(vite@8.2.0(@types/node@26.0.0)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.0)): + nitro@3.0.260610-beta(@electric-sql/pglite@0.5.3)(chokidar@5.0.0)(dotenv@17.4.2)(giget@3.3.0)(ioredis@5.11.1)(jiti@2.7.0)(lru-cache@11.5.1)(rollup@4.62.2)(vite@8.2.0(@types/node@26.0.0)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.0)): dependencies: consola: 3.4.2 crossws: 0.4.10(srvx@0.11.16) @@ -5766,7 +6137,7 @@ snapshots: rolldown: 1.2.0 srvx: 0.11.16 unenv: 2.0.0-rc.24 - unstorage: 2.0.0-alpha.7(chokidar@5.0.0)(db0@0.3.4(@electric-sql/pglite@0.5.3))(ioredis@5.11.1(supports-color@10.2.2))(lru-cache@11.5.1)(ofetch@2.0.0-alpha.3) + unstorage: 2.0.0-alpha.7(chokidar@5.0.0)(db0@0.3.4(@electric-sql/pglite@0.5.3))(ioredis@5.11.1)(lru-cache@11.5.1)(ofetch@2.0.0-alpha.3) optionalDependencies: dotenv: 17.4.2 giget: 3.3.0 @@ -5924,15 +6295,19 @@ snapshots: pg-cloudflare@1.4.0: optional: true - pg-connection-string@2.13.0: {} + pg-connection-string@2.13.0: + optional: true - pg-int8@1.0.1: {} + pg-int8@1.0.1: + optional: true pg-pool@3.14.0(pg@8.21.0): dependencies: pg: 8.21.0 + optional: true - pg-protocol@1.14.0: {} + pg-protocol@1.14.0: + optional: true pg-types@2.2.0: dependencies: @@ -5941,6 +6316,7 @@ snapshots: postgres-bytea: 1.0.1 postgres-date: 1.0.7 postgres-interval: 1.2.0 + optional: true pg@8.21.0: dependencies: @@ -5951,10 +6327,12 @@ snapshots: pgpass: 1.0.5 optionalDependencies: pg-cloudflare: 1.4.0 + optional: true pgpass@1.0.5: dependencies: split2: 4.2.0 + optional: true picocolors@1.1.1: {} @@ -5962,21 +6340,36 @@ snapshots: picomatch@4.0.5: {} + pkijs@3.4.0: + dependencies: + '@noble/hashes': 1.4.0 + asn1js: 3.0.10 + bytestreamjs: 2.0.1 + pvtsutils: 1.3.6 + pvutils: 1.2.0 + tslib: 2.8.1 + postcss@8.5.25: dependencies: nanoid: 3.3.16 picocolors: 1.1.1 source-map-js: 1.2.1 - postgres-array@2.0.0: {} + postgres-array@2.0.0: + optional: true - postgres-bytea@1.0.1: {} + postgres-bytea@1.0.1: + optional: true - postgres-date@1.0.7: {} + postgres-date@1.0.7: + optional: true postgres-interval@1.2.0: dependencies: xtend: 4.0.2 + optional: true + + postgres@3.4.9: {} prelude-ls@1.2.1: {} @@ -5988,12 +6381,22 @@ snapshots: punycode@2.3.1: {} + pvtsutils@1.3.6: + dependencies: + tslib: 2.8.1 + + pvutils@1.2.0: {} + quansync@1.0.0: {} queue-microtask@1.2.3: {} radix3@1.1.2: {} + rdf-canonize@5.0.0: + dependencies: + setimmediate: 1.0.5 + readdirp@5.0.0: optional: true @@ -6175,10 +6578,10 @@ snapshots: '@corvu/utils': 0.4.2(solid-js@1.9.14) solid-js: 1.9.14 - solid-refresh@0.6.3(solid-js@1.9.14)(supports-color@10.2.2): + solid-refresh@0.6.3(solid-js@1.9.14): dependencies: '@babel/generator': 7.29.8 - '@babel/helper-module-imports': 7.29.7(supports-color@10.2.2) + '@babel/helper-module-imports': 7.29.7 '@babel/types': 7.29.8 solid-js: 1.9.14 transitivePeerDependencies: @@ -6209,7 +6612,8 @@ snapshots: space-separated-tokens@2.0.2: {} - split2@4.2.0: {} + split2@4.2.0: + optional: true sprintf-js@1.0.3: {} @@ -6233,17 +6637,25 @@ snapshots: strip-json-comments@3.1.1: {} + structured-field-values@2.0.4: {} + style-to-object@1.0.14: dependencies: inline-style-parser: 0.2.7 - supports-color@10.2.2: - optional: true - supports-color@7.2.0: dependencies: has-flag: 4.0.0 + temporal-polyfill@1.0.4: + dependencies: + temporal-spec: 1.0.1 + temporal-utils: 1.0.2 + + temporal-spec@1.0.1: {} + + temporal-utils@1.0.2: {} + terracotta@1.1.1(solid-js@1.9.14): dependencies: solid-js: 1.9.14 @@ -6351,6 +6763,8 @@ snapshots: undici-types@8.3.0: {} + undici@6.28.0: {} + unenv@2.0.0-rc.24: dependencies: pathe: 2.0.3 @@ -6378,11 +6792,11 @@ snapshots: unist-util-is: 6.0.1 unist-util-visit-parents: 6.0.2 - unstorage@2.0.0-alpha.7(chokidar@5.0.0)(db0@0.3.4(@electric-sql/pglite@0.5.3))(ioredis@5.11.1(supports-color@10.2.2))(lru-cache@11.5.1)(ofetch@2.0.0-alpha.3): + unstorage@2.0.0-alpha.7(chokidar@5.0.0)(db0@0.3.4(@electric-sql/pglite@0.5.3))(ioredis@5.11.1)(lru-cache@11.5.1)(ofetch@2.0.0-alpha.3): optionalDependencies: chokidar: 5.0.0 db0: 0.3.4(@electric-sql/pglite@0.5.3) - ioredis: 5.11.1(supports-color@10.2.2) + ioredis: 5.11.1 lru-cache: 11.5.1 ofetch: 2.0.0-alpha.3 @@ -6430,14 +6844,14 @@ snapshots: transitivePeerDependencies: - typescript - vite-plugin-solid@2.11.14(solid-js@1.9.14)(supports-color@10.2.2)(vite@8.2.0(@types/node@26.0.0)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.0)): + vite-plugin-solid@2.11.14(solid-js@1.9.14)(vite@8.2.0(@types/node@26.0.0)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.0)): dependencies: - '@babel/core': 7.29.7(supports-color@10.2.2) + '@babel/core': 7.29.7 '@types/babel__core': 7.20.5 - babel-preset-solid: 1.9.12(@babel/core@7.29.7(supports-color@10.2.2))(solid-js@1.9.14) + babel-preset-solid: 1.9.12(@babel/core@7.29.7)(solid-js@1.9.14) merge-anything: 5.1.7 solid-js: 1.9.14 - solid-refresh: 0.6.3(solid-js@1.9.14)(supports-color@10.2.2) + solid-refresh: 0.6.3(solid-js@1.9.14) vite: 8.2.0(@types/node@26.0.0)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.0) vitefu: 1.1.3(vite@8.2.0(@types/node@26.0.0)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.48.0)(yaml@2.9.0)) transitivePeerDependencies: @@ -6477,10 +6891,13 @@ snapshots: xdg-basedir@5.1.0: {} - xtend@4.0.2: {} + xtend@4.0.2: + optional: true yallist@3.1.1: {} + yallist@4.0.0: {} + yaml@2.9.0: {} yocto-queue@0.1.0: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 0e6d23a..512da5c 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -8,6 +8,10 @@ allowBuilds: catalog: "@electric-sql/pglite": ^0.5.3 + "@fedify/fedify": 2.4.0-pr.1020.43+41cebe9c + "@fedify/postgres": 2.4.0-pr.1020.43+41cebe9c + "@fedify/pglite": 2.4.0-pr.1020.43+41cebe9c + "@fedify/vocab": 2.4.0-pr.1020.43+41cebe9c "@logtape/drizzle-orm": ^2.2.2 "@logtape/graphql-yoga": 2.3.0-dev.840 "@logtape/logtape": 2.3.0-dev.840 @@ -16,7 +20,6 @@ catalog: "@optique/logtape": ^1.2.0 "@optique/run": ^1.2.0 "@types/node": ^26.0.0 - "@types/pg": ^8.20.0 "@upyo/core": 0.6.0-dev.263+e633e1e6 "@upyo/logtape": 0.6.0-dev.263+e633e1e6 "@upyo/mock": 0.6.0-dev.263+e633e1e6 @@ -24,12 +27,21 @@ catalog: drizzle-orm: 1.0.0-beta.22 graphql: ^16.14.2 pg: ^8.21.0 + postgres: ^3.4.9 skills-npm: ^1.2.0 tsdown: ^0.22.14 typescript: ^7.0.2 uuid: ^14.0.1 minimumReleaseAgeExclude: + - "@fedify/fedify@2.4.0-pr.1020.43" + - "@fedify/postgres@2.4.0-pr.1020.43" + - "@fedify/uri-template@2.4.0-pr.1020.43" + - "@fedify/vocab-runtime@2.4.0-pr.1020.43" + - "@fedify/vocab-tools@2.4.0-pr.1020.43" + - "@fedify/vocab@2.4.0-pr.1020.43" + - "@fedify/webfinger@2.4.0-pr.1020.43" + - "@fedify/pglite@2.4.0-pr.1020.43" - "@logtape/graphql-yoga@2.3.0-dev.840" - "@logtape/logtape@2.3.0-dev.840" - "@logtape/testing-node@2.3.0-dev.840"