Skip to content

Commit dca6c7d

Browse files
docs(features/plugins): bring current to v0.0.219
The loader page was written at v0.0.215 and called several shipped seams 'proposed' or 'road ahead'. Update to reality: - Status table: api.mountApp (#590), api.serverInfo (#601/#605), api.reservePath (#602/#607), api.plugins (#610/#612) all shipped in 0.0.217–0.0.218; the CLI config-file + --plugin flag shipped (#593/#595). - activate(api) sample gains serverInfo() and api.plugins; the api-surface paragraph documents reservePath and mountApp. - id derivation: generic <name>/plugin.js now derives from the parent dir (#596, 0.0.219), so distinct plugins get distinct ids with none set. - Add the --plugin module@prefix CLI form. - Rewrite the raw-body section to lead with api.mountApp (it shipped) — the manual pattern is now the under-the-hood note. - Reference set: add the 33-plugin out-of-tree suite (dashboard, DAV family, the shims, S3, JMAP, …) alongside Tideholm & bridge. - Road ahead: CLI + mountApp done; next are api.events (#603) and api.authorize (#604), then publishing the plugin suite as a package. Docusaurus build passes.
1 parent 5ca6194 commit dca6c7d

1 file changed

Lines changed: 87 additions & 45 deletions

File tree

docs/features/plugins.md

Lines changed: 87 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ description: Extend JSS with whole applications — where the plugin system stan
88

99
JSS can host entire applications beside your pod — same origin, same server,
1010
with the pod's identity system as the app's login. This page covers what
11-
works **today** (v0.0.215+), how to build a plugin, and the seams underneath.
11+
works **today** (v0.0.219+), how to build a plugin, and the seams underneath.
1212

1313
:::tip The one-line version
1414
`createServer({ plugins: [{ module: 'my-app/plugin.js', prefix: '/myapp' }] })`
@@ -21,13 +21,17 @@ be the app's account — no separate passwords.
2121

2222
| Piece | Status | Where |
2323
|---|---|---|
24-
| **Plugin loader** (`plugins` option, `activate(api)`) |**Shipped in v0.0.215** | [#206](https://github.com/JavaScriptSolidServer/JavaScriptSolidServer/issues/206), [#589](https://github.com/JavaScriptSolidServer/JavaScriptSolidServer/pull/589) |
25-
| Application mount points (`appPaths`) | ✅ Shipped in v0.0.213 | [#582](https://github.com/JavaScriptSolidServer/JavaScriptSolidServer/issues/582), [#585](https://github.com/JavaScriptSolidServer/JavaScriptSolidServer/pull/585) |
26-
| Public identity accessor (`getAgent`) | ✅ Shipped in v0.0.214 | [#584](https://github.com/JavaScriptSolidServer/JavaScriptSolidServer/issues/584), [#586](https://github.com/JavaScriptSolidServer/JavaScriptSolidServer/pull/586) |
27-
| WebSocket routing for realtime plugins (`api.ws.route`) | ✅ Shipped in v0.0.215 | [#588](https://github.com/JavaScriptSolidServer/JavaScriptSolidServer/issues/588), [#589](https://github.com/JavaScriptSolidServer/JavaScriptSolidServer/pull/589) |
28-
| Reference plugins | ✅ Two running — [Tideholm](https://github.com/melvincarvalho/tideholm/tree/gh-pages/jss-plugin) (strategy game, pod WebIDs as player accounts) and [bridge](https://github.com/melvincarvalho/bridge/tree/gh-pages/jss-plugin) (realtime card game over WebSocket) | [#206 discussion](https://github.com/JavaScriptSolidServer/JavaScriptSolidServer/issues/206) |
29-
| Raw-body mode for wrapped apps | 📋 Pattern documented below; helper proposed | [#583](https://github.com/JavaScriptSolidServer/JavaScriptSolidServer/issues/583) |
30-
| Bundled-feature migration, CLI config block, panes | 🔭 Next | [#564](https://github.com/JavaScriptSolidServer/JavaScriptSolidServer/issues/564) |
24+
| **Plugin loader** (`plugins` option, `activate(api)`) |**Shipped v0.0.215** | [#206](https://github.com/JavaScriptSolidServer/JavaScriptSolidServer/issues/206), [#589](https://github.com/JavaScriptSolidServer/JavaScriptSolidServer/pull/589) |
25+
| Application mount points (`appPaths`) | ✅ Shipped v0.0.213 | [#582](https://github.com/JavaScriptSolidServer/JavaScriptSolidServer/issues/582), [#585](https://github.com/JavaScriptSolidServer/JavaScriptSolidServer/pull/585) |
26+
| Public identity accessor (`getAgent`) | ✅ Shipped v0.0.214 | [#584](https://github.com/JavaScriptSolidServer/JavaScriptSolidServer/issues/584), [#586](https://github.com/JavaScriptSolidServer/JavaScriptSolidServer/pull/586) |
27+
| WebSocket routing (`api.ws.route`) | ✅ Shipped v0.0.215 | [#588](https://github.com/JavaScriptSolidServer/JavaScriptSolidServer/issues/588), [#589](https://github.com/JavaScriptSolidServer/JavaScriptSolidServer/pull/589) |
28+
| CLI: config-file `plugins` + repeatable `--plugin module@prefix` | ✅ Shipped v0.0.216–217 | [#593](https://github.com/JavaScriptSolidServer/JavaScriptSolidServer/pull/593), [#595](https://github.com/JavaScriptSolidServer/JavaScriptSolidServer/pull/595) |
29+
| Wrapping node apps (`api.mountApp`) | ✅ Shipped v0.0.217 | [#583](https://github.com/JavaScriptSolidServer/JavaScriptSolidServer/issues/583), [#590](https://github.com/JavaScriptSolidServer/JavaScriptSolidServer/pull/590) |
30+
| Server origin (`api.serverInfo()`) | ✅ Shipped v0.0.218 | [#601](https://github.com/JavaScriptSolidServer/JavaScriptSolidServer/issues/601), [#605](https://github.com/JavaScriptSolidServer/JavaScriptSolidServer/pull/605) |
31+
| Reserve fixed paths (`api.reservePath`) | ✅ Shipped v0.0.218 | [#602](https://github.com/JavaScriptSolidServer/JavaScriptSolidServer/issues/602), [#607](https://github.com/JavaScriptSolidServer/JavaScriptSolidServer/pull/607) |
32+
| Loaded-plugin roster (`api.plugins`) | ✅ Shipped v0.0.218 | [#610](https://github.com/JavaScriptSolidServer/JavaScriptSolidServer/issues/610), [#612](https://github.com/JavaScriptSolidServer/JavaScriptSolidServer/pull/612) |
33+
| Reference plugins | ✅ A [33-plugin out-of-tree suite](https://github.com/JavaScriptSolidServer/plugins) (dashboard, WebDAV/CalDAV/CardDAV, Mastodon/Bluesky/Matrix shims, S3, JMAP, …) plus [Tideholm](https://github.com/melvincarvalho/tideholm) & [bridge](https://github.com/melvincarvalho/bridge) | [plugins repo](https://github.com/JavaScriptSolidServer/plugins) |
34+
| Bundled-feature migration; `api.events`, `api.authorize` seams; panes | 🔭 Next | [#564](https://github.com/JavaScriptSolidServer/JavaScriptSolidServer/issues/564), [#603](https://github.com/JavaScriptSolidServer/JavaScriptSolidServer/issues/603), [#604](https://github.com/JavaScriptSolidServer/JavaScriptSolidServer/issues/604) |
3135

3236
## Using the loader
3337

@@ -49,6 +53,16 @@ const fastify = createServer({
4953
await fastify.listen({ port: 4443 });
5054
```
5155

56+
No config file? Mount the same apps from the CLI — the repeatable `--plugin`
57+
flag takes `module[@prefix]` and appends to any config-file `plugins`
58+
(v0.0.216–217):
59+
60+
```bash
61+
jss start \
62+
--plugin ./relay/plugin.js@/relay \
63+
--plugin ./dashboard/plugin.js@/dashboard
64+
```
65+
5266
A plugin module exports one function:
5367

5468
```js
@@ -67,15 +81,33 @@ export async function activate(api) {
6781
// Private server-side storage (never served over HTTP):
6882
const dir = api.storage.pluginDir();
6983

84+
// The server's own origin — for absolute URLs and loopback calls, so you
85+
// never repeat baseUrl in config. Call it per request (with port 0 the
86+
// real port exists only once listening):
87+
const { baseUrl, host, port } = api.serverInfo(); // (#601)
88+
89+
// Every co-loaded plugin, read-only — enumerate your siblings instead of a
90+
// hand-copied list (this is how the dashboard plugin knows what to show):
91+
for (const { id, prefix, module } of api.plugins) { /**/ } // (#610)
92+
7093
return { deactivate() { /* save state, clear timers */ } };
7194
}
7295
```
7396

74-
The api also carries `api.config` (the entry's config, verbatim) and
75-
`api.log` (speaks both pino and console dialects). A plugin that fails to
76-
import or activate **fails `listen()` loudly** — a server silently missing
77-
an app is worse than one that refuses to start. Entries take an optional
78-
`id` when two modules would reduce to the same name (it keys `pluginDir`).
97+
The api also carries `api.config` (the entry's config, verbatim), `api.log`
98+
(speaks both pino and console dialects), `api.reservePath(path)` — claim and
99+
WAC-exempt a fixed or `/:param/`-shaped route **outside** your prefix, for
100+
protocols that pin absolute paths (`/xrpc`, `/:user/did.json`) — and
101+
`api.mountApp(handler, { prefix })` for wrapping a node-style app (below). A
102+
plugin that fails to import or activate **fails `listen()` loudly** — a
103+
server silently missing an app is worse than one that refuses to start.
104+
105+
Entries take an optional `id` that keys `pluginDir`. It defaults to a name
106+
derived from `module`: the file's basename, or its **parent directory** for
107+
the conventional `<name>/plugin.js` layout (`relay/plugin.js``relay`), so
108+
distinct plugins get distinct ids with none set (v0.0.219+,
109+
[#596](https://github.com/JavaScriptSolidServer/JavaScriptSolidServer/issues/596)).
110+
Set it explicitly only if two specifiers still reduce to the same name.
79111

80112
Plugins load **only from operator config — never from pod storage** (pods
81113
are user-writable; a loader that read them would be remote code execution).
@@ -165,39 +197,46 @@ endpoint, stores the returned Bearer token, and attaches it to the app's
165197
API calls. Tokens expire after 3600s — handle the 401 by returning to the
166198
login screen.
167199

168-
## Wrapping an existing app (raw bodies)
200+
## Wrapping an existing app (`api.mountApp`)
169201

170202
If your "plugin" is an existing node-style HTTP app (`(req, res)` handler),
171-
two gotchas — both solved with one pattern:
172-
173-
1. Fastify's content parsers **consume request bodies** before handlers
174-
run, so your wrapped app hangs waiting for a stream that's been drained.
175-
2. Fastify wants to own the response unless you tell it otherwise.
203+
two gotchas bite: Fastify's content parsers **consume request bodies**
204+
before handlers run (so a wrapped app hangs waiting for a stream that's been
205+
drained), and Fastify wants to own the response. `api.mountApp` (v0.0.217,
206+
[#590](https://github.com/JavaScriptSolidServer/JavaScriptSolidServer/pull/590))
207+
handles both — plus the `appPaths` exemption and `reply.hijack()`:
176208

177209
```js
178-
await fastify.register(async (scope) => {
179-
// Pass bodies through untouched, scoped so the host is unaffected.
180-
scope.removeAllContentTypeParsers();
181-
scope.addContentTypeParser('*', (req, payload, done) => done(null, payload));
182-
183-
const handler = async (request, reply) => {
184-
request.raw.myAppAgent = await getAgent(request); // identity for the raw handler
185-
reply.hijack(); // fastify lets go of the response
186-
myNodeApp.handle(request.raw, reply.raw);
187-
};
188-
scope.all('/myapp', handler);
189-
scope.all('/myapp/*', handler);
190-
});
210+
export async function activate(api) {
211+
await api.mountApp(
212+
(req, res) => myNodeApp.handle(req, res), // your (req, res) app
213+
{ prefix: api.prefix }, // omit to use the entry prefix
214+
);
215+
}
191216
```
192217

193-
[#583](https://github.com/JavaScriptSolidServer/JavaScriptSolidServer/issues/583)
194-
proposes packaging this as `api.mountApp(prefix, nodeHandler)` so nobody
195-
rediscovers it the hard way.
218+
The wrapped app receives the **undrained** request stream and owns the
219+
response — including gzipped bodies, which it inflates itself. Need the
220+
caller's identity inside it? Resolve it before handing off:
221+
`req.agent = await api.auth.getAgent(request)`.
222+
223+
Under the hood it's a scoped pass-through content-type parser plus
224+
`reply.hijack()`, registered on both the bare prefix and its subtree — the
225+
pattern plugins rediscovered the hard way before the helper shipped
226+
([#583](https://github.com/JavaScriptSolidServer/JavaScriptSolidServer/issues/583)).
196227

197228
## Case studies: the plugins that built the system
198229

199230
Every seam above was forced by a real consumer before it shipped — that's
200-
the house method. Two reference plugins are live at
231+
the house method, and it scaled. A
232+
[33-plugin out-of-tree suite](https://github.com/JavaScriptSolidServer/plugins)
233+
now exercises the whole api — a status **dashboard** that auto-discovers its
234+
siblings via `api.plugins`, the WebDAV/CalDAV/CardDAV family, Mastodon /
235+
Bluesky / Matrix / ActivityPub shims, an S3 gateway, JMAP mail, Micropub,
236+
remoteStorage, and more, all on one server from pure config. Each plugin's
237+
`README` records the seam it needed and the wall it hit — the findings that
238+
drove `serverInfo`, `reservePath`, and `api.plugins` upstream. The two games
239+
remain the original forcing consumers, live at
201240
[nostr.social/tideholm](https://nostr.social/tideholm/) and
202241
[nostr.social/bridge](https://nostr.social/bridge/):
203242

@@ -232,19 +271,22 @@ Tideholm player and a bridge seat with a single sign-in.
232271
From [#206](https://github.com/JavaScriptSolidServer/JavaScriptSolidServer/issues/206)
233272
/ [#564](https://github.com/JavaScriptSolidServer/JavaScriptSolidServer/issues/564):
234273

235-
1. **A `plugins` block in the CLI config file** — the programmatic option
236-
shipped; a config-file form makes "install an app = edit config" real
237-
for non-programmatic deployments.
238-
2. **Migrate the bundled features** onto the loader (#564) — the relay,
274+
1. **Migrate the bundled features** onto the loader (#564) — the relay,
239275
ActivityPub, git, pay and friends become battle-tested consumers, one at
240276
a time.
241-
3. **Richer seams as consumers demand them**`api.mountApp` (#583),
242-
`registerMcpTool`, `registerPane`, with the
277+
2. **The next demanded seams**
278+
[`api.events.onResourceChange`](https://github.com/JavaScriptSolidServer/JavaScriptSolidServer/issues/603)
279+
(react to pod writes; seven consumers waiting) and
280+
[`api.authorize`](https://github.com/JavaScriptSolidServer/JavaScriptSolidServer/issues/604)
281+
(ask WAC about authority the caller doesn't drive), then `registerMcpTool`
282+
and `registerPane`, with the
243283
[pane store](https://github.com/JavaScriptSolidServer/JavaScriptSolidServer/issues/184)
244284
as the eventual marketplace layer.
285+
3. **Publish the plugin suite as a package** so
286+
`--plugin @jss/plugins/dashboard@/dashboard` loads by name, no clone.
245287

246288
The pattern for contributing a seam is established: build a real thing
247289
against JSS, hit a wall, file the smallest issue that removes it, prove it
248-
with your consumer. Three seams (`appPaths`, `getAgent`, `ws.route`) and
249-
the loader itself each went from idea to npm in about a day this way — the
250-
door is open.
290+
with your consumer. Seven seams (`appPaths`, `getAgent`, `ws.route`,
291+
`mountApp`, `serverInfo`, `reservePath`, `api.plugins`) and the loader
292+
itself each went from idea to npm this way — the door is open.

0 commit comments

Comments
 (0)