@leumas/api-kit
The express boilerplate every Leumas router was writing for itself — async wrapping, HTTP-status errors, the admin/ownership predicates, the optional-guard and PassNode-meter seams, pagination and...
@leumas/api-kit
The express boilerplate every Leumas router was writing for itself. No domain logic, no express import — it sits under both halves of the routes/controllers split.
// a router
import { wrap, optionalGuard, meter } from '@leumas/api-kit';
const auth = optionalGuard(requireAuth);
const aiGuard = meter(passnode, 'feature:graphs.ai');
router.post('/:id/statements', wrap(async (req, res) => {
const { texts, text, source, aiMeta } = req.body || {};
res.json(await ctl.addStatements({ id: req.params.id, texts, text, source, aiMeta }, { user: req.user }));
}));
// a controller — no req, no res, callable from an action or a workflow node
import { httpError, requireOwned } from '@leumas/api-kit';
async function addStatements({ id, texts }, { user }) {
const doc = await requireOwned((i) => conn.getById('reasoning_graphs', i), { user, id, kind: 'graph' });
if (doc.kind === 'imported') throw httpError(400, 'imported graphs have no statements');
...
}
What each export replaces
| Export | Was | ||
|---|---|---|---|
wrap(fn) | 29 copies of const wrap = (fn) => …, in three incompatible bodies | ||
httpError(status, msg, extra?) / assertHttp | ~50 hand-written Object.assign(new Error(m), { status }), plus two private copies of this helper | ||
isAdmin(user) | 8 module-level definitions + 6 inlinings of req.user?.isAdmin === true | ||
owns(row, ownerId) | own() in products/leumas-api/src/app.js — unowned rows are shared | ||
requireOwned(load, { user, id, kind }) | the 6 ownGraph/ownScene/ownMap/ownStation/ownItem/ownOutfit helpers — unowned rows belong to nobody; throws instead of writing the response | ||
optionalGuard(g) | 14 copies of `requireAuth \ | \ | ((req,res,next) => next())` |
meter(passnode, feature) | 12 copies of the passnode?.guard ? … : next block | ||
ownerStore(conn, coll) / ownerStores(conn) | collStore() in app.js | ||
paginate(query, opts) | promoted from router-kit/src/social-lib.js |
owns and requireOwned disagree about an unowned row on purpose — see the comment at the top of src/access.js. Merging them is a security change, not a cleanup.
Rules
- Controllers may not import express and may not mention
req/res. Enforced by
pnpm check:controllers.
- The route table and its ordering may not drift. Enforced by
pnpm check:routesagainst
ops/infra/baselines/api-routes.json.
- There is no error class.
instanceofacross a pnpm workspace is a trap and nothing needs one —
every consumer reads .status, which errlog.finalHandler() already does.
What it exports
30 exports: 1 components, 0 hooks, 29 helpers. Import from @leumas/api-kit.
DEFAULT_CONCURRENCYassertHttpassertHttpasStringcopyNamehttpErrorhttpErrorisAdminisAdminmapPoolmetermeteroptionalGuardoptionalGuardoptionalMeteroptionalMeterownerStoreownerStoreownerStoresownerStoresownsownspaginatepaginateparseBoolrequireOwnedrequireOwnedslugifywrapwrap