@fedify/adonisjs 2.4.0-dev.0

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 (82) hide show
  1. package/LICENSE +20 -0
  2. package/README.md +420 -0
  3. package/dist/configure.cjs +108 -0
  4. package/dist/configure.cjs.map +1 -0
  5. package/dist/configure.d.cts +14 -0
  6. package/dist/configure.d.ts +14 -0
  7. package/dist/configure.js +107 -0
  8. package/dist/configure.js.map +1 -0
  9. package/dist/index.cjs +19 -0
  10. package/dist/index.d.cts +8 -0
  11. package/dist/index.d.ts +8 -0
  12. package/dist/index.js +8 -0
  13. package/dist/package.cjs +121 -0
  14. package/dist/package.cjs.map +1 -0
  15. package/dist/package.js +115 -0
  16. package/dist/package.js.map +1 -0
  17. package/dist/providers/fedify_provider.cjs +270 -0
  18. package/dist/providers/fedify_provider.cjs.map +1 -0
  19. package/dist/providers/fedify_provider.d.cts +54 -0
  20. package/dist/providers/fedify_provider.d.ts +55 -0
  21. package/dist/providers/fedify_provider.js +269 -0
  22. package/dist/providers/fedify_provider.js.map +1 -0
  23. package/dist/services/builder.cjs +9 -0
  24. package/dist/services/builder.cjs.map +1 -0
  25. package/dist/services/builder.d.cts +7 -0
  26. package/dist/services/builder.d.ts +8 -0
  27. package/dist/services/builder.js +8 -0
  28. package/dist/services/builder.js.map +1 -0
  29. package/dist/src/builder.cjs +55 -0
  30. package/dist/src/builder.cjs.map +1 -0
  31. package/dist/src/builder.d.cts +14 -0
  32. package/dist/src/builder.d.ts +14 -0
  33. package/dist/src/builder.js +54 -0
  34. package/dist/src/builder.js.map +1 -0
  35. package/dist/src/define_config.cjs +114 -0
  36. package/dist/src/define_config.cjs.map +1 -0
  37. package/dist/src/define_config.d.cts +30 -0
  38. package/dist/src/define_config.d.ts +30 -0
  39. package/dist/src/define_config.js +113 -0
  40. package/dist/src/define_config.js.map +1 -0
  41. package/dist/src/errors.cjs +77 -0
  42. package/dist/src/errors.cjs.map +1 -0
  43. package/dist/src/errors.d.cts +56 -0
  44. package/dist/src/errors.d.ts +56 -0
  45. package/dist/src/errors.js +73 -0
  46. package/dist/src/errors.js.map +1 -0
  47. package/dist/src/fedify_middleware.cjs +507 -0
  48. package/dist/src/fedify_middleware.cjs.map +1 -0
  49. package/dist/src/fedify_middleware.d.cts +85 -0
  50. package/dist/src/fedify_middleware.d.ts +85 -0
  51. package/dist/src/fedify_middleware.js +505 -0
  52. package/dist/src/fedify_middleware.js.map +1 -0
  53. package/dist/src/http_context.cjs +79 -0
  54. package/dist/src/http_context.cjs.map +1 -0
  55. package/dist/src/http_context.d.cts +31 -0
  56. package/dist/src/http_context.d.ts +31 -0
  57. package/dist/src/http_context.js +76 -0
  58. package/dist/src/http_context.js.map +1 -0
  59. package/dist/src/logging.cjs +106 -0
  60. package/dist/src/logging.cjs.map +1 -0
  61. package/dist/src/logging.d.cts +45 -0
  62. package/dist/src/logging.d.ts +45 -0
  63. package/dist/src/logging.js +106 -0
  64. package/dist/src/logging.js.map +1 -0
  65. package/dist/src/types.cjs +1 -0
  66. package/dist/src/types.d.cts +269 -0
  67. package/dist/src/types.d.ts +269 -0
  68. package/dist/src/types.js +1 -0
  69. package/dist/src/unusable_authority.cjs +79 -0
  70. package/dist/src/unusable_authority.cjs.map +1 -0
  71. package/dist/src/unusable_authority.js +76 -0
  72. package/dist/src/unusable_authority.js.map +1 -0
  73. package/dist/stubs/config/fedify.stub +105 -0
  74. package/dist/stubs/federation/main.stub +64 -0
  75. package/dist/stubs/main.cjs +16 -0
  76. package/dist/stubs/main.cjs.map +1 -0
  77. package/dist/stubs/main.d.cts +11 -0
  78. package/dist/stubs/main.d.ts +11 -0
  79. package/dist/stubs/main.js +15 -0
  80. package/dist/stubs/main.js.map +1 -0
  81. package/dist/stubs/start/federation.stub +20 -0
  82. package/package.json +124 -0
package/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ MIT License
2
+
3
+ Copyright 2024–2026 Hong Minhee
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
6
+ this software and associated documentation files (the "Software"), to deal in
7
+ the Software without restriction, including without limitation the rights to
8
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9
+ the Software, and to permit persons to whom the Software is furnished to do so,
10
+ subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,420 @@
1
+ <!-- deno-fmt-ignore-file -->
2
+
3
+ @fedify/adonisjs: Integrate Fedify with AdonisJS
4
+ ================================================
5
+
6
+ [![npm][npm badge]][npm]
7
+ [![Matrix][Matrix badge]][Matrix]
8
+ [![@fedify@hackers.pub][@fedify@hackers.pub badge]][@fedify@hackers.pub]
9
+
10
+ *This package is available since Fedify 2.4.0.*
11
+
12
+ [Fedify] is a TypeScript library for building federated server applications
13
+ powered by [ActivityPub] and other standards, so-called [fediverse]. This
14
+ package integrates Fedify with [AdonisJS], a fully featured Node.js web
15
+ framework.
16
+
17
+ It gives an AdonisJS application everything Fedify needs and nothing it does
18
+ not: a server middleware that mounts the federation, a service provider that
19
+ owns the `Federation` object's lifecycle and constructs that middleware, a
20
+ *config/fedify.ts* file, and a `ctx.federation` context on every request. The
21
+ package follows the layout and conventions of the official AdonisJS packages
22
+ (`@adonisjs/cors`, `@adonisjs/session`), so it should feel familiar.
23
+
24
+ [npm badge]: https://img.shields.io/npm/v/@fedify/adonisjs?logo=npm
25
+ [npm]: https://www.npmjs.com/package/@fedify/adonisjs
26
+ [Matrix badge]: https://img.shields.io/matrix/fedify%3Amatrix.org
27
+ [Matrix]: https://matrix.to/#/#fedify:matrix.org
28
+ [@fedify@hackers.pub badge]: https://fedi-badge.minhee.org/@fedify@hackers.pub/followers.svg
29
+ [@fedify@hackers.pub]: https://hackers.pub/@fedify
30
+ [Fedify]: https://fedify.dev/
31
+ [ActivityPub]: https://www.w3.org/TR/activitypub/
32
+ [fediverse]: https://en.wikipedia.org/wiki/Fediverse
33
+ [AdonisJS]: https://adonisjs.com/
34
+
35
+
36
+ Requirements
37
+ ------------
38
+
39
+ - AdonisJS 7.4.0 or later
40
+ - Fedify 2.4.0 or later
41
+ - Node.js 24 or later — and Node.js only; see below
42
+
43
+ The package ships both ESM and CommonJS builds. The CommonJS one works because
44
+ the Node.js versions AdonisJS supports can `require()` ESM dependencies.
45
+
46
+ ### Runtime support: Node.js only
47
+
48
+ > [!IMPORTANT]
49
+ > This package targets Node.js exclusively. It is published to npm only, never
50
+ > to JSR, and it is neither tested nor supported on Deno or Bun.
51
+
52
+ That is a deliberate scope decision inherited from the framework rather than an
53
+ oversight, for three reasons.
54
+
55
+ - AdonisJS documents Node.js as its only runtime. Its installation guide
56
+ lists Node.js ≥ 24 and npm ≥ 11 as the sole prerequisites and never
57
+ mentions Deno or Bun, and its maintainers have
58
+ [declined to support multiple runtimes][adonisjs-runtimes]. An integration
59
+ package cannot credibly promise a runtime that the framework it integrates
60
+ does not.
61
+ - The Node.js 24 floor is AdonisJS's own. `@adonisjs/core` declares
62
+ `engines.node >= 24.0.0`; this package matches that floor rather than
63
+ inventing one.
64
+ - JSR cannot represent this package. The AdonisJS-native layer works by
65
+ augmenting AdonisJS's own module types — `ctx.federation` on `HttpContext`,
66
+ and the `fedify`, `fedify.builder` and `fedify.config` container bindings.
67
+ JSR's [“slow types”][jsr-slow-types] rules reject ambient `declare module`
68
+ augmentation anywhere in a published module graph, and the augmentation
69
+ cannot be hidden from that graph without also hiding it from the
70
+ applications that need it. So the feature and JSR publication are mutually
71
+ exclusive, and the feature is the entire point of the package.
72
+
73
+ Consequently this package ships no *deno.json* and stays out of the Fedify
74
+ monorepo's Deno workspace — the same position as `@fedify/nestjs` and
75
+ `@fedify/next`.
76
+
77
+ [adonisjs-runtimes]: https://github.com/orgs/adonisjs/discussions/4191
78
+ [jsr-slow-types]: https://jsr.io/docs/about-slow-types#global-augmentation
79
+
80
+
81
+ Installation
82
+ ------------
83
+
84
+ ~~~~ sh
85
+ node ace add @fedify/adonisjs
86
+ ~~~~
87
+
88
+ That installs the package and runs its `configure` hook, which does the whole
89
+ wiring:
90
+
91
+ - installs `@fedify/fedify` and `@fedify/vocab`, in the version range this
92
+ package's peer dependency accepts,
93
+ - declares `PUBLIC_URL` and an optional `FEDERATION_HANDLE_HOST` in
94
+ *start/env.ts*, and adds `PUBLIC_URL` to *.env*,
95
+ - writes *config/fedify.ts*, *start/federation.ts* and
96
+ *app/federation/main.ts*,
97
+ - registers the service provider (`@fedify/adonisjs/fedify_provider`) and the
98
+ *start/federation.ts* preload file in *adonisrc.ts*, and
99
+ - adds the middleware (`@fedify/adonisjs/fedify_middleware`) to the front of
100
+ the `server` middleware stack in *start/kernel.ts*.
101
+
102
+ To install it without running the hook, use `npm install @fedify/adonisjs`
103
+ followed by `node ace configure @fedify/adonisjs`.
104
+
105
+
106
+ Usage
107
+ -----
108
+
109
+ ### Registering dispatchers
110
+
111
+ Actor dispatchers, collection dispatchers and inbox listeners are registered on
112
+ the `FederationBuilder` exported by `@fedify/adonisjs/services/builder`:
113
+
114
+ ~~~~ typescript
115
+ // app/federation/main.ts
116
+ import federation from '@fedify/adonisjs/services/builder'
117
+ import { Endpoints, Person } from '@fedify/vocab'
118
+ import Actor from '#models/actor'
119
+
120
+ federation
121
+ .setActorDispatcher('/actors/{identifier}', async (ctx, identifier) => {
122
+ const actor = await Actor.findBy('identifier', identifier)
123
+ if (actor === null) return null
124
+
125
+ const keys = await ctx.getActorKeyPairs(identifier)
126
+
127
+ return new Person({
128
+ id: ctx.getActorUri(identifier),
129
+ preferredUsername: identifier,
130
+ name: actor.name,
131
+ inbox: ctx.getInboxUri(identifier),
132
+ endpoints: new Endpoints({ sharedInbox: ctx.getInboxUri() }),
133
+ publicKeys: keys.map((keyPair) => keyPair.cryptographicKey),
134
+ assertionMethods: keys.map((keyPair) => keyPair.multikey),
135
+ })
136
+ })
137
+ .setKeyPairsDispatcher(async (_ctx, _identifier) => {
138
+ // Load or generate the actor's key pairs.
139
+ return []
140
+ })
141
+ ~~~~
142
+
143
+ The module is loaded by the *start/federation.ts* preload file, which AdonisJS
144
+ imports after every service provider has booted — so dispatchers may use Lucid
145
+ models, the container, and anything else the application provides. Import
146
+ further modules from that file as the federation grows.
147
+
148
+ Registration happens once per process. Do not add *app/federation* to
149
+ `hotHook.boundaries`: re-importing a module on a hot reload would register the
150
+ same dispatcher twice, and Fedify rejects that. Restart the dev server after
151
+ changing a dispatcher.
152
+
153
+ ### Using the federation from controllers
154
+
155
+ The middleware attaches a Fedify `RequestContext` to every request:
156
+
157
+ ~~~~ typescript
158
+ import type { HttpContext } from '@adonisjs/core/http'
159
+
160
+ export default class ActorsController {
161
+ // GET /actors/:identifier — the same URL template the actor dispatcher
162
+ // serves, so the route parameter is the local identifier.
163
+ async show({ params, view, federation }: HttpContext) {
164
+ return view.render('pages/actors/show', {
165
+ actorUri: federation.getActorUri(params.identifier).href,
166
+ })
167
+ }
168
+
169
+ // GET /lookup/:handle — dereference a remote object by its fediverse handle
170
+ // or URI, for example `@fedify@hackers.pub`.
171
+ async lookup({ params, view, federation }: HttpContext) {
172
+ const object = await federation.lookupObject(params.handle)
173
+
174
+ return view.render('pages/actors/lookup', { object })
175
+ }
176
+ }
177
+ ~~~~
178
+
179
+ `federation` is a getter on `HttpContext` — installed once by the service
180
+ provider through AdonisJS's `Macroable`, the same mechanism behind `ctx.view` —
181
+ and the context is created lazily on first access and memoised for the rest of
182
+ the request, so requests that never touch it pay nothing for it.
183
+
184
+ Outside an HTTP request — in an Ace command, a scheduled job or a queue worker —
185
+ resolve the `Federation` object from the container and create a context from
186
+ the canonical origin:
187
+
188
+ ~~~~ typescript
189
+ import app from '@adonisjs/core/services/app'
190
+ import env from '#start/env'
191
+
192
+ const federation = await app.container.make('fedify')
193
+ const ctx = federation.createContext(new URL(env.get('PUBLIC_URL')), null)
194
+ await ctx.sendActivity({ identifier: 'me' }, recipient, activity)
195
+ ~~~~
196
+
197
+ The binding resolves only once the application is ready; resolving it earlier —
198
+ from a preload file or a service provider — throws `E_FEDERATION_NOT_READY`
199
+ rather than handing back a federation with no routes. Preload files should
200
+ register dispatchers on `@fedify/adonisjs/services/builder` instead.
201
+
202
+ ### Without the container
203
+
204
+ `fedifyMiddleware` is the package's main integration function and its default
205
+ export. It takes a `Federation` and a context-data factory, and has nothing to
206
+ do with the IoC container, so an application that would rather build its own
207
+ `Federation` can skip the provider entirely:
208
+
209
+ ~~~~ typescript
210
+ // start/kernel.ts
211
+ import fedifyMiddleware from '@fedify/adonisjs'
212
+ import federation from '../app/federation.js'
213
+
214
+ server.use([
215
+ async () => ({ default: fedifyMiddleware(federation, (ctx) => ctx) }),
216
+ ])
217
+ ~~~~
218
+
219
+ Here *app/federation.ts* is a module of the application's own that
220
+ default-exports the `Federation` it built — not the scaffolded
221
+ *app/federation/main.ts*, which registers dispatchers on the builder service
222
+ and exports nothing.
223
+
224
+ The factory may be left out only when the federation's context data type admits
225
+ `undefined`, since that is what the default factory produces; with the package's
226
+ default context data of `HttpContext | null`, `(ctx) => ctx` gives the same
227
+ behaviour as the provider.
228
+
229
+ It returns a subclass of `FedifyMiddleware`, the class the service provider
230
+ constructs, with the arguments closed over so that `server.use()` can
231
+ instantiate it without the container. The class itself is exported too, for
232
+ applications that bind it in the container from a provider of their own —
233
+ *start/kernel.ts* then registers `@fedify/adonisjs/fedify_middleware` as usual,
234
+ and the binding is what constructs it:
235
+
236
+ ~~~~ typescript
237
+ import { FedifyMiddleware } from '@fedify/adonisjs'
238
+
239
+ this.app.container.bind(
240
+ FedifyMiddleware,
241
+ () => new FedifyMiddleware(federation, (ctx) => ctx),
242
+ )
243
+ ~~~~
244
+
245
+ `ctx.federation` works either way: the middleware installs the getter itself
246
+ when no provider has.
247
+
248
+ ### Configuration
249
+
250
+ *config/fedify.ts* takes every [`FederationOptions`] Fedify accepts, plus a few
251
+ AdonisJS-specific keys:
252
+
253
+ ~~~~ typescript
254
+ import { defineConfig } from '@fedify/adonisjs'
255
+ import { SqliteKvStore, SqliteMessageQueue } from '@fedify/sqlite'
256
+ import env from '#start/env'
257
+
258
+ export default defineConfig({
259
+ origin: env.get('PUBLIC_URL'),
260
+ kv: new SqliteKvStore(database),
261
+ queue: new SqliteMessageQueue(database),
262
+ queueContextData: () => null,
263
+ ignoreRoutePrefixes: ['/@vite/'],
264
+ })
265
+ ~~~~
266
+
267
+ | Option | Purpose |
268
+ | --------------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
269
+ | `origin` | Required. The canonical public origin. Actor URIs and activity IDs are minted from it. |
270
+ | `kv` | Fedify's key–value store. Defaults to an in-memory store, with a warning in production. |
271
+ | `contextDataFactory` | Builds the per-request context data. Defaults to the `HttpContext` itself, and is required once `FedifyTypes` is augmented. |
272
+ | `ignoreRoutePrefixes` | URL path prefixes that skip Fedify entirely. Defaults to none. |
273
+ | `logging` | Whether to pipe Fedify's LogTape output into the AdonisJS logger. Defaults to `true`. |
274
+ | `queueContextData` | Who drains the queue: a callback (this process) or `false` (a separate worker). Required with `queue`; a callback without one warns. |
275
+
276
+ [`FederationOptions`]: https://jsr.io/@fedify/fedify/doc/~/FederationOptions
277
+
278
+ ### Module exports
279
+
280
+ | Specifier | What it is |
281
+ | ------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
282
+ | `@fedify/adonisjs` | `fedifyMiddleware` (default), `FedifyMiddleware`, `defineConfig`, `configure`, `configureFedifyLogging`, `stubsRoot`, the error classes and the types |
283
+ | `@fedify/adonisjs/types` | The augmentation target for `FedifyTypes` |
284
+ | `@fedify/adonisjs/fedify_provider` | The service provider, referenced by specifier from *adonisrc.ts* |
285
+ | `@fedify/adonisjs/fedify_middleware` | The `FedifyMiddleware` class, referenced by specifier from *start/kernel.ts* |
286
+ | `@fedify/adonisjs/services/builder` | The `FederationBuilder`, as an AdonisJS service module |
287
+
288
+ Each subpath exists because AdonisJS reaches for it by module specifier or by
289
+ service-module convention, and the names follow the official packages
290
+ (`@adonisjs/cors/cors_middleware`, `@adonisjs/cors/cors_provider`); everything
291
+ else lives on the root export. The built `Federation` deliberately has no
292
+ service module of its own — resolve it with `app.container.make('fedify')`,
293
+ which can also tell you *why* when it is not available yet. A service module
294
+ in the style of `@adonisjs/core/services/router` would have to capture the
295
+ instance from an `app.booted()` hook, but dispatchers are registered from
296
+ preload files, which AdonisJS imports after `booted`, so the `Federation` is
297
+ only sealed in the provider's `ready` phase — later than such a module could
298
+ observe.
299
+
300
+ ### Context data
301
+
302
+ Fedify threads an application-defined value through every dispatcher and inbox
303
+ listener. By default that value is `HttpContext | null` — `null` when Fedify is
304
+ working outside a request, such as inside a queue worker. To carry something
305
+ else, augment the register interface and supply a factory. The augmentation
306
+ makes `contextDataFactory` a required option, so forgetting it is a type error
307
+ in *config/fedify.ts* rather than an `HttpContext` turning up where a dispatcher
308
+ expected the augmented value:
309
+
310
+ ~~~~ typescript
311
+ declare module '@fedify/adonisjs/types' {
312
+ interface FedifyTypes {
313
+ contextData: { requestId: string | undefined }
314
+ }
315
+ }
316
+
317
+ export default defineConfig({
318
+ origin: env.get('PUBLIC_URL'),
319
+ contextDataFactory: (ctx) => ({ requestId: ctx.request.id() }),
320
+ })
321
+ ~~~~
322
+
323
+ The factory runs in the server middleware stack, before route matching, so only
324
+ `ctx.request`, `ctx.response`, `ctx.logger` and `ctx.containerResolver` are set;
325
+ `ctx.auth`, `ctx.session`, `ctx.bouncer`, `ctx.params` and `ctx.route` are still
326
+ `undefined` (`ctx.params` is `{}`) because the middleware that fills them has
327
+ not run yet. Look anything session-bound up inside the dispatcher that needs
328
+ it—a federation request comes from a remote server and has no session anyway.
329
+
330
+
331
+ How requests are routed
332
+ -----------------------
333
+
334
+ The middleware runs in the `server` stack, before route matching and before
335
+ the body parser. Both matter: Fedify serves paths the AdonisJS router knows
336
+ nothing about, and HTTP Signature verification needs the raw request body. It
337
+ also runs before `@adonisjs/shield`, so inbox `POST`s are not rejected by CSRF
338
+ protection.
339
+
340
+ The configure hook puts it *first* in that stack, and it belongs there:
341
+ content negotiation decides what Fedify answers, so nothing that rewrites
342
+ `Accept` may run ahead of it. The API starter kit's
343
+ `force_json_response_middleware` turns every `Accept` into
344
+ `application/json`, which Fedify reads as a request for the ActivityPub
345
+ document—a browser asking an actor URL for HTML would get the actor document
346
+ instead, and the HTML fall-through below could never run. Requests Fedify
347
+ answers itself therefore skip the rest of the stack, including the starter
348
+ kits' `container_bindings_middleware`; a dispatcher that needs an
349
+ `HttpContext` reads it from `ctx.data`, which is what the default context data
350
+ carries.
351
+
352
+ Every request the middleware does not bypass (see `ignoreRoutePrefixes`)
353
+ reaches Fedify first, and one of three things happens.
354
+
355
+ - Fedify serves it. Actor documents, collections, inboxes,
356
+ */.well-known/webfinger* and */.well-known/nodeinfo* never reach the
357
+ AdonisJS router.
358
+ - Fedify has no route for the path. The request is handed to the AdonisJS
359
+ router, and the application answers exactly as it would without this
360
+ package.
361
+ - Fedify has a route but cannot satisfy the `Accept` header. This is the
362
+ interesting one. Rather than answering `406` immediately, the middleware
363
+ gives the AdonisJS router a chance first, and only answers
364
+ `406 Not Acceptable` if the router had nothing either.
365
+
366
+ That last rule is what lets a single URL serve an HTML profile page to browsers
367
+ and an ActivityPub actor document to servers. Register an ordinary route on the
368
+ same path as the actor dispatcher and it will handle the HTML half:
369
+
370
+ ~~~~ typescript
371
+ // start/routes.ts — same path as setActorDispatcher('/actors/{identifier}', …)
372
+ router.get('/actors/:identifier', [controllers.Actors, 'show'])
373
+ ~~~~
374
+
375
+
376
+ Deployment notes
377
+ ----------------
378
+
379
+ - Set `origin` to the public URL. Fedify mints actor URIs from it, so behind
380
+ a reverse proxy, a load balancer or a tunnel it must be the address remote
381
+ servers can reach, not the address Node listens on. When developing with
382
+ `fedify tunnel`, point `PUBLIC_URL` at the tunnel and restart.
383
+ - Configure a persistent `kv` and a `queue`. Without a queue, delivery is
384
+ synchronous and failed deliveries are never retried. `@fedify/sqlite`,
385
+ `@fedify/postgres`, `@fedify/redis` and `@fedify/mysql` all provide both.
386
+ `queueContextData` says who drains the queue: a callback has the web
387
+ process do it (started in the `ready` hook, stopped on shutdown, and never
388
+ started in an Ace command or a test run), while `false` leaves the queue to
389
+ a separate worker. A `queue` without it is refused at boot. A callback
390
+ without a `queue` only warns, since a task registered through
391
+ `defineTask()` may bring its own queue. `false` without a `queue` is
392
+ accepted silently, so a deployment that enables the queue in production
393
+ only can write `queue: app.inProduction ? … : undefined` together with
394
+ `queueContextData: app.inProduction ? () => … : false`.
395
+ - Configure `trustProxy` in *config/app.ts* when a proxy sits in front of the
396
+ application. AdonisJS honours `X-Forwarded-Proto` and `X-Forwarded-Host`
397
+ only from peers it trusts, and its default trusts loopback — which is
398
+ where `fedify tunnel` connects from. Name the proxy's address rather than
399
+ trusting every client, since a trusted client chooses the host and protocol
400
+ the application sees.
401
+ - Make the proxy preserve `Host`. HTTP Signatures cover the bytes the peer
402
+ sent, so a proxy that rewrites `Host` to the upstream's own name—the
403
+ `proxy_pass` default in nginx—breaks signature verification. Keep it, port
404
+ included: `proxy_set_header Host $http_host;` in nginx. Over HTTP/2 the
405
+ `:authority` pseudo-header wins over `X-Forwarded-Host`, so a proxy
406
+ speaking HTTP/2 to the application has to preserve that too.
407
+ - Watch for the “unusable request authority” warning. A host carrying a
408
+ path, query, fragment or userinfo cannot be a real authority. The request
409
+ is still answered, but Fedify sees a placeholder host, and while `origin`
410
+ keeps the URLs it mints correct, a request signed over RFC 9421's
411
+ `@authority` fails to verify. The warning is logged once per distinct
412
+ value, for at most sixteen values an hour, so junk hosts from clients can
413
+ neither fill the log nor hide a real misconfiguration for long. Behind a
414
+ proxy, treat it as a misconfiguration to fix; without one, it only records
415
+ that a client sent an unusable `Host`.
416
+
417
+ A complete, runnable application using all of this lives in the
418
+ [*examples/adonisjs*] directory.
419
+
420
+ [*examples/adonisjs*]: https://github.com/fedify-dev/fedify/tree/main/examples/adonisjs
@@ -0,0 +1,108 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
+ const require_package = require('./package.cjs');
3
+ const require_stubs_main = require('./stubs/main.cjs');
4
+
5
+ //#region configure.ts
6
+ /**
7
+ * The range the Fedify runtime packages are installed with.
8
+ *
9
+ * Derived from this package's own version so a release cannot leave it behind:
10
+ * every package in this monorepo shares one version, and the `@fedify/fedify`
11
+ * peer dependency is declared as `workspace:^`, which publishes as this range.
12
+ *
13
+ * Installing unpinned would pull the next major once it is out, which the peer
14
+ * range rejects: a peer warning, and two copies of the vocabulary, which the
15
+ * `instanceof` comparisons noted below cannot survive.
16
+ */
17
+ const FEDIFY_RANGE = `^${require_package.version}`;
18
+ /**
19
+ * Wires `@fedify/adonisjs` into an application.
20
+ *
21
+ * Run by `node ace configure @fedify/adonisjs` (and therefore by
22
+ * `node ace add @fedify/adonisjs`); see the module documentation for the
23
+ * steps it performs.
24
+ */
25
+ async function configure(command) {
26
+ const codemods = await command.createCodemods();
27
+ /**
28
+ * Fedify itself is a peer dependency: the application, not this package,
29
+ * decides which version it runs, and having exactly one copy in the tree
30
+ * matters because Fedify's vocabulary classes are compared with `instanceof`.
31
+ *
32
+ * `@fedify/vocab` carries the ActivityStreams classes (`Person`, `Follow`,
33
+ * `Note`, …) that dispatchers and inbox listeners are written against.
34
+ */
35
+ await codemods.installPackages([{
36
+ name: `@fedify/fedify@${FEDIFY_RANGE}`,
37
+ isDevDependency: false
38
+ }, {
39
+ name: `@fedify/vocab@${FEDIFY_RANGE}`,
40
+ isDevDependency: false
41
+ }]);
42
+ /**
43
+ * `PUBLIC_URL` is the canonical origin remote servers use to reach this
44
+ * application. Fedify mints actor URIs and activity IDs from it, so it has to
45
+ * be the public address rather than the listen address — behind a proxy or a
46
+ * `fedify tunnel` those differ.
47
+ *
48
+ * `FEDERATION_HANDLE_HOST` is optional and only needed when fediverse handles
49
+ * live on a different domain than the web origin, for example `@me@example.com`
50
+ * served from `https://social.example.com`.
51
+ */
52
+ await codemods.defineEnvValidations({
53
+ leadingComment: "Variables for configuring Fedify",
54
+ variables: {
55
+ PUBLIC_URL: `Env.schema.string({ format: 'url', tld: false })`,
56
+ FEDERATION_HANDLE_HOST: `Env.schema.string.optional({ format: 'host' })`
57
+ }
58
+ });
59
+ await codemods.defineEnvVariables({ PUBLIC_URL: "http://localhost:3333" });
60
+ /**
61
+ * `config/fedify.ts` — the federation options.
62
+ */
63
+ await codemods.makeUsingStub(require_stubs_main.stubsRoot, "config/fedify.stub", {});
64
+ /**
65
+ * `start/federation.ts` — a preload file that imports the application's
66
+ * dispatcher modules.
67
+ *
68
+ * A preload file rather than directory globbing: preload files are ordinary
69
+ * application source, so `node ace build` compiles them and their relative
70
+ * imports along with everything else, and AdonisJS imports them after every
71
+ * provider has booted, which is what lets dispatchers use Lucid models.
72
+ */
73
+ await codemods.makeUsingStub(require_stubs_main.stubsRoot, "start/federation.stub", {});
74
+ /**
75
+ * `app/federation/main.ts` — a starting point for dispatchers.
76
+ */
77
+ await codemods.makeUsingStub(require_stubs_main.stubsRoot, "federation/main.stub", {});
78
+ await codemods.updateRcFile((rcFile) => {
79
+ rcFile.addProvider("@fedify/adonisjs/fedify_provider");
80
+ rcFile.addPreloadFile("#start/federation");
81
+ });
82
+ /**
83
+ * The middleware must live in the **server** stack.
84
+ *
85
+ * Fedify answers paths the AdonisJS router does not know about
86
+ * (`/.well-known/webfinger`, `/.well-known/nodeinfo`, actor and inbox
87
+ * endpoints), so it has to run before route matching. The server stack also
88
+ * runs before `bodyparser_middleware` and before `@adonisjs/shield`, which
89
+ * matters twice over: HTTP Signature verification needs the raw, unparsed
90
+ * request body, and inbox `POST`s must not be rejected by CSRF protection.
91
+ *
92
+ * It also goes *first* in that stack, so that Fedify negotiates on the
93
+ * client's real `Accept` header. The API starter kit's
94
+ * `force_json_response_middleware`, for example, rewrites every `Accept` to
95
+ * `application/json`, which would make Fedify serve the ActivityPub document
96
+ * to a browser asking for HTML and skip the HTML fall-through.
97
+ */
98
+ await codemods.registerMiddleware("server", [{
99
+ path: "@fedify/adonisjs/fedify_middleware",
100
+ position: "before"
101
+ }]);
102
+ command.logger.success("Configured @fedify/adonisjs");
103
+ command.logger.info("Register dispatchers in app/federation/main.ts, then run \"node ace serve\"");
104
+ }
105
+
106
+ //#endregion
107
+ exports.configure = configure;
108
+ //# sourceMappingURL=configure.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"configure.cjs","names":["stubsRoot"],"sources":["../configure.ts"],"sourcesContent":["/**\n * The configure hook, run by `node ace configure @fedify/adonisjs`.\n *\n * It performs every wiring step the package needs, so that a freshly configured\n * application already federates: install the Fedify runtime packages, declare\n * the environment variables, publish the config file and the federation preload\n * files, register the service provider, and add the middleware to the server\n * stack.\n *\n * @module\n */\nimport type ConfigureCommand from \"@adonisjs/core/commands/configure\";\n\nimport packageJson from \"./package.json\" with { type: \"json\" };\nimport { stubsRoot } from \"./stubs/main.ts\";\n\n/**\n * The slice of `@adonisjs/assembler`'s `RcFileTransformer` this hook touches.\n *\n * Spelled out rather than inferred because the assembler is an *optional* peer\n * dependency: wherever it is absent — a type-check of a tree that did not\n * install it, for instance — the inferred parameter type of `updateRcFile`'s\n * callback degrades to `any`, and this file stops compiling under\n * `noImplicitAny`.\n */\ninterface RcFileEditor {\n addProvider(providerPath: string): unknown;\n addPreloadFile(modulePath: string): unknown;\n}\n\n/**\n * The range the Fedify runtime packages are installed with.\n *\n * Derived from this package's own version so a release cannot leave it behind:\n * every package in this monorepo shares one version, and the `@fedify/fedify`\n * peer dependency is declared as `workspace:^`, which publishes as this range.\n *\n * Installing unpinned would pull the next major once it is out, which the peer\n * range rejects: a peer warning, and two copies of the vocabulary, which the\n * `instanceof` comparisons noted below cannot survive.\n */\nconst FEDIFY_RANGE = `^${packageJson.version}`;\n\n/**\n * Wires `@fedify/adonisjs` into an application.\n *\n * Run by `node ace configure @fedify/adonisjs` (and therefore by\n * `node ace add @fedify/adonisjs`); see the module documentation for the\n * steps it performs.\n */\nexport async function configure(command: ConfigureCommand): Promise<void> {\n const codemods = await command.createCodemods();\n\n /**\n * Fedify itself is a peer dependency: the application, not this package,\n * decides which version it runs, and having exactly one copy in the tree\n * matters because Fedify's vocabulary classes are compared with `instanceof`.\n *\n * `@fedify/vocab` carries the ActivityStreams classes (`Person`, `Follow`,\n * `Note`, …) that dispatchers and inbox listeners are written against.\n */\n await codemods.installPackages([\n { name: `@fedify/fedify@${FEDIFY_RANGE}`, isDevDependency: false },\n { name: `@fedify/vocab@${FEDIFY_RANGE}`, isDevDependency: false },\n ]);\n\n /**\n * `PUBLIC_URL` is the canonical origin remote servers use to reach this\n * application. Fedify mints actor URIs and activity IDs from it, so it has to\n * be the public address rather than the listen address — behind a proxy or a\n * `fedify tunnel` those differ.\n *\n * `FEDERATION_HANDLE_HOST` is optional and only needed when fediverse handles\n * live on a different domain than the web origin, for example `@me@example.com`\n * served from `https://social.example.com`.\n */\n await codemods.defineEnvValidations({\n leadingComment: \"Variables for configuring Fedify\",\n variables: {\n PUBLIC_URL: `Env.schema.string({ format: 'url', tld: false })`,\n FEDERATION_HANDLE_HOST: `Env.schema.string.optional({ format: 'host' })`,\n },\n });\n\n await codemods.defineEnvVariables({ PUBLIC_URL: \"http://localhost:3333\" });\n\n /**\n * `config/fedify.ts` — the federation options.\n */\n await codemods.makeUsingStub(stubsRoot, \"config/fedify.stub\", {});\n\n /**\n * `start/federation.ts` — a preload file that imports the application's\n * dispatcher modules.\n *\n * A preload file rather than directory globbing: preload files are ordinary\n * application source, so `node ace build` compiles them and their relative\n * imports along with everything else, and AdonisJS imports them after every\n * provider has booted, which is what lets dispatchers use Lucid models.\n */\n await codemods.makeUsingStub(stubsRoot, \"start/federation.stub\", {});\n\n /**\n * `app/federation/main.ts` — a starting point for dispatchers.\n */\n await codemods.makeUsingStub(stubsRoot, \"federation/main.stub\", {});\n\n await codemods.updateRcFile((rcFile: RcFileEditor) => {\n rcFile.addProvider(\"@fedify/adonisjs/fedify_provider\");\n rcFile.addPreloadFile(\"#start/federation\");\n });\n\n /**\n * The middleware must live in the **server** stack.\n *\n * Fedify answers paths the AdonisJS router does not know about\n * (`/.well-known/webfinger`, `/.well-known/nodeinfo`, actor and inbox\n * endpoints), so it has to run before route matching. The server stack also\n * runs before `bodyparser_middleware` and before `@adonisjs/shield`, which\n * matters twice over: HTTP Signature verification needs the raw, unparsed\n * request body, and inbox `POST`s must not be rejected by CSRF protection.\n *\n * It also goes *first* in that stack, so that Fedify negotiates on the\n * client's real `Accept` header. The API starter kit's\n * `force_json_response_middleware`, for example, rewrites every `Accept` to\n * `application/json`, which would make Fedify serve the ActivityPub document\n * to a browser asking for HTML and skip the HTML fall-through.\n */\n await codemods.registerMiddleware(\"server\", [{\n path: \"@fedify/adonisjs/fedify_middleware\",\n position: \"before\",\n }]);\n\n command.logger.success(\"Configured @fedify/adonisjs\");\n command.logger.info(\n 'Register dispatchers in app/federation/main.ts, then run \"node ace serve\"',\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAyCA,MAAM,eAAe;;;;;;;;AASrB,eAAsB,UAAU,SAA0C;CACxE,MAAM,WAAW,MAAM,QAAQ,eAAe;;;;;;;;;CAU9C,MAAM,SAAS,gBAAgB,CAC7B;EAAE,MAAM,kBAAkB;EAAgB,iBAAiB;CAAM,GACjE;EAAE,MAAM,iBAAiB;EAAgB,iBAAiB;CAAM,CAClE,CAAC;;;;;;;;;;;CAYD,MAAM,SAAS,qBAAqB;EAClC,gBAAgB;EAChB,WAAW;GACT,YAAY;GACZ,wBAAwB;EAC1B;CACF,CAAC;CAED,MAAM,SAAS,mBAAmB,EAAE,YAAY,wBAAwB,CAAC;;;;CAKzE,MAAM,SAAS,cAAcA,8BAAW,sBAAsB,CAAC,CAAC;;;;;;;;;;CAWhE,MAAM,SAAS,cAAcA,8BAAW,yBAAyB,CAAC,CAAC;;;;CAKnE,MAAM,SAAS,cAAcA,8BAAW,wBAAwB,CAAC,CAAC;CAElE,MAAM,SAAS,cAAc,WAAyB;EACpD,OAAO,YAAY,kCAAkC;EACrD,OAAO,eAAe,mBAAmB;CAC3C,CAAC;;;;;;;;;;;;;;;;;CAkBD,MAAM,SAAS,mBAAmB,UAAU,CAAC;EAC3C,MAAM;EACN,UAAU;CACZ,CAAC,CAAC;CAEF,QAAQ,OAAO,QAAQ,6BAA6B;CACpD,QAAQ,OAAO,KACb,6EACF;AACF"}
@@ -0,0 +1,14 @@
1
+ import ConfigureCommand from "@adonisjs/core/commands/configure";
2
+
3
+ //#region configure.d.ts
4
+ /**
5
+ * Wires `@fedify/adonisjs` into an application.
6
+ *
7
+ * Run by `node ace configure @fedify/adonisjs` (and therefore by
8
+ * `node ace add @fedify/adonisjs`); see the module documentation for the
9
+ * steps it performs.
10
+ */
11
+ declare function configure(command: ConfigureCommand): Promise<void>;
12
+ //#endregion
13
+ export { configure };
14
+ //# sourceMappingURL=configure.d.cts.map
@@ -0,0 +1,14 @@
1
+ import ConfigureCommand from "@adonisjs/core/commands/configure";
2
+
3
+ //#region configure.d.ts
4
+ /**
5
+ * Wires `@fedify/adonisjs` into an application.
6
+ *
7
+ * Run by `node ace configure @fedify/adonisjs` (and therefore by
8
+ * `node ace add @fedify/adonisjs`); see the module documentation for the
9
+ * steps it performs.
10
+ */
11
+ declare function configure(command: ConfigureCommand): Promise<void>;
12
+ //#endregion
13
+ export { configure };
14
+ //# sourceMappingURL=configure.d.ts.map