Skip to content

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

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

feat(endpoint-microsub): PR 1 - Core Microsub server with channels and timeline#829
rmdes wants to merge 6 commits into
getindiekit:feat/microsubfrom
rmdes:microsub/pr1-core-channels-timeline

Conversation

@rmdes

@rmdes rmdes commented Feb 1, 2026

Copy link
Copy Markdown
Contributor

Summary

This PR adds the foundational Microsub endpoint with channel and timeline management.

What's included

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

How to test

  1. Enable the plugin in your Indiekit config
  2. Use a Microsub client like Monocle pointed at your /microsub endpoint
  3. Or test via curl:
# List channels
curl -H "Authorization: Bearer YOUR_TOKEN" \
  "https://your-site.com/microsub?action=channels"

# Create a channel
curl -X POST -H "Authorization: Bearer YOUR_TOKEN" \
  -d "action=channels&name=Tech News" \
  "https://your-site.com/microsub"

# Get timeline (will be empty until feed fetching is added in PR 3)
curl -H "Authorization: Bearer YOUR_TOKEN" \
  "https://your-site.com/microsub?action=timeline&channel=CHANNEL_UID"

PR breakdown

This is PR 1 of 6 for the Microsub implementation:

  • PR 1 (this): Core + Channels + Timeline API ← functional Microsub server
  • 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

@paulrobertlloyd
paulrobertlloyd force-pushed the microsub/pr1-core-channels-timeline branch from c50cd1e to ee6ae94 Compare July 4, 2026 14:28
@paulrobertlloyd
paulrobertlloyd force-pushed the microsub/pr1-core-channels-timeline branch from ee6ae94 to ae01604 Compare July 4, 2026 14:39
@paulrobertlloyd

paulrobertlloyd commented Jul 4, 2026

Copy link
Copy Markdown
Collaborator

Hi @rmdes. Firstly, sorry it’s taken me so long to get back to you on this; life got in the way. I should hopefully now have some time to review/collaborate on this feature.

I’ve pushed two extra commits:

  1. Added the plug-in to the development config so that when running the application locally the plugin is available; once the plugin is completed, we can possibly make it a plugin that ships by default with Indiekit, and then not need to add it here.
  2. Added an icon for the plug-in, using the icon for Microsub.

Before merging this into main, might we be able to do the following:

  1. Add unit and integration tests (see other endpoints for examples)
  2. Run and fix issues reported by npm run lint (I recently updated eslint’s rules so there are a few new errors)

Thanks again for your patience, looking forward to getting this contribution into Indiekit!

@paulrobertlloyd
paulrobertlloyd force-pushed the microsub/pr1-core-channels-timeline branch 2 times, most recently from d04ba60 to 8dfa381 Compare July 4, 2026 15:35
rmdes and others added 3 commits July 4, 2026 16:40
…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>
@paulrobertlloyd
paulrobertlloyd force-pushed the microsub/pr1-core-channels-timeline branch from 8dfa381 to 5e0b783 Compare July 4, 2026 15:42
rmdes added 3 commits August 15, 2026 13:31
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.
@rmdes

rmdes commented Aug 15, 2026

Copy link
Copy Markdown
Contributor Author

Thanks @paulrobertlloyd, and no problem about the delay. I’ve pushed three commits covering both points, plus one fix that writing the tests uncovered.

Two things are worth calling out, as neither is obvious from the diff.

mongodb version mismatch (d6499142)

The plug-in declared mongodb: ^6.0.0 while @indiekit/indiekit declares ^7.4.0. npm hoisted 7.4.0 to the root and installed a nested 6.21.0 under packages/endpoint-microsub/node_modules, so lib/storage/items.js resolved its ObjectId import to bson 6 while the collections it operates on are served by the host’s bson 7 driver.

Any query carrying a plug-in-created ObjectId then failed to serialise:

BSONVersionError: Unsupported BSON version, bson types must be from bson 7.x.x

This affected markItemsRead, markItemsUnread and removeItems. All three build ObjectIds from the entry IDs in the request:

const objectIds = entryIds
  .map((id) => {
    try {
      return new ObjectId(id);
    } catch {
      return;
    }
  })
  .filter(Boolean);

It matters in practice because transformToJf2 exposes _id to clients as a string, so a Microsub client marking items as read sends those IDs straight back and lands on this path. Construction succeeds; the failure happens at serialisation when the query is sent.

The fix is just to declare the same major as the host, which removes the nested copy (package-lock.json shrinks accordingly). Happy to drop the dependency entirely instead if you’d rather the plug-in never imported mongodb directly — ObjectId is the only thing it uses.

init() now awaits index creation (3d5f66c6)

This one is a genuine behaviour change rather than a pure lint fix, so flagging it explicitly.

Index creation was deliberately fire-and-forget:

// Create indexes for optimal performance (runs in background)
if (indiekit.database) {
  createIndexes(indiekit).catch((error) => {
    console.warn("[Microsub] Index creation failed:", error.message);
  });
}

unicorn/prefer-await flags the .catch() chain, and I couldn’t satisfy it without awaiting, so init() is now async and initialisation waits for the indexes:

if (indiekit.database) {
  try {
    await createIndexes(indiekit);
  } catch (error) {
    console.warn("[Microsub] Index creation failed:", error.message);
  }
}

The plug-in loader already does await plugin.init(Indiekit) (packages/indiekit/lib/plugins.js:22), so this is within the existing contract, and the try/catch keeps the original “warn, don’t crash” behaviour if the database is unreachable. It also removes a race where timeline queries could run before the indexes existed. The trade-off is that start-up now blocks on index creation. If you’d prefer to keep it in the background, an inline disable with a comment would be the alternative — your call.

Tests

Unit tests cover lib/utils and lib/storage, in a directory structure mirroring lib/ (as packages/indiekit/test/unit/middleware does). Controllers are covered by the integration tests instead, matching endpoint-auth, endpoint-media and endpoint-micropub. 135 tests in total, and npm run lint is clean for the package.

One note on the failing check: it’s the Localazy step failing on a missing readKey, which can’t pass on a pull request from a fork since secrets aren’t exposed. I don’t think there’s anything I can do about that from here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants