Skip to content

feat(endpoint-microsub): PR 1 - Core Microsub server with channels and timeline - #944

Open
rmdes wants to merge 9 commits into
feat/microsubfrom
microsub/pr1-core-channels-timeline
Open

rmdes wants to merge 9 commits into
feat/microsubfrom
microsub/pr1-core-channels-timeline

Conversation

@rmdes

@rmdes rmdes commented Sep 5, 2026

Copy link
Copy Markdown
Collaborator

Part one of seven: the core Microsub server — channels and the timeline. Scoped deliberately small so it is reviewable; the remaining six are written and follow behind it.

+3511 / −0 across 32 files, all new. No existing behaviour is touched.

What it adds

lib/controllers/microsub.js    action dispatch for the Microsub endpoint
lib/controllers/channels.js    channels: list, create, update, delete, order
lib/controllers/timeline.js    timeline reads, marking read/unread, removal
lib/storage/channels.js        channel persistence
lib/storage/items.js           item persistence, using getObjectId from @indiekit/util
lib/utils/auth.js              token verification and scope checks
lib/utils/pagination.js        cursor-based paging
lib/utils/uid.js               channel and item identifiers
lib/utils/validation.js        request validation
locales/en.json                strings

17 test files cover them.

Verification

Green: 895 tests, 894 pass, 0 fail on the full monorepo suite, endpoint-microsub included.

Worth stating plainly because it was not true until today. This is the successor to #829, which had carried a red check since February for two reasons, neither in this code:

Both are now cleared — this branches from this repo, and feat/microsub carries #914's fix as of e4c2821, byte-identical to main. So the six parts that follow will not inherit a red check either.

One open question

Should this target main rather than feat/microsub? I asked on #829 in August and it got lost in the write-access thread. feat/microsub is 32 commits behind main and, apart from that one backported fix, has no commits of its own — so if all seven parts land there it will keep drifting. Happy either way; it just needs deciding once rather than seven times.

Notes for review

  • @indiekit/util's getObjectId is used throughout rather than the plug-in declaring mongodb itself, per your note on feat(endpoint-microsub): PR 1 - Core Microsub server with channels and timeline #829 — there is no mongodb dependency here, so nothing to drift when the host's version moves.
  • Running the suite locally needs PASSWORD_SECRET set alongside SECRET, as build.yml does. Without it the session-backed tests redirect rather than fail cleanly, which is not obvious from the output.

rmdes and others added 9 commits August 22, 2026 14:07
…imeline

This PR adds the foundational Microsub endpoint with:

**Microsub API:**
- GET/POST ?action=channels - list, create, update, delete, reorder channels
- GET/POST ?action=timeline - list items, mark read/unread, remove

**Storage:**
- MongoDB collections for channels and items
- Cursor-based pagination for timeline
- Per-user channel ordering and read state tracking

**Features:**
- Follows Microsub spec for channel and timeline actions
- Testable with existing Microsub clients (Monocle, Indigenous, etc.)
- Multi-user support via userId from session/token

This is PR 1 of 6 for the Microsub implementation. Future PRs will add:
- PR 2: Feed discovery and subscription
- PR 3: Feed fetching and parsing
- PR 4: Reader UI
- PR 5: Compose and Micropub integration
- PR 6: Settings and filtering

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The plug-in declared mongodb ^6.0.0 while indiekit declares ^7.4.0, so npm
installed a nested copy of the driver. ObjectId values created by the plug-in
came from bson 6 but were passed to collections served by bson 7, which threw
BSONVersionError in markItemsRead, markItemsUnread and removeItems.
Fixes unicorn/prefer-await, unicorn/prefer-number-coercion,
unicorn/consistent-boolean-name, unicorn/no-computed-property-existence-check
and jsdoc/reject-function-type, and removes unused eslint-disable directives.

Satisfying unicorn/prefer-await means init() now awaits index creation rather
than leaving it to run in the background, so plug-in initialisation waits for
indexes to be created. Errors are still caught and warned about, and the plug-in
loader already awaits init().
Unit tests cover lib/utils and lib/storage, mirroring the structure of lib/.
Controllers are covered by integration tests, as in other endpoint plug-ins.
Replaces the direct mongodb import with @indiekit/util's getObjectId, as
suggested in review. The plug-in no longer declares mongodb at all, so its
driver version can't drift from the host's — @indiekit/util owns that pin.

This supersedes the earlier version bump, which fixed the same mismatch by
matching the pin by hand and would have needed maintaining.
`generateChannelUid` built its own string from `Math.random()`. `randomString`
from `@indiekit/util` does the same job with `randomBytes`, which is what a
channel identifier should be using.

That changes the alphabet from `[a-z0-9]` to base64url, so the tests asserting
lowercase now assert URL-safe characters instead — that was the actual
requirement, since a uid appears in Microsub request URLs.

Replaces the one `console.info` in the package with `debug`, matching
endpoint-micropub and endpoint-media, and declares the dependency.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WGHR7MuyvBaDbAFfAGUxeT
@rmdes

rmdes commented Sep 5, 2026

Copy link
Copy Markdown
Collaborator Author

Correcting my comment I had the shape of this right but missed the history, which matters for how it gets fixed.

feat/microsub is at ba8e19a7, which is the commit that introduced the Prettier failure (#910: "has failed on packages/endpoint-share/README.md since ba8e19a, when the rel="share-url" example was added"). So the branch was pinned at the break and had received neither fix.

More importantly, the fix on main is not "run Prettier". #910 did that and you pushed back:

We should stick to one line and avoid the non-self-closing tag. Prettier's HTML linting is a bit suspect, and we disable it in .prettierignore … Let's use <!-- prettier-ignore --> instead.

#914 then did it your way. The branch already had the one-line, non-self-closing form you asked for — all it lacked was the marker, so anyone clearing this with prettier --write would have reintroduced the split, self-closing version you rejected.

Now fixed on feat/microsub in 2d7579e, which takes main's version of the file wholesale — a one-line addition, byte-identical to main. Not a reformat.

That said, the retarget question still stands on its own merits: feat/microsub is 32 commits behind main with no commits of its own beyond that one, so if the seven Microsub parts are going to land there it will keep drifting. Happy either way — just say which.

@rmdes rmdes closed this Sep 5, 2026
@rmdes rmdes reopened this Sep 5, 2026
@rmdes rmdes closed this Sep 5, 2026
@rmdes rmdes reopened this Sep 5, 2026
@rmdes

rmdes commented Sep 5, 2026

Copy link
Copy Markdown
Collaborator Author

Finally !! damned this was convoluted to make it work but now we're good to go !

@rmdes rmdes added the plugin-endpoint Endpoint plug-in label Sep 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

plugin-endpoint Endpoint plug-in

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants