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"