Linting
This package is available since Fedify 2.0.0.
TIP
We highly recommend using the @fedify/lint package in your federated server app to catch common mistakes early and enforce best practices.
Fedify provides the @fedify/lint package, which includes lint rules specifically designed for Fedify applications. It supports Deno Lint, ESLint, and Oxlint, so you can use it regardless of your JavaScript/TypeScript runtime.
The plugin includes rules that check for:
- Proper actor ID configuration
- Required actor properties (inbox, outbox, followers, etc.)
- Correct URL patterns for actor collections
- Public key and assertion method requirements
- Collection filtering implementation
Installation
deno add jsr:@fedify/lintnpm add -D @fedify/lintpnpm add -D @fedify/lintyarn add -D @fedify/lintbun add -D @fedify/lintDeno Lint
Basic setup
Add the plugin to your deno.json configuration file:
{
"lint": {
"plugins": ["jsr:@fedify/lint"]
}
}By default, this enables all recommended rules.
Custom configuration
You can customize which rules to enable and their severity levels:
{
"lint": {
"plugins": ["jsr:@fedify/lint"],
"rules": {
"tags": ["recommended"],
"include": [
"@fedify/lint/actor-id-required",
"@fedify/lint/actor-id-mismatch"
],
"exclude": [
"@fedify/lint/actor-featured-property-required"
]
}
}
}Running Deno Lint
After setting up the configuration, run Deno's linter:
deno lintYou can also specify which files to lint:
deno lint federation.ts
deno lint src/federation/ESLint
Basic setup
Add the plugin to your ESLint configuration file (e.g., eslint.config.ts or eslint.config.js):
import fedifyLint from "@fedify/lint";
// If your `createFederation` code is in `federation.ts` or `federation/**.ts`
export default fedifyLint;Or specify your own federation files:
export default {
...fedifyLint,
files: ["my-own-federation.ts"],
};If you use other ESLint configurations:
export default [
// otherConfig,
fedifyLint,
];The default configuration applies recommended rules to files that match common federation-related patterns (e.g., federation.ts, federation/*.ts).
Custom configuration
You can customize which files to lint and which rules to enable:
import { plugin } from "@fedify/lint";
export default [{
files: ["src/federation/**/*.ts"], // Your federation code location
plugins: {
"@fedify/lint": plugin,
},
rules: {
"@fedify/lint/actor-id-required": "error",
"@fedify/lint/actor-id-mismatch": "error",
"@fedify/lint/actor-inbox-property-required": "warn",
// ... other rules
},
}];Using configurations
The plugin provides two preset configurations:
Recommended (default)
Enables critical rules as errors and optional rules as warnings:
import fedifyLint from "@fedify/lint";
export default fedifyLint;Strict
Enables all rules as errors:
import { plugin } from "@fedify/lint";
export default [{
files: ["**/*.ts"],
...plugin.configs.strict,
}];Running ESLint
Set up your ESLint configuration as shown above and add a script to package.json:
{
"scripts": {
"lint": "eslint ."
}
}After setting up the configuration, run ESLint on your codebase:
npm run lintpnpm lintyarn lintbun lintOr run the linter directly via command line:
npx eslint .pnpx eslint .yarn eslint .bunx eslint .Oxlint
Oxlint is a fast Rust-based linter that supports ESLint-compatible JS plugins. @fedify/lint exposes its rules through Oxlint's JS plugin API via the @fedify/lint/oxlint subpath export.
NOTE
Oxlint's JS plugin API is currently in alpha and not subject to semver.
Basic setup
Add the plugin to your .oxlintrc.json via the jsPlugins field, then enable the rules you want:
{
"$schema": "https://raw.githubusercontent.com/oxc-project/oxc/main/npm/oxlint/configuration_schema.json",
"jsPlugins": ["@fedify/lint/oxlint"],
"rules": {
"@fedify/lint/actor-id-required": "error",
"@fedify/lint/actor-id-mismatch": "error"
}
}Rule IDs are namespaced under @fedify/lint/, matching the ESLint preset.
Custom configuration
Each rule accepts "error", "warn", or "off". Enable any subset listed in the Rules section below:
{
"jsPlugins": ["@fedify/lint/oxlint"],
"rules": {
"@fedify/lint/actor-id-required": "error",
"@fedify/lint/actor-id-mismatch": "error",
"@fedify/lint/actor-inbox-property-required": "warn",
"@fedify/lint/actor-outbox-property-required": "warn",
"@fedify/lint/actor-followers-property-required": "warn",
"@fedify/lint/actor-public-key-required": "warn",
"@fedify/lint/actor-assertion-method-required": "warn",
"@fedify/lint/collection-filtering-not-implemented": "warn"
}
}Running Oxlint
Add a script to package.json:
{
"scripts": {
"lint": "oxlint ."
}
}Then run the linter:
npm run lintpnpm lintyarn lintbun lintOr invoke Oxlint directly:
npx oxlint .pnpx oxlint .yarn oxlint .bunx oxlint .Rules
actor-id-required
Ensures all actors have an id property in the actor dispatcher.
When this rule applies: The actor dispatcher returns a Person, Organization, Group, Application, or Service object without an id property.
Why it matters: Every ActivityPub actor must have a unique identifier (ID) to be discoverable and to receive activities from other servers.
// ❌ Bad: Missing id property
federation.setActorDispatcher("/users/{identifier}", (ctx, identifier) => {
return new Person({
name: "John Doe", // No id!
});
});
// ✅ Good: Include id property
federation.setActorDispatcher("/users/{identifier}", (ctx, identifier) => {
return new Person({
id: ctx.getActorUri(identifier),
name: "John Doe",
});
});actor-id-mismatch
Validates that actor IDs match the expected URI from Context.getActorUri().
When this rule applies: The id property is set to a value other than ctx.getActorUri(identifier), such as a hardcoded URL string, new URL(...), or a different context method.
Why it matters: Using the wrong URI for the actor ID can cause federation issues. Other servers won't be able to properly verify the actor's identity or send activities to it.
// ❌ Bad: Using hardcoded URL
federation.setActorDispatcher("/users/{identifier}", (ctx, identifier) => {
return new Person({
id: new URL(`https://example.com/users/${identifier}`),
name: "John Doe",
});
});
// ❌ Bad: Using wrong context method
federation.setActorDispatcher("/users/{identifier}", (ctx, identifier) => {
return new Person({
id: ctx.getFollowersUri(identifier), // Wrong method!
name: "John Doe",
});
});
// ✅ Good: Use ctx.getActorUri()
federation.setActorDispatcher("/users/{identifier}", (ctx, identifier) => {
return new Person({
id: ctx.getActorUri(identifier),
name: "John Doe",
});
});actor-public-key-required
Ensures actors have public keys for HTTP Signatures.
When this rule applies: The actor dispatcher is chained with setKeyPairsDispatcher(), but the actor object doesn't include a publicKey or publicKeys property.
Why it matters: HTTP Signatures are used to verify the authenticity of activities. Without a public key, other servers cannot verify that activities sent by your actor are legitimate.
// ❌ Bad: Missing publicKey when setKeyPairsDispatcher is configured
federation
.setActorDispatcher("/users/{identifier}", async (ctx, identifier) => {
return new Person({
id: ctx.getActorUri(identifier),
name: "John Doe",
// Missing publicKey!
});
})
.setKeyPairsDispatcher(async (ctx, identifier) => {
// Returns key pairs...
return [];
});
// ✅ Good: Include publicKey from key pairs dispatcher
federation
.setActorDispatcher("/users/{identifier}", async (ctx, identifier) => {
const keyPairs = await ctx.getActorKeyPairs(identifier);
return new Person({
id: ctx.getActorUri(identifier),
name: "John Doe",
publicKey: keyPairs[0].cryptographicKey,
});
})
.setKeyPairsDispatcher(async (ctx, identifier) => {
// Returns key pairs...
return [];
});actor-assertion-method-required
Validates that actors have assertion methods for Object Integrity Proofs.
When this rule applies: The actor dispatcher is chained with setKeyPairsDispatcher(), but the actor object doesn't include an assertionMethod property.
Why it matters: Object Integrity Proofs use assertion methods to cryptographically sign activities. This provides an additional layer of security beyond HTTP Signatures.
// ❌ Bad: Missing assertionMethod when setKeyPairsDispatcher is configured
federation
.setActorDispatcher("/users/{identifier}", async (ctx, identifier) => {
const keyPairs = await ctx.getActorKeyPairs(identifier);
return new Person({
id: ctx.getActorUri(identifier),
name: "John Doe",
publicKey: keyPairs[0].cryptographicKey,
// Missing assertionMethod!
});
})
.setKeyPairsDispatcher(async (ctx, identifier) => {
// Returns key pairs...
return [];
});
// ✅ Good: Include assertionMethod from key pairs dispatcher
federation
.setActorDispatcher("/users/{identifier}", async (ctx, identifier) => {
const keyPairs = await ctx.getActorKeyPairs(identifier);
return new Person({
id: ctx.getActorUri(identifier),
name: "John Doe",
publicKey: keyPairs[0].cryptographicKey,
assertionMethod: keyPairs[0].multikey,
});
})
.setKeyPairsDispatcher(async (ctx, identifier) => {
// Returns key pairs...
return [];
});actor-inbox-property-required
Ensures inbox is defined when setInboxListeners() is configured.
When this rule applies: You've called federation.setInboxListeners() to handle incoming activities, but the actor object doesn't include an inbox property.
Why it matters: The inbox URL tells other servers where to send activities to your actor. Without it, your actor cannot receive follow requests, mentions, or any other activities.
// ❌ Bad: Missing inbox when setInboxListeners is configured
federation.setActorDispatcher("/users/{identifier}", (ctx, identifier) => {
return new Person({
id: ctx.getActorUri(identifier),
name: "John Doe",
// Missing inbox!
});
});
federation.setInboxListeners("/users/{identifier}/inbox", "/inbox");
// ✅ Good: Include inbox property
federation.setActorDispatcher("/users/{identifier}", (ctx, identifier) => {
return new Person({
id: ctx.getActorUri(identifier),
name: "John Doe",
inbox: ctx.getInboxUri(identifier),
});
});
federation.setInboxListeners("/users/{identifier}/inbox", "/inbox");actor-inbox-property-mismatch
Validates that the inbox URI is set using ctx.getInboxUri(identifier).
When this rule applies: The inbox property is set to a value other than ctx.getInboxUri(identifier).
Why it matters: The inbox URI must match the path configured in setInboxListeners(). Using a different URI will cause incoming activities to fail.
// ❌ Bad: Using hardcoded URL
federation.setActorDispatcher("/users/{identifier}", (ctx, identifier) => {
return new Person({
id: ctx.getActorUri(identifier),
inbox: new URL(`https://example.com/inbox/${identifier}`), // Wrong!
});
});
// ✅ Good: Use ctx.getInboxUri()
federation.setActorDispatcher("/users/{identifier}", (ctx, identifier) => {
return new Person({
id: ctx.getActorUri(identifier),
inbox: ctx.getInboxUri(identifier),
});
});actor-outbox-property-required
Ensures outbox is defined when setOutboxDispatcher() is configured.
When this rule applies: You've called federation.setOutboxDispatcher() to serve the actor's outbox, but the actor object doesn't include an outbox property.
Why it matters: The outbox URL allows other servers and users to view the actor's published activities. It's part of the standard ActivityPub actor profile.
// ❌ Bad: Missing outbox when setOutboxDispatcher is configured
federation.setActorDispatcher("/users/{identifier}", (ctx, identifier) => {
return new Person({
id: ctx.getActorUri(identifier),
// Missing outbox!
});
});
federation.setOutboxDispatcher(
"/users/{identifier}/outbox",
(ctx, identifier) => ({ items: [] })
);
// ✅ Good: Include outbox property
federation.setActorDispatcher("/users/{identifier}", (ctx, identifier) => {
return new Person({
id: ctx.getActorUri(identifier),
outbox: ctx.getOutboxUri(identifier),
});
});actor-outbox-property-mismatch
Validates that the outbox URI is set using ctx.getOutboxUri(identifier).
When this rule applies: The outbox property is set to a value other than ctx.getOutboxUri(identifier).
Why it matters: The outbox URI must match the path configured in setOutboxDispatcher().
// ❌ Bad: Using wrong context method
federation.setActorDispatcher("/users/{identifier}", (ctx, identifier) => {
return new Person({
id: ctx.getActorUri(identifier),
outbox: ctx.getInboxUri(identifier), // Wrong method!
});
});
// ✅ Good: Use ctx.getOutboxUri()
federation.setActorDispatcher("/users/{identifier}", (ctx, identifier) => {
return new Person({
id: ctx.getActorUri(identifier),
outbox: ctx.getOutboxUri(identifier),
});
});outbox-listener-delivery-required
Warns when an outbox listener body does not deliver the posted activity with ctx.sendActivity() or ctx.forwardActivity().
When this rule applies: You've registered an outbox listener with setOutboxListeners(), but the listener body never calls either delivery method.
Why it matters: Fedify does not federate client-to-server outbox posts automatically. If your application intends to deliver a posted activity, the listener must choose an explicit delivery path.
// ❌ Bad: Listener stores the activity locally but never federates it
federation
.setOutboxListeners("/users/{identifier}/outbox")
.on(Activity, async (ctx, activity) => {
console.log(ctx.identifier, activity.id?.href);
});
// ✅ Good: Listener federates explicitly
federation
.setOutboxListeners("/users/{identifier}/outbox")
.on(Activity, async (ctx, activity) => {
await ctx.sendActivity(
{ identifier: ctx.identifier },
"followers",
activity,
);
});
// ✅ Good: Listener forwards the original posted payload explicitly
federation
.setOutboxListeners("/users/{identifier}/outbox")
.on(Activity, async (ctx) => {
await ctx.forwardActivity(
{ identifier: ctx.identifier },
"followers",
);
});actor-followers-property-required
Ensures followers is defined when setFollowersDispatcher() is configured.
When this rule applies: You've called federation.setFollowersDispatcher() to serve the actor's followers collection, but the actor object doesn't include a followers property.
Why it matters: The followers URL allows other servers to discover who follows this actor, which is important for activity delivery and social graph discovery.
// ❌ Bad: Missing followers when setFollowersDispatcher is configured
federation.setActorDispatcher("/users/{identifier}", (ctx, identifier) => {
return new Person({
id: ctx.getActorUri(identifier),
// Missing followers!
});
});
federation.setFollowersDispatcher(
"/users/{identifier}/followers",
(ctx, identifier) => ({ items: [] })
);
// ✅ Good: Include followers property
federation.setActorDispatcher("/users/{identifier}", (ctx, identifier) => {
return new Person({
id: ctx.getActorUri(identifier),
followers: ctx.getFollowersUri(identifier),
});
});actor-followers-property-mismatch
Validates that the followers URI is set using ctx.getFollowersUri(identifier).
When this rule applies: The followers property is set to a value other than ctx.getFollowersUri(identifier).
Why it matters: The followers URI must match the path configured in setFollowersDispatcher().
// ❌ Bad: Using wrong context method
federation.setActorDispatcher("/users/{identifier}", (ctx, identifier) => {
return new Person({
id: ctx.getActorUri(identifier),
followers: ctx.getFollowingUri(identifier), // Wrong method!
});
});
// ✅ Good: Use ctx.getFollowersUri()
federation.setActorDispatcher("/users/{identifier}", (ctx, identifier) => {
return new Person({
id: ctx.getActorUri(identifier),
followers: ctx.getFollowersUri(identifier),
});
});actor-following-property-required
Ensures following is defined when setFollowingDispatcher() is configured.
When this rule applies: You've called federation.setFollowingDispatcher() to serve the actor's following collection, but the actor object doesn't include a following property.
Why it matters: The following URL allows other servers to discover who this actor follows.
// ❌ Bad: Missing following when setFollowingDispatcher is configured
federation.setActorDispatcher("/users/{identifier}", (ctx, identifier) => {
return new Person({
id: ctx.getActorUri(identifier),
// Missing following!
});
});
federation.setFollowingDispatcher(
"/users/{identifier}/following",
(ctx, identifier) => ({ items: [] })
);
// ✅ Good: Include following property
federation.setActorDispatcher("/users/{identifier}", (ctx, identifier) => {
return new Person({
id: ctx.getActorUri(identifier),
following: ctx.getFollowingUri(identifier),
});
});actor-following-property-mismatch
Validates that the following URI is set using ctx.getFollowingUri(identifier).
When this rule applies: The following property is set to a value other than ctx.getFollowingUri(identifier).
Why it matters: The following URI must match the path configured in setFollowingDispatcher().
// ❌ Bad: Using wrong context method
federation.setActorDispatcher("/users/{identifier}", (ctx, identifier) => {
return new Person({
id: ctx.getActorUri(identifier),
following: ctx.getFollowersUri(identifier), // Wrong method!
});
});
// ✅ Good: Use ctx.getFollowingUri()
federation.setActorDispatcher("/users/{identifier}", (ctx, identifier) => {
return new Person({
id: ctx.getActorUri(identifier),
following: ctx.getFollowingUri(identifier),
});
});actor-liked-property-required
Ensures liked is defined when setLikedDispatcher() is configured.
When this rule applies: You've called federation.setLikedDispatcher() to serve the actor's liked collection, but the actor object doesn't include a liked property.
Why it matters: The liked URL allows other servers to discover what content this actor has liked.
// ❌ Bad: Missing liked when setLikedDispatcher is configured
federation.setActorDispatcher("/users/{identifier}", (ctx, identifier) => {
return new Person({
id: ctx.getActorUri(identifier),
// Missing liked!
});
});
federation.setLikedDispatcher(
"/users/{identifier}/liked",
(ctx, identifier) => ({ items: [] })
);
// ✅ Good: Include liked property
federation.setActorDispatcher("/users/{identifier}", (ctx, identifier) => {
return new Person({
id: ctx.getActorUri(identifier),
liked: ctx.getLikedUri(identifier),
});
});actor-liked-property-mismatch
Validates that the liked URI is set using ctx.getLikedUri(identifier).
When this rule applies: The liked property is set to a value other than ctx.getLikedUri(identifier).
Why it matters: The liked URI must match the path configured in setLikedDispatcher().
// ❌ Bad: Using wrong context method
federation.setActorDispatcher("/users/{identifier}", (ctx, identifier) => {
return new Person({
id: ctx.getActorUri(identifier),
liked: ctx.getFollowersUri(identifier), // Wrong method!
});
});
// ✅ Good: Use ctx.getLikedUri()
federation.setActorDispatcher("/users/{identifier}", (ctx, identifier) => {
return new Person({
id: ctx.getActorUri(identifier),
liked: ctx.getLikedUri(identifier),
});
});actor-featured-property-required
Ensures featured is defined when setFeaturedDispatcher() is configured.
When this rule applies: You've called federation.setFeaturedDispatcher() to serve the actor's featured/pinned posts collection, but the actor object doesn't include a featured property.
Why it matters: The featured URL allows other servers to discover the actor's pinned or highlighted content (commonly shown at the top of a profile).
// ❌ Bad: Missing featured when setFeaturedDispatcher is configured
federation.setActorDispatcher("/users/{identifier}", (ctx, identifier) => {
return new Person({
id: ctx.getActorUri(identifier),
// Missing featured!
});
});
federation.setFeaturedDispatcher(
"/users/{identifier}/featured",
(ctx, identifier) => ({ items: [] })
);
// ✅ Good: Include featured property
federation.setActorDispatcher("/users/{identifier}", (ctx, identifier) => {
return new Person({
id: ctx.getActorUri(identifier),
featured: ctx.getFeaturedUri(identifier),
});
});actor-featured-property-mismatch
Validates that the featured URI is set using ctx.getFeaturedUri(identifier).
When this rule applies: The featured property is set to a value other than ctx.getFeaturedUri(identifier).
Why it matters: The featured URI must match the path configured in setFeaturedDispatcher().
// ❌ Bad: Using wrong context method
federation.setActorDispatcher("/users/{identifier}", (ctx, identifier) => {
return new Person({
id: ctx.getActorUri(identifier),
featured: ctx.getFollowersUri(identifier), // Wrong method!
});
});
// ✅ Good: Use ctx.getFeaturedUri()
federation.setActorDispatcher("/users/{identifier}", (ctx, identifier) => {
return new Person({
id: ctx.getActorUri(identifier),
featured: ctx.getFeaturedUri(identifier),
});
});actor-featured-tags-property-required
Ensures featuredTags is defined when setFeaturedTagsDispatcher() is configured.
When this rule applies: You've called federation.setFeaturedTagsDispatcher() to serve the actor's featured hashtags collection, but the actor object doesn't include a featuredTags property.
Why it matters: The featuredTags URL allows other servers to discover the actor's featured hashtags (commonly used for profile discovery).
// ❌ Bad: Missing featuredTags when setFeaturedTagsDispatcher is configured
federation.setActorDispatcher("/users/{identifier}", (ctx, identifier) => {
return new Person({
id: ctx.getActorUri(identifier),
// Missing featuredTags!
});
});
federation.setFeaturedTagsDispatcher(
"/users/{identifier}/tags",
(ctx, identifier) => ({ items: [] })
);
// ✅ Good: Include featuredTags property
federation.setActorDispatcher("/users/{identifier}", (ctx, identifier) => {
return new Person({
id: ctx.getActorUri(identifier),
featuredTags: ctx.getFeaturedTagsUri(identifier),
});
});actor-featured-tags-property-mismatch
Validates that the featuredTags URI is set using ctx.getFeaturedTagsUri(identifier).
When this rule applies: The featuredTags property is set to a value other than ctx.getFeaturedTagsUri(identifier).
Why it matters: The featuredTags URI must match the path configured in setFeaturedTagsDispatcher().
// ❌ Bad: Using wrong context method
federation.setActorDispatcher("/users/{identifier}", (ctx, identifier) => {
return new Person({
id: ctx.getActorUri(identifier),
featuredTags: ctx.getFollowersUri(identifier), // Wrong method!
});
});
// ✅ Good: Use ctx.getFeaturedTagsUri()
federation.setActorDispatcher("/users/{identifier}", (ctx, identifier) => {
return new Person({
id: ctx.getActorUri(identifier),
featuredTags: ctx.getFeaturedTagsUri(identifier),
});
});actor-shared-inbox-property-required
Ensures endpoints.sharedInbox is defined when setInboxListeners() is configured with a shared inbox path.
When this rule applies: You've called federation.setInboxListeners() with a second parameter (shared inbox path), but the actor object doesn't include an endpoints: new Endpoints({ sharedInbox: ... }) property.
Why it matters: The shared inbox allows other servers to send activities to multiple actors on your server with a single request, improving federation efficiency.
// ❌ Bad: Missing sharedInbox when setInboxListeners has shared inbox path
federation.setActorDispatcher("/users/{identifier}", (ctx, identifier) => {
return new Person({
id: ctx.getActorUri(identifier),
inbox: ctx.getInboxUri(identifier),
// Missing endpoints.sharedInbox!
});
});
federation.setInboxListeners("/users/{identifier}/inbox", "/inbox");
// ✅ Good: Include endpoints.sharedInbox
federation.setActorDispatcher("/users/{identifier}", (ctx, identifier) => {
return new Person({
id: ctx.getActorUri(identifier),
inbox: ctx.getInboxUri(identifier),
endpoints: new Endpoints({
sharedInbox: ctx.getInboxUri(),
}),
});
});actor-shared-inbox-property-mismatch
Validates that endpoints.sharedInbox is set using ctx.getInboxUri() (without identifier).
When this rule applies: The endpoints.sharedInbox property is set to a value other than ctx.getInboxUri() (called without arguments for the shared inbox).
Why it matters: The shared inbox URI must match the shared inbox path configured in setInboxListeners().
// ❌ Bad: Using getInboxUri with identifier for shared inbox
federation.setActorDispatcher("/users/{identifier}", (ctx, identifier) => {
return new Person({
id: ctx.getActorUri(identifier),
inbox: ctx.getInboxUri(identifier),
endpoints: new Endpoints({
sharedInbox: ctx.getInboxUri(identifier), // Wrong! Should be no args
}),
});
});
// ✅ Good: Use ctx.getInboxUri() without arguments
federation.setActorDispatcher("/users/{identifier}", (ctx, identifier) => {
return new Person({
id: ctx.getActorUri(identifier),
inbox: ctx.getInboxUri(identifier),
endpoints: new Endpoints({
sharedInbox: ctx.getInboxUri(), // No identifier for shared inbox
}),
});
});collection-filtering-not-implemented
Warns when collection dispatchers don't implement filtering.
When this rule applies: The setFollowersDispatcher() callback function has fewer than 4 parameters (missing the filter parameter).
NOTE
Currently, this rule only checks setFollowersDispatcher(). Other collection dispatchers may be added in the future.
Why it matters: Collection filtering allows clients to request specific subsets of a collection, reducing response payload sizes and improving performance. Without filtering, large collections could cause performance issues.
For more information, see the Filtering by server section in the collections manual.
// ❌ Bad: Missing filter parameter
federation.setFollowersDispatcher(
"/users/{identifier}/followers",
async (ctx, identifier, cursor) => { // Only 3 parameters!
return { items: [] };
}
);
// ✅ Good: Include filter parameter (4th parameter)
federation.setFollowersDispatcher(
"/users/{identifier}/followers",
async (ctx, identifier, cursor, filter) => {
// Use filter to handle filtering requests
return { items: [] };
}
);Example
Here's an example of code that would trigger lint errors:
// ❌ Wrong: Using relative URL for actor ID
federation.setActorDispatcher(
"/{identifier}",
(_ctx, identifier) => {
return new Person({
id: new URL(`/${identifier}`), // ❌ Should use ctx.getActorUri()
name: "Example User",
});
},
);Corrected version:
// ✅ Correct: Using Context.getActorUri() for actor ID
federation.setActorDispatcher(
"/{identifier}",
(ctx, identifier) => {
return new Person({
id: ctx.getActorUri(identifier), // ✅ Correct
name: "Example User",
inbox: ctx.getInboxUri(identifier),
outbox: ctx.getOutboxUri(identifier),
followers: ctx.getFollowersUri(identifier),
// ... other required properties
});
},
);When you run the linter on the incorrect code, you'll see an error like:
error[fedify-lint/actor-id-mismatch]: Actor's `id` property must match
`ctx.getActorUri(identifier)`. Ensure you're using the correct context method.