Move federation logic into @drfed/federation - #112
Merged
Merged
Conversation
DrFed's ActivityPub dispatchers, inbox listeners, vocabulary
serialization, and activity delivery tracking lived in @drfed/graphql,
so serving ActivityPub required the GraphQL package even though it
never needs a schema. They now live in a new @drfed/federation package
that depends on @drfed/models but not on GraphQL, following the
structure of Hackers' Pub's federation package while keeping DrFed's
fresh-builder factory.
The new package exposes four subpaths:
- @drfed/federation: buildFederation(db) and the default
createFederation(db, options), unchanged in signature and behavior,
plus createInboundRecorder(), deliverActivity(), and the
TrackedFederation type. buildFederation() still creates a fresh
builder on every call and passes it to register functions in
actor.ts, object-dispatchers.ts, collection.ts, and inbox.ts, in the
same registration order as before. Nothing registers on a
module-level builder.
- @drfed/federation/activity-delivery: the delivery recording
functions that @drfed/graphql/activity-delivery used to re-export.
- @drfed/federation/object: objectSelection, activitySelection,
toObject(), toCreate(), and the StoredObject/StoredCreate input
types. GraphQL mutations keep using these to build stored JSON-LD
documents, so stored documents and ActivityPub responses share one
set of serialization rules.
- @drfed/federation/origin: the instance host rules, moved verbatim
from packages/graphql/src/origin.ts so that server routing and
federation lookups cannot disagree about instance hosts.
The activity delivery runtime from #101 had to move along with the
dispatchers: createFederation() wraps the KV store, tracer, meter, and
queues with it, the inbox listener calls markHandled(), and the builder
registers its permanent-failure handler. Its nine GraphQL-free modules
moved unchanged into src/activity-delivery/; only the Pothos types in
packages/graphql/src/activity-delivery/entry.ts stay behind.
The Public addressing and served-activity predicates moved to an
internal visibility.ts shared by the Create dispatcher, outbox pages,
and outbox counter. Registration helpers stay internal and are not
exported.
@drfed/graphql drops its ./federation and ./origin subpaths, and
./activity-delivery no longer re-exports runtime functions, without
compatibility shims, since no version has been released and every
consumer is in this repository. Its README lists the replacement
imports. @drfed/graphql and @drfed/drfed import from
@drfed/federation, and mise.toml's build:server task builds the new
package. No tsconfig path alias is added for @drfed/federation:
mapping its root to source while its subpaths resolve to dist/ would
give TrackedFederation two incompatible unique-symbol brands.
The intended observable changes are the logger categories, from
["drfed", "graphql", "federation"] to ["drfed", "federation"] and from
["drfed", "graphql", "activity-delivery"] to
["drfed", "federation", "activity-delivery"].
Tests that need only a database and a federation moved to the new
package with their own temporary-database harness, a copy of the
database-only seed fixtures, and a copy of the remote inbox helper;
this includes the outbound and queue delivery tests. The
GraphQL-dependent ones remain in @drfed/graphql, including
activitypub-integration.test.ts and the inbound delivery tests; the
test that checked collection items over both ActivityPub and GraphQL
was split between the two. A new regression test builds federations
from two databases holding the same actor identifier and checks that
each one serves its own database's actor, which a shared builder would
break. The new package adds DOM to its TypeScript lib so the moved
tests keep reading untyped JSON response bodies, as they did under
@drfed/graphql, which gets DOM typings transitively through
graphql-yoga.
Verified with a clean `mise run build`, `mise run check`, and
`mise run test`; with `mise run dev`; and by installing the packed
tarballs of all four server packages outside the repository, starting
the installed drfed-server on an empty PGlite directory, seeding an
instance, checking GraphQL, actor, object, Create, outbox, and
WebFinger responses, checking that an unsigned inbox POST is recorded
as an unverified inbound delivery, and type-checking a strict
TypeScript consumer of every @drfed/federation subpath.
Closes #102
AI provenance: Claude Code (Claude Opus 5.5) read the issue and the
Hackers' Pub reference code, drafted the design and refactoring plan,
implemented the move, resolved the rebase onto the activity delivery
work by moving its runtime into the new package, wrote the new tests
and documentation, and ran the build, check, test, dev-server, and
packed-installation verification. Codex (GPT-6 Astra) reviewed the
plan over three rounds; its feedback separated the public serializer
subpath from internal dispatcher registration and shared predicates,
added the cross-database isolation test, and extended the
packed-package verification. Codex (GPT-6 Astra) and Claude Code
(Claude Fable 5.1) then reviewed the change before and after the
rebase and reported no actionable findings, so neither review changed
the code.
Assisted-by: Claude Code:claude-opus-5-5
Assisted-by: Codex:gpt-6-astra
dodok8
approved these changes
Oct 6, 2026
sij411
approved these changes
Oct 6, 2026
2chanhaeng
approved these changes
Oct 6, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #102.
The ActivityPub dispatchers, vocabulary serializers, instance host rules, and activity delivery tracking lived in
@drfed/graphql, although none of them need a GraphQL schema. This PR moves them into a new@drfed/federationpackage that depends on@drfed/modelsand not on GraphQL.@drfed/drfedstill builds the federation at startup and routes requests between it and GraphQL.How it is split
Hackers' Pub registers its handlers by side effect on a module-level builder. DrFed's
buildFederation(db)instead closes over a database and returns a fresh builder on every call, so each group of handlers became an internal registration function that receives the builder. src/index.ts creates the builder and calls them in the original order. A new test builds federations from two databases holding the same actor ID and checks that each serves its own row.The package has four public entry points:
buildFederation(),createFederation(),createInboundRecorder(), anddeliverActivity().@drfed/federation/objectexports the query selections plustoObject()andtoCreate(), so GraphQL mutations and ActivityPub responses use the same serializers.@drfed/federation/originis packages/graphql/src/origin.ts moved unchanged, so server routing and federation lookups keep one definition of an instance host.@drfed/federation/activity-deliveryexports the delivery recording functions that packages/graphql/src/activity-delivery/entry.ts used to re-export.The activity delivery code from #101 had to move too, since
createFederation()wraps the KV store, tracer, meter, and queues with it and the inbox listener callsmarkHandled(). Only the Pothos types in entry.ts stay in@drfed/graphql.@drfed/graphql/federationand@drfed/graphql/originare removed, and@drfed/graphql/activity-deliveryno longer re-exports runtime functions. There are no compatibility re-exports, since nothing has been released and every consumer is in this repository. packages/graphql/README.md maps the old imports to the new ones.Things worth a look
Tests that need only a database moved to the new package with their own harness, including outbound.test.ts and queue.test.ts. The database-only seed fixtures and the remote.test.ts inbox helper are duplicated so that each package's tests stay self-contained. Tests that go through GraphQL, such as inbound.test.ts, remain in
@drfed/graphql.The new package adds
DOMto its TypeScriptlib.@drfed/graphqlgets DOM typings transitively through graphql-yoga, and the moved tests read untyped JSON bodies fromResponse.json(). Adding the lib avoided type assertions throughout those checks.Log categories move from
["drfed", "graphql", "federation"]and["drfed", "graphql", "activity-delivery"]to["drfed", "federation"]and["drfed", "federation", "activity-delivery"].Verification
Besides
mise run build,mise run check, andmise run testfrom a clean tree, the four server packages were packed and installed outside the repository. The installeddrfed-servermigrated an empty PGlite directory and, after one instance was seeded, served GraphQL and the actor, object, Create, outbox, and WebFinger responses, and recorded an unsigned inbox POST as an unverified inbound delivery. A strict TypeScript consumer of every entry point type-checks without repository path aliases.AI assistance
Claude Code (Claude Opus 5.5) drafted the plan, wrote the code, tests, and docs, resolved the rebase onto the activity delivery work, and ran the verification above. Codex (GPT-6 Astra) reviewed the plan over three rounds; its feedback led to the public/internal split and the cross-database test. Codex and Claude Code (Claude Fable 5.1) then reviewed the diff before and after the rebase and found nothing actionable.