{
  "schema": "leumas.docs.page/1",
  "id": "pkg:@leumas/api-kit",
  "slug": "packages/api-kit",
  "kind": "capabilities",
  "bucket": "package",
  "title": "@leumas/api-kit",
  "name": "@leumas/api-kit",
  "eyebrow": null,
  "chip": null,
  "summary": "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...",
  "keywords": [
    "api-kit",
    "controllers",
    "async",
    "boilerplate",
    "export replaces",
    "api kit api",
    "leumas api kit",
    "thin"
  ],
  "audience": "both",
  "funnel": {
    "product": null,
    "cta": null
  },
  "body": "# @leumas/api-kit\n\nThe express boilerplate every Leumas router was writing for itself. No domain logic, no express\nimport — it sits under both halves of the routes/controllers split.\n\n```js\n// a router\nimport { wrap, optionalGuard, meter } from '@leumas/api-kit';\n\nconst auth = optionalGuard(requireAuth);\nconst aiGuard = meter(passnode, 'feature:graphs.ai');\n\nrouter.post('/:id/statements', wrap(async (req, res) => {\n  const { texts, text, source, aiMeta } = req.body || {};\n  res.json(await ctl.addStatements({ id: req.params.id, texts, text, source, aiMeta }, { user: req.user }));\n}));\n```\n\n```js\n// a controller — no req, no res, callable from an action or a workflow node\nimport { httpError, requireOwned } from '@leumas/api-kit';\n\nasync function addStatements({ id, texts }, { user }) {\n  const doc = await requireOwned((i) => conn.getById('reasoning_graphs', i), { user, id, kind: 'graph' });\n  if (doc.kind === 'imported') throw httpError(400, 'imported graphs have no statements');\n  ...\n}\n```\n\n## What each export replaces\n\n| Export | Was |\n|---|---|\n| `wrap(fn)` | 29 copies of `const wrap = (fn) => …`, in three incompatible bodies |\n| `httpError(status, msg, extra?)` / `assertHttp` | ~50 hand-written `Object.assign(new Error(m), { status })`, plus two private copies of this helper |\n| `isAdmin(user)` | 8 module-level definitions + 6 inlinings of `req.user?.isAdmin === true` |\n| `owns(row, ownerId)` | `own()` in `products/leumas-api/src/app.js` — **unowned rows are shared** |\n| `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 |\n| `optionalGuard(g)` | 14 copies of `requireAuth \\|\\| ((req,res,next) => next())` |\n| `meter(passnode, feature)` | 12 copies of the `passnode?.guard ? … : next` block |\n| `ownerStore(conn, coll)` / `ownerStores(conn)` | `collStore()` in `app.js` |\n| `paginate(query, opts)` | promoted from `router-kit/src/social-lib.js` |\n\n`owns` and `requireOwned` disagree about an unowned row **on purpose** — see the comment at the top\nof `src/access.js`. Merging them is a security change, not a cleanup.\n\n## Rules\n\n- Controllers may not import express and may not mention `req`/`res`. Enforced by\n  `pnpm check:controllers`.\n- The route table and its ordering may not drift. Enforced by `pnpm check:routes` against\n  `ops/infra/baselines/api-routes.json`.\n- There is no error *class*. `instanceof` across a pnpm workspace is a trap and nothing needs one —\n  every consumer reads `.status`, which `errlog.finalHandler()` already does.\n",
  "source": {
    "path": "shared/packages/api-kit/README.md",
    "blobSha": "",
    "commit": "",
    "committedAt": "",
    "provenance": "no-git",
    "bytes": 2794,
    "hash": "a570fd3425f811845fc4fd74dce9b7ba6c0ae6ea"
  },
  "urls": {
    "html": "/p/packages/api-kit",
    "json": "/docs/packages/api-kit.json",
    "md": "/docs/packages/api-kit.md"
  },
  "links": {
    "composes": [],
    "usedBy": [
      "pkg:@leumas/body",
      "pkg:@leumas/dynamic",
      "pkg:@leumas/jobs",
      "pkg:@leumas/middleware",
      "pkg:@leumas/router-kit",
      "pkg:@leumas/tv",
      "pkg:@leumas/variables",
      "pkg:@leumas/webhooks"
    ],
    "product": [
      "pkg:@leumas/studio"
    ],
    "howTo": [],
    "skills": []
  },
  "exports": {
    "total": 30,
    "component": 1,
    "hook": 0,
    "helper": 29,
    "names": [
      {
        "n": "DEFAULT_CONCURRENCY",
        "k": "component"
      },
      {
        "n": "assertHttp",
        "k": "helper"
      },
      {
        "n": "assertHttp",
        "k": "helper"
      },
      {
        "n": "asString",
        "k": "helper"
      },
      {
        "n": "copyName",
        "k": "helper"
      },
      {
        "n": "httpError",
        "k": "helper"
      },
      {
        "n": "httpError",
        "k": "helper"
      },
      {
        "n": "isAdmin",
        "k": "helper"
      },
      {
        "n": "isAdmin",
        "k": "helper"
      },
      {
        "n": "mapPool",
        "k": "helper"
      },
      {
        "n": "meter",
        "k": "helper"
      },
      {
        "n": "meter",
        "k": "helper"
      },
      {
        "n": "optionalGuard",
        "k": "helper"
      },
      {
        "n": "optionalGuard",
        "k": "helper"
      },
      {
        "n": "optionalMeter",
        "k": "helper"
      },
      {
        "n": "optionalMeter",
        "k": "helper"
      },
      {
        "n": "ownerStore",
        "k": "helper"
      },
      {
        "n": "ownerStore",
        "k": "helper"
      },
      {
        "n": "ownerStores",
        "k": "helper"
      },
      {
        "n": "ownerStores",
        "k": "helper"
      },
      {
        "n": "owns",
        "k": "helper"
      },
      {
        "n": "owns",
        "k": "helper"
      },
      {
        "n": "paginate",
        "k": "helper"
      },
      {
        "n": "paginate",
        "k": "helper"
      },
      {
        "n": "parseBool",
        "k": "helper"
      },
      {
        "n": "requireOwned",
        "k": "helper"
      },
      {
        "n": "requireOwned",
        "k": "helper"
      },
      {
        "n": "slugify",
        "k": "helper"
      },
      {
        "n": "wrap",
        "k": "helper"
      },
      {
        "n": "wrap",
        "k": "helper"
      }
    ]
  }
}
