@sdxc/activitypub 0.0.0-pre.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/LICENSE.md +21 -0
  2. package/README.md +711 -0
  3. package/dist/collections.d.ts +67 -0
  4. package/dist/collections.js +38 -0
  5. package/dist/conformance.d.ts +80 -0
  6. package/dist/conformance.js +313 -0
  7. package/dist/discovery.d.ts +46 -0
  8. package/dist/discovery.js +105 -0
  9. package/dist/errors.d.ts +115 -0
  10. package/dist/errors.js +124 -0
  11. package/dist/federation.d.ts +281 -0
  12. package/dist/federation.js +513 -0
  13. package/dist/fixtures/gotosocial.d.ts +114 -0
  14. package/dist/fixtures/gotosocial.js +115 -0
  15. package/dist/fixtures/index.d.ts +15 -0
  16. package/dist/fixtures/index.js +50 -0
  17. package/dist/fixtures/mastodon.d.ts +489 -0
  18. package/dist/fixtures/mastodon.js +334 -0
  19. package/dist/fixtures/misskey.d.ts +254 -0
  20. package/dist/fixtures/misskey.js +163 -0
  21. package/dist/fixtures/pixelfed.d.ts +118 -0
  22. package/dist/fixtures/pixelfed.js +126 -0
  23. package/dist/fixtures/shared.d.ts +14 -0
  24. package/dist/fixtures/shared.js +14 -0
  25. package/dist/fixtures/threads.d.ts +82 -0
  26. package/dist/fixtures/threads.js +88 -0
  27. package/dist/index.d.ts +33 -0
  28. package/dist/index.js +22 -0
  29. package/dist/keys.d.ts +100 -0
  30. package/dist/keys.js +147 -0
  31. package/dist/lib/availability.d.ts +37 -0
  32. package/dist/lib/availability.js +52 -0
  33. package/dist/lib/compact.d.ts +145 -0
  34. package/dist/lib/compact.js +514 -0
  35. package/dist/lib/constants.d.ts +35 -0
  36. package/dist/lib/constants.js +65 -0
  37. package/dist/lib/delivery-fan-out.d.ts +72 -0
  38. package/dist/lib/delivery-fan-out.js +202 -0
  39. package/dist/lib/delivery-post.d.ts +108 -0
  40. package/dist/lib/delivery-post.js +318 -0
  41. package/dist/lib/fetch.d.ts +64 -0
  42. package/dist/lib/fetch.js +103 -0
  43. package/dist/lib/inbox-error.d.ts +45 -0
  44. package/dist/lib/inbox-error.js +74 -0
  45. package/dist/lib/inbox-handle.d.ts +119 -0
  46. package/dist/lib/inbox-handle.js +587 -0
  47. package/dist/lib/inbox-receive.d.ts +117 -0
  48. package/dist/lib/inbox-receive.js +233 -0
  49. package/dist/lib/inbox-signature.d.ts +61 -0
  50. package/dist/lib/inbox-signature.js +156 -0
  51. package/dist/lib/inbox-summarize.d.ts +41 -0
  52. package/dist/lib/inbox-summarize.js +97 -0
  53. package/dist/lib/messages.d.ts +36 -0
  54. package/dist/lib/messages.js +35 -0
  55. package/dist/lib/parse.d.ts +42 -0
  56. package/dist/lib/parse.js +348 -0
  57. package/dist/lib/response.d.ts +45 -0
  58. package/dist/lib/response.js +59 -0
  59. package/dist/lib/stringify.d.ts +51 -0
  60. package/dist/lib/stringify.js +122 -0
  61. package/dist/lib/tombstone.d.ts +24 -0
  62. package/dist/lib/tombstone.js +15 -0
  63. package/dist/lib/types.d.ts +249 -0
  64. package/dist/lib/types.js +0 -0
  65. package/dist/memory.d.ts +77 -0
  66. package/dist/memory.js +146 -0
  67. package/dist/remote.d.ts +105 -0
  68. package/dist/remote.js +262 -0
  69. package/dist/store.d.ts +78 -0
  70. package/dist/store.js +8 -0
  71. package/dist/testing.d.ts +12 -0
  72. package/dist/testing.js +10 -0
  73. package/package.json +54 -0
@@ -0,0 +1,115 @@
1
+ /**
2
+ * The failures this package answers inside a `Result`: a base with a code and a retry
3
+ * verdict that a job consumer acts on, the parse failure every `parse*` returns, and the
4
+ * fetch failure discovery and the remote resolver return, and the failure a queued
5
+ * federation message ends with.
6
+ *
7
+ * @author [Sergio Xalambrí](https://sergiodxa.com)
8
+ * @copyright Sergio Xalambrí 2026
9
+ */
10
+ /**
11
+ * Every failure of this package. `code` is stable for a caller to branch on, and
12
+ * `retryable` tells a job consumer whether asking again later can change the outcome.
13
+ *
14
+ * @template Code - The codes a subclass can carry.
15
+ */
16
+ export declare class ActivityPubError<Code extends string = string> extends Error {
17
+ name: string;
18
+ /** Machine-readable cause, stable across releases of a subclass. */
19
+ readonly code: Code;
20
+ /** Whether a later attempt can succeed, so a job retries it rather than acknowledging. */
21
+ readonly retryable: boolean;
22
+ /**
23
+ * @param code - Why the operation failed.
24
+ * @param message - The explanation a log shows.
25
+ * @param options - Whether a retry can help, and the underlying error.
26
+ */
27
+ constructor(code: Code, message: string, options?: ActivityPubError.Options);
28
+ }
29
+ export declare namespace ActivityPubError {
30
+ /** How a failure is classified beyond its code. */
31
+ interface Options {
32
+ /** @default false */
33
+ retryable?: boolean;
34
+ cause?: unknown;
35
+ }
36
+ }
37
+ /**
38
+ * A document that is not the ActivityStreams shape asked for. Every issue found is
39
+ * listed with a JSON Pointer into the received document, and `message` summarizes the
40
+ * first; a resent copy of the same document fails the same way, so it never retries.
41
+ */
42
+ export declare class ActivityPubParseError extends ActivityPubError<"invalid-document"> {
43
+ name: string;
44
+ /** What was being read: `activity`, `actor`, `object` or `collection`. */
45
+ readonly kind: ActivityPubParseError.Kind;
46
+ readonly issues: ActivityPubParseError.Issue[];
47
+ /**
48
+ * @param kind - What was being read.
49
+ * @param issues - Every problem found, at least one.
50
+ */
51
+ constructor(kind: ActivityPubParseError.Kind, issues: ActivityPubParseError.Issue[]);
52
+ }
53
+ export declare namespace ActivityPubParseError {
54
+ /** The document reader that failed. */
55
+ type Kind = "activity" | "actor" | "object" | "collection";
56
+ /** One place a document breaks the shape asked for. */
57
+ interface Issue {
58
+ /** A JSON Pointer into the received document; `""` for the document as a whole. */
59
+ at: string;
60
+ message: string;
61
+ }
62
+ }
63
+ /**
64
+ * Why a remote ActivityPub or WebFinger document could not be used. `gone` means the
65
+ * server answered `410` or a `Tombstone`, so a caller forgets the resource; `timeout`,
66
+ * `network` and a `5xx` or `429` under `http` are the outcomes a retry can change.
67
+ */
68
+ export type ActivityPubFetchErrorCode = "gone" | "not-found" | "unauthorized" | "refused-url" | "too-large" | "timeout" | "network" | "invalid-document" | "id-mismatch" | "http";
69
+ /**
70
+ * A remote document that could not be fetched or trusted. `url` is the document that
71
+ * failed, and `status` the HTTP status when a response arrived at all.
72
+ */
73
+ export declare class ActivityPubFetchError extends ActivityPubError<ActivityPubFetchErrorCode> {
74
+ name: string;
75
+ readonly url: string;
76
+ /** The response status, or `null` when the request was refused, timed out or never connected. */
77
+ readonly status: number | null;
78
+ /**
79
+ * `retryable` defaults from the code and status: `timeout` and `network` always, and
80
+ * `http` for a `5xx` or `429`; every other outcome repeats on a retry.
81
+ *
82
+ * @param code - Why the document could not be used.
83
+ * @param url - The document's URL.
84
+ * @param message - The explanation a log shows.
85
+ * @param options - The status, an explicit retry verdict, and the underlying error.
86
+ */
87
+ constructor(code: ActivityPubFetchErrorCode, url: string, message: string, options?: ActivityPubFetchError.Options);
88
+ }
89
+ export declare namespace ActivityPubFetchError {
90
+ /** How a fetch failure is classified beyond its code. */
91
+ interface Options extends ActivityPubError.Options {
92
+ status?: number | null;
93
+ }
94
+ }
95
+ /**
96
+ * Why `Federation#process` could not finish a queued message. `retryable` and `delay` are
97
+ * all a job needs (retry after `delay` milliseconds, or acknowledge); `code` and `cause`
98
+ * keep the failure underneath, such as a `DeliveryError` or an `ActivityPubFetchError`.
99
+ */
100
+ export declare class FederationError extends ActivityPubError {
101
+ name: string;
102
+ /** Which kind of message failed. */
103
+ readonly kind: "inbox" | "fanOut" | "deliver";
104
+ /**
105
+ * Milliseconds to wait before the retry: the backoff step for this attempt, or longer
106
+ * when the inbox named a `Retry-After`. `0` when the failure is not retryable.
107
+ */
108
+ readonly delay: number;
109
+ /**
110
+ * @param kind - Which kind of message failed.
111
+ * @param cause - The failure underneath, whose code, message and verdict this one keeps.
112
+ * @param delay - The wait before a retry, in milliseconds.
113
+ */
114
+ constructor(kind: FederationError["kind"], cause: ActivityPubError, delay: number);
115
+ }
package/dist/errors.js ADDED
@@ -0,0 +1,124 @@
1
+ /**
2
+ * The failures this package answers inside a `Result`: a base with a code and a retry
3
+ * verdict that a job consumer acts on, the parse failure every `parse*` returns, and the
4
+ * fetch failure discovery and the remote resolver return, and the failure a queued
5
+ * federation message ends with.
6
+ *
7
+ * @author [Sergio Xalambrí](https://sergiodxa.com)
8
+ * @copyright Sergio Xalambrí 2026
9
+ */
10
+ /**
11
+ * Every failure of this package. `code` is stable for a caller to branch on, and
12
+ * `retryable` tells a job consumer whether asking again later can change the outcome.
13
+ *
14
+ * @template Code - The codes a subclass can carry.
15
+ */
16
+ export class ActivityPubError extends Error {
17
+ name = "ActivityPubError";
18
+ /** Machine-readable cause, stable across releases of a subclass. */
19
+ code;
20
+ /** Whether a later attempt can succeed, so a job retries it rather than acknowledging. */
21
+ retryable;
22
+ /**
23
+ * @param code - Why the operation failed.
24
+ * @param message - The explanation a log shows.
25
+ * @param options - Whether a retry can help, and the underlying error.
26
+ */
27
+ constructor(code, message, options = {}) {
28
+ super(message, options.cause === undefined ? undefined : { cause: options.cause });
29
+ this.code = code;
30
+ this.retryable = options.retryable ?? false;
31
+ }
32
+ }
33
+ /**
34
+ * A document that is not the ActivityStreams shape asked for. Every issue found is
35
+ * listed with a JSON Pointer into the received document, and `message` summarizes the
36
+ * first; a resent copy of the same document fails the same way, so it never retries.
37
+ */
38
+ export class ActivityPubParseError extends ActivityPubError {
39
+ name = "ActivityPubParseError";
40
+ /** What was being read: `activity`, `actor`, `object` or `collection`. */
41
+ kind;
42
+ issues;
43
+ /**
44
+ * @param kind - What was being read.
45
+ * @param issues - Every problem found, at least one.
46
+ */
47
+ constructor(kind, issues) {
48
+ let first = issues[0];
49
+ let where = first === undefined || first.at === "" ? "" : ` at ${first.at}`;
50
+ let more = issues.length > 1 ? ` (and ${issues.length - 1} more)` : "";
51
+ super("invalid-document", `Invalid ${kind}${where}: ${first?.message ?? "the document is invalid."}${more}`);
52
+ this.kind = kind;
53
+ this.issues = issues;
54
+ }
55
+ }
56
+ /** Codes a later attempt can clear regardless of status: the deadline and the transport. */
57
+ const RETRYABLE_FETCH_CODES = new Set(["timeout", "network"]);
58
+ /**
59
+ * A remote document that could not be fetched or trusted. `url` is the document that
60
+ * failed, and `status` the HTTP status when a response arrived at all.
61
+ */
62
+ export class ActivityPubFetchError extends ActivityPubError {
63
+ name = "ActivityPubFetchError";
64
+ url;
65
+ /** The response status, or `null` when the request was refused, timed out or never connected. */
66
+ status;
67
+ /**
68
+ * `retryable` defaults from the code and status: `timeout` and `network` always, and
69
+ * `http` for a `5xx` or `429`; every other outcome repeats on a retry.
70
+ *
71
+ * @param code - Why the document could not be used.
72
+ * @param url - The document's URL.
73
+ * @param message - The explanation a log shows.
74
+ * @param options - The status, an explicit retry verdict, and the underlying error.
75
+ */
76
+ constructor(code, url, message, options = {}) {
77
+ let status = options.status ?? null;
78
+ super(code, message, {
79
+ retryable: options.retryable ?? isRetryableFetch(code, status),
80
+ cause: options.cause,
81
+ });
82
+ this.url = url;
83
+ this.status = status;
84
+ }
85
+ }
86
+ /**
87
+ * Whether a fetch failure can clear on its own: a deadline, a dropped connection, a
88
+ * server error or a rate limit.
89
+ *
90
+ * @param code - The failure's code.
91
+ * @param status - The response status, when one arrived.
92
+ */
93
+ function isRetryableFetch(code, status) {
94
+ if (RETRYABLE_FETCH_CODES.has(code))
95
+ return true;
96
+ if (code !== "http" || status === null)
97
+ return false;
98
+ return status === 429 || status >= 500;
99
+ }
100
+ /**
101
+ * Why `Federation#process` could not finish a queued message. `retryable` and `delay` are
102
+ * all a job needs (retry after `delay` milliseconds, or acknowledge); `code` and `cause`
103
+ * keep the failure underneath, such as a `DeliveryError` or an `ActivityPubFetchError`.
104
+ */
105
+ export class FederationError extends ActivityPubError {
106
+ name = "FederationError";
107
+ /** Which kind of message failed. */
108
+ kind;
109
+ /**
110
+ * Milliseconds to wait before the retry: the backoff step for this attempt, or longer
111
+ * when the inbox named a `Retry-After`. `0` when the failure is not retryable.
112
+ */
113
+ delay;
114
+ /**
115
+ * @param kind - Which kind of message failed.
116
+ * @param cause - The failure underneath, whose code, message and verdict this one keeps.
117
+ * @param delay - The wait before a retry, in milliseconds.
118
+ */
119
+ constructor(kind, cause, delay) {
120
+ super(cause.code, cause.message, { retryable: cause.retryable, cause });
121
+ this.kind = kind;
122
+ this.delay = cause.retryable ? delay : 0;
123
+ }
124
+ }
@@ -0,0 +1,281 @@
1
+ /**
2
+ * The ActivityPub runtime of one local actor: its documents and inbox served from `fetch`,
3
+ * every queued step (inbox processing, fan-out, delivery) run by `process`, and the app's
4
+ * handlers registered with `on`, so the actor, keys, stores and queue are wired once.
5
+ *
6
+ * @author [Sergio Xalambrí](https://sergiodxa.com)
7
+ * @copyright Sergio Xalambrí 2026
8
+ */
9
+ import type { Backoff } from "@sdxc/backoff";
10
+ import type { Cache } from "@sdxc/cache";
11
+ import type { DurationInput } from "@sdxc/duration";
12
+ import type { Result } from "@sdxc/result";
13
+ import type { Schema } from "remix/data-schema";
14
+ import type { CollectionItem } from "./collections.js";
15
+ import type { ActivityPubFetchError } from "./errors.js";
16
+ import type { ActorKeys } from "./keys.js";
17
+ import type { Delivered } from "./lib/delivery-post.js";
18
+ import type { InboxError } from "./lib/inbox-error.js";
19
+ import type { DeleteInbound, FollowDecision, HandleOutcome, Inbound } from "./lib/inbox-handle.js";
20
+ import type { BlockedCheck, VerifiedFetch } from "./lib/inbox-receive.js";
21
+ import type { Summary } from "./lib/inbox-summarize.js";
22
+ import type { Message as QueueMessage } from "./lib/messages.js";
23
+ import type { RespondOptions as DocumentRespondOptions } from "./lib/response.js";
24
+ import type { ActivityPub } from "./lib/types.js";
25
+ import type { ResolverTtl } from "./remote.js";
26
+ import type { FollowerStore, KeyProvider, LocalObjects, SeenActivities } from "./store.js";
27
+ import { ActivityPubError, FederationError } from "./errors.js";
28
+ import { RemoteResolver } from "./remote.js";
29
+ /**
30
+ * Federates one local actor. Construct it wherever the stores are at hand (per request or
31
+ * per job is fine: it holds no state beyond its options and handlers), route the actor's
32
+ * URLs through `fetch`, and hand every queued message to `process`.
33
+ *
34
+ * @example
35
+ * let federation = new Federation({ actor, keys, stores, cache, userAgent, queue });
36
+ * federation.on("Create", async (ctx) => saveReply(ctx.summary()));
37
+ */
38
+ export declare class Federation {
39
+ #private;
40
+ /**
41
+ * The Standard Schema of every message federation enqueues (`inbox`, `fanOut` and
42
+ * `deliver`, told apart by `kind`), so one job declared with it carries all three.
43
+ *
44
+ * @example federation: job({ input: Federation.MESSAGE })
45
+ */
46
+ static readonly MESSAGE: Schema<unknown, Federation.Message>;
47
+ /**
48
+ * Fetches remote actors, keys and objects through the app's cache, signing every GET
49
+ * with the local actor's keys unless `resolver.signed` is `false`.
50
+ */
51
+ readonly resolver: RemoteResolver;
52
+ /** @param options - The local actor, its keys, the stores, the cache and the queue. */
53
+ constructor(options: Federation.Options);
54
+ /**
55
+ * Registers the app's handler for one or more activity types, called once the package
56
+ * has done the work the protocol requires (followers kept, Accept sent, ownership and
57
+ * relevance checked). A later registration for a type replaces the earlier one.
58
+ *
59
+ * A handler answers nothing or a `Result`; a failure, or an exception, retries the
60
+ * message, so a handler tolerates seeing an activity twice. A `Follow` handler may
61
+ * answer `"accept"`, `"reject"` or `"pending"`. `Like` handlers also receive Pleroma's
62
+ * `EmojiReact`.
63
+ *
64
+ * @param type - The activity type, or several sharing one handler.
65
+ * @param handler - Receives the verified activity and its `summary()`.
66
+ * @returns The federation, so registrations chain.
67
+ * @example federation.on(["Like", "Announce"], async (ctx) => saveResponse(ctx.summary()));
68
+ */
69
+ on<T extends Federation.HandledType>(type: T | readonly T[], handler: (ctx: Federation.ContextOf<T>) => Federation.HandlerReturn<Federation.AnswerOf<T>>): this;
70
+ /**
71
+ * Answers the local actor's own URLs, as its document names them: the actor (with
72
+ * `publicKey` from its keys), a `POST` to `inbox` or `endpoints.sharedInbox`, `outbox`,
73
+ * `followers` and `following`. Any other URL answers `null`, so the app routes it on.
74
+ *
75
+ * An inbox `POST` is verified here, inside the request, and answers `202` once its
76
+ * message is queued, or the status of the check it failed. With `authorizedFetch`, the
77
+ * collections require a signed GET; the actor document always answers unsigned.
78
+ *
79
+ * @param request - Any incoming request.
80
+ * @returns The response, or `null` for a URL that is not one of the actor's.
81
+ * @example let response = await federation.fetch(request); if (response) return response;
82
+ */
83
+ fetch(request: Request): Promise<Response | null>;
84
+ /**
85
+ * Answers `document` as ActivityStreams when the request prefers it over HTML, for a
86
+ * URL that serves both, such as a post's page. The response carries `Vary: Accept`, an
87
+ * `ETag` with `304` revalidation, a public 5-minute cache unless stated, and `410` for a
88
+ * `Tombstone`. With `authorizedFetch`, the request must carry a valid signature.
89
+ *
90
+ * @param request - The incoming request.
91
+ * @param document - The object, actor or collection the URL names.
92
+ * @param options - The cache policy, extra headers and status.
93
+ * @returns The response, or `null` when HTML wins and the app renders its page.
94
+ * @example let response = await federation.respond(request, article); if (response) return response;
95
+ */
96
+ respond(request: Request, document: ActivityPub.Draft<ActivityPub.Document>, options?: Federation.RespondOptions): Promise<Response | null>;
97
+ /**
98
+ * Verifies the signature on a GET (authorized fetch): the blocked host of the key, the
99
+ * signature's age, and the key, refetched once when it no longer verifies. The local
100
+ * actor's document passes unsigned, because remote servers read its key from there.
101
+ *
102
+ * @param request - The GET.
103
+ * @returns The signer, or an `InboxError` whose `toResponse()` refuses the request.
104
+ * @example let verified = await federation.verify(request); if (isFailure(verified)) return verified.error.toResponse();
105
+ */
106
+ verify(request: Request): Promise<Result<VerifiedFetch, InboxError>>;
107
+ /**
108
+ * Runs one queued message: an inbox activity through the protocol's checks and the
109
+ * app's handlers, a fan-out into one `deliver` message per distinct inbox, or one signed
110
+ * delivery. Accept and Reject replies are queued as deliveries, and an inbox answering
111
+ * `410` drops every follower reached through it.
112
+ *
113
+ * @param message - What the queue handed back, read through `Federation.MESSAGE`.
114
+ * @param options - The job's attempt, starting at 1, which spaces retries.
115
+ * @returns What happened, or a `FederationError` whose `retryable` and `delay` say
116
+ * whether and when to retry.
117
+ * @example
118
+ * let processed = await federation.process(ctx.input, { attempts: ctx.attempts });
119
+ * if (isFailure(processed) && processed.error.retryable) ctx.retry({ delay: processed.error.delay });
120
+ */
121
+ process(message: Federation.Message, options?: Federation.ProcessOptions): Promise<Result<Federation.Outcome, FederationError>>;
122
+ /**
123
+ * Queues the fan-out of an activity by the local actor to everyone it addresses: its
124
+ * followers when the followers collection is in `to` or `cc`, and every other addressed
125
+ * actor's inbox.
126
+ *
127
+ * @param activity - The activity, as a document or as JSON text already serialized.
128
+ * @returns `too-large` for an activity over 120 KB, which no delivery message can carry,
129
+ * or a retryable `enqueue` failure.
130
+ * @example await federation.publish({ id: `${post.url}#create`, type: "Create", actor: ACTOR_ID, object: article, to: [PUBLIC], cc: [FOLLOWERS_ID] });
131
+ */
132
+ publish(activity: ActivityPub.Draft<ActivityPub.Activity> | string): Promise<Result<void, ActivityPubError>>;
133
+ /**
134
+ * Resolves a handle to its actor the way Mastodon does: WebFinger on the handle's host,
135
+ * the `self` link typed as ActivityStreams, then the actor, whose own host must confirm
136
+ * the handle when it differs from the actor's canonical one.
137
+ *
138
+ * @param handle - `@user@host`, `user@host` or `acct:user@host`.
139
+ * @returns The actor; `not-found` when WebFinger names no actor, `id-mismatch` when the
140
+ * actor's host does not confirm the handle.
141
+ * @example let actor = await federation.lookup("@someone@mastodon.social");
142
+ */
143
+ lookup(handle: string): Promise<Result<ActivityPub.Actor, ActivityPubFetchError>>;
144
+ }
145
+ export declare namespace Federation {
146
+ /** What a `Federation` is built from. */
147
+ interface Options {
148
+ /**
149
+ * The local actor's document. Its `inbox`, `endpoints.sharedInbox`, `outbox`,
150
+ * `followers`, `following` and `id` are the URLs `fetch` answers, and Follows of it
151
+ * are what the inbox keeps followers for.
152
+ */
153
+ actor: ActivityPub.Actor;
154
+ /** The actor's keys, or a provider holding the keys of every actor the app hosts. */
155
+ keys: ActorKeys | KeyProvider;
156
+ stores: Stores;
157
+ /** Remote documents, accepted signature schemes and failing origins; a failing cache costs a fetch. */
158
+ cache: Cache;
159
+ /** Sent on every outbound request; some instances refuse requests without one. */
160
+ userAgent: string;
161
+ /** Where inbox, fan-out and delivery messages go; the app hands each back to `process`. */
162
+ queue: Queue;
163
+ /**
164
+ * Whether requests from a host are refused: it cannot deliver, follow, or receive.
165
+ * @default () => false
166
+ */
167
+ blocked?: BlockedCheck;
168
+ /** Pages of the outbox collection; without it the outbox is empty. */
169
+ outbox?: (cursor: string | null) => Promise<Result<OutboxPage, Error>>;
170
+ /** Requires a signed GET for the collections and for `respond` (secure mode). @default false */
171
+ authorizedFetch?: boolean;
172
+ inbox?: InboxOptions;
173
+ resolver?: ResolverOptions;
174
+ delivery?: DeliveryOptions;
175
+ collections?: CollectionOptions;
176
+ /**
177
+ * The wait before each retry of a failed message, by attempt. The default covers about
178
+ * 21 hours over five retries.
179
+ * @default DELIVERY_BACKOFF
180
+ */
181
+ backoff?: Backoff;
182
+ /** How long a processed activity id is remembered to skip redeliveries. @default "1 day" */
183
+ seenTtl?: DurationInput;
184
+ /** The clock signatures, failure windows and `receivedAt` are read from. @default () => new Date() */
185
+ now?: () => Date;
186
+ }
187
+ /** The app's storage, each implementing the interface the package calls. */
188
+ interface Stores {
189
+ followers: FollowerStore;
190
+ seen: SeenActivities;
191
+ objects: LocalObjects;
192
+ }
193
+ /**
194
+ * The app's queue. Each message is plain JSON under 128 KB, and a job declared with
195
+ * `Federation.MESSAGE` reads it back. A failure or an exception makes the step that
196
+ * enqueued it fail as retryable.
197
+ */
198
+ interface Queue {
199
+ enqueue(message: Message): Promise<Result<void, Error> | void>;
200
+ /** Writes a fan-out's deliveries in one call; without it they go one by one. */
201
+ enqueueMany?(messages: Message[]): Promise<Result<void, Error> | void>;
202
+ }
203
+ /** One page of the outbox, newest first. */
204
+ interface OutboxPage {
205
+ /** Activities, usually each post's `Create`, or their IRIs. */
206
+ items: CollectionItem[];
207
+ /** The cursor of the following page, `null` on the last one. */
208
+ next: string | null;
209
+ /** The outbox's size, published on the collection. */
210
+ totalItems?: number | null;
211
+ }
212
+ /** How the inbox reads a POST. */
213
+ interface InboxOptions {
214
+ /** Bodies past this answer `413`. @default 102400 */
215
+ maxBytes?: number;
216
+ /** How old a signature may be, plus five minutes of clock skew. @default "1 hour" */
217
+ maxAge?: DurationInput;
218
+ }
219
+ /** How remote documents are fetched. */
220
+ interface ResolverOptions {
221
+ /** Signs every GET with the actor's keys, as GoToSocial and secure-mode Mastodon require. @default true */
222
+ signed?: boolean;
223
+ ttl?: ResolverTtl;
224
+ /** @default "10 seconds" */
225
+ timeout?: DurationInput;
226
+ /** @default 1048576 */
227
+ maxBytes?: number;
228
+ }
229
+ /** How activities are delivered. */
230
+ interface DeliveryOptions {
231
+ /** The deadline of each POST. @default "15 seconds" */
232
+ timeout?: DurationInput;
233
+ /** Followers' inboxes read per store page, and the most deliveries per enqueue. @default 100 */
234
+ pageSize?: number;
235
+ }
236
+ /** How the actor's collections are served. */
237
+ interface CollectionOptions {
238
+ /** Followers per page of the followers collection. @default 50 */
239
+ pageSize?: number;
240
+ }
241
+ /** How `process` runs a message. */
242
+ interface ProcessOptions {
243
+ /** The job's attempt, starting at 1. Past the first, a redelivery check is skipped. @default 1 */
244
+ attempts?: number;
245
+ }
246
+ /** How `respond` answers a document. */
247
+ type RespondOptions = Pick<DocumentRespondOptions, "cache" | "headers" | "status">;
248
+ /** Every message federation enqueues, told apart by `kind`. */
249
+ type Message = QueueMessage;
250
+ /** The activity types the app can register a handler for. */
251
+ type HandledType = "Follow" | "Undo" | "Create" | "Update" | "Delete" | "Like" | "Announce" | "Accept" | "Reject" | "Move";
252
+ /** A verified activity as a handler receives it, with its response summary. */
253
+ interface Context<A extends ActivityPub.Activity = ActivityPub.Activity> extends Inbound<A> {
254
+ /**
255
+ * A reply, mention, like or boost of local content in the shape a Webmention is
256
+ * stored in, with remote HTML sanitized; `null` for any other activity.
257
+ */
258
+ summary(): Summary | null;
259
+ }
260
+ /** A verified `Delete`; `actor` is `null` when the deleted account's document is gone. */
261
+ interface DeleteContext extends DeleteInbound {
262
+ /** A deletion summarizes as nothing, so this is always `null`. */
263
+ summary(): Summary | null;
264
+ }
265
+ /** The context a handler for `T` receives. */
266
+ type ContextOf<T extends HandledType> = T extends "Delete" ? DeleteContext : Context;
267
+ /** What a handler for `T` answers: a Follow decision, or nothing. */
268
+ type AnswerOf<T extends HandledType> = T extends "Follow" ? FollowDecision | null : void;
269
+ /** A handler's return: the answer or a `Result` of it, sync or async. */
270
+ type HandlerReturn<T> = T | Result<T, Error> | Promise<T | Result<T, Error>>;
271
+ /** What `process` did with a message. */
272
+ type Outcome = ({
273
+ kind: "inbox";
274
+ } & HandleOutcome) | {
275
+ kind: "fanOut";
276
+ inboxes: number;
277
+ skipped: number;
278
+ } | ({
279
+ kind: "deliver";
280
+ } & Delivered);
281
+ }