mikser-io 9.0.1 → 9.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/CLAUDE.md +50 -5
  2. package/README.md +28 -24
  3. package/docs/api-reference.md +701 -0
  4. package/docs/architecture.md +357 -0
  5. package/docs/caching.md +233 -0
  6. package/docs/configuration.md +423 -0
  7. package/docs/decisions/0001-content-layer-not-the-app.md +61 -0
  8. package/docs/decisions/0002-files-as-source-of-truth.md +64 -0
  9. package/docs/decisions/0003-plugins-independent-engine-stable.md +64 -0
  10. package/docs/decisions/0004-compose-via-protocols.md +71 -0
  11. package/docs/decisions/0005-engine-infrastructure-runs-before-plugin-hooks.md +94 -0
  12. package/docs/decisions/0006-when-to-add-to-core.md +98 -0
  13. package/docs/decisions/0007-references-declaration-and-expansion.md +349 -0
  14. package/docs/decisions/0009-database-engine-substrate.md +252 -0
  15. package/docs/decisions/0010-plugin-bundles-and-inline-options.md +226 -0
  16. package/docs/decisions/0011-served-entities-expose-deployed-urls.md +306 -0
  17. package/docs/decisions/README.md +39 -0
  18. package/docs/entities.md +364 -0
  19. package/docs/getting-started.md +294 -0
  20. package/docs/lifecycle.md +417 -0
  21. package/docs/overview.md +224 -0
  22. package/docs/plugins.md +922 -0
  23. package/docs/rendering.md +557 -0
  24. package/docs/watch-mode.md +266 -0
  25. package/index.js +1 -0
  26. package/package.json +1 -1
  27. package/src/catalog.js +11 -4
  28. package/src/database/sift-to-sql.js +1 -0
  29. package/src/plugins/api.js +17 -13
  30. package/src/plugins/assets.js +29 -0
  31. package/src/plugins/files.js +7 -0
  32. package/src/plugins/preview.js +13 -8
  33. package/src/plugins/resources.js +8 -2
  34. package/src/render.js +66 -1
  35. package/src/routes.js +124 -0
  36. package/src/server.js +24 -10
  37. package/src/utils.js +40 -0
package/CLAUDE.md CHANGED
@@ -175,6 +175,19 @@ brevity.
175
175
  Strings produce a v9 migration error pointing at the new shape.
176
176
  - `manager.js` — file watching (chokidar) and cron scheduling.
177
177
  - `source.js` — `useSource` codifies the folder-of-files pattern.
178
+ - `routes.js` — HTTP route registry. Plugins mount on
179
+ `runtime.options.app` directly; the Express router stack has the
180
+ paths but not the intent (loopback-only? streaming?). So plugins
181
+ declare each mount via `registerRoute({ path, plugin, reachability,
182
+ streaming })` as they make it. `reachability` is `public` | `token`
183
+ | `loopback`; `streaming` flags SSE/WS routes a facade must not
184
+ buffer. Inventory lives at `runtime.routes`; consumers (a Caddy/nginx
185
+ facade generator, healthcheck list, `mikser://routes` resource) read
186
+ it — none baked in. `registerRoute` also folds in the origin/location
187
+ URL building and the standard `"<label> mounted: <loc>
188
+ [<reachability>]"` boot log that api/preview/mcp/vector/forms/decap
189
+ were each copy-pasting. Pure inventory — takes no position on what to
190
+ do with the routes.
178
191
  - `constants.js` — `OPERATION` (CREATE/UPDATE/DELETE/RENDER/
179
192
  POSTPROCESS), `ACTION` (sync action types), `TASKS` (`INLINE`/
180
193
  `SERIAL`/`WORKER` — dispatch modes).
@@ -199,7 +212,7 @@ substrate.
199
212
  - **`mikser-io-render-{hbs,liquid,eta,markdown,metatext}`** — renderer
200
213
  plugins. `renderHbs` is bundled in core; the rest are siblings.
201
214
  - **`mikser-io-post-{mjml,pdf,email}`** — postprocessor plugins,
202
- composable in chains (see `documentation/rendering.md#postprocess`).
215
+ composable in chains (see `docs/rendering.md#postprocess`).
203
216
  - **`mikser-io-mcp`** — AI tooling surface (ADR-0006 test #5).
204
217
  - **`mikser-io-vector`** — vector index over the catalog.
205
218
  - **`mikser-io-forms`** — HTTP form receivers → entities.
@@ -291,6 +304,18 @@ Test coverage: `test/unit/source-sweep.test.js`.
291
304
  `runtime.options.port` is the internal listener port (kept for
292
305
  loopback/dev URL building); `runtime.options.url` is the external
293
306
  origin. When in doubt: external = `url`, internal = `port`.
307
+ - **Trust proxy default is `'loopback'`** (server.js), not Express's
308
+ `false`. The dominant deployment puts a same-host reverse proxy in
309
+ front, where every socket peer is 127.0.0.1; with no trust, mikser
310
+ reads every proxied request as loopback and the loopback-only gate
311
+ (api/mcp/forms/decap enforce `isLoopback(req.ip)`) inverts the
312
+ moment a facade is added. `'loopback'` honors `X-Forwarded-For` only
313
+ from a loopback peer — safe everywhere (a remote attacker's kernel-
314
+ set peer is never 127.0.0.1, so they can't forge a loopback
315
+ `req.ip`). Override via `config.server.trustProxy` — `'uniquelocal'`
316
+ for a sibling-container proxy, a CIDR for a specific subnet, `false`
317
+ to opt out. The loopback-enforcement and this default are a pair; a
318
+ facade that injects `X-Forwarded-For` (Caddy/nginx do) makes it work.
294
319
  - **Engine functions** as module-level exports from `mikser-io`:
295
320
  `import { queryEntities, subscribe, useRenderer, useCollection,
296
321
  readEntityContent, isTextEntity } from 'mikser-io'`.
@@ -364,6 +389,26 @@ Test coverage: `test/unit/source-sweep.test.js`.
364
389
  plugin config moved entirely off `runtime.config.<plugin>`; it
365
390
  arrives as factory args, gets stashed on the descriptor, and is
366
391
  passed as `config` to `load`/`render`/`setup`/`postprocess`.
392
+ - **0011** (implemented; proven against gpoint) — File and resource
393
+ entities expose deployed URLs. References to served files
394
+ (image/video/PDF) are `$`-keyed **served paths** (`/img/X.jpg`,
395
+ `/media/clip.mp4` — the path content authors, = the entity's
396
+ `meta.url`), resolving via a new `refFilter` `{ 'meta.url': … }`
397
+ clause (utils.js) backed by an indexed `meta_url` column
398
+ (catalog.js + the `INDEXED_COLUMNS` map in sift-to-sql.js; schema
399
+ 9.0.1 → 9.0.2). No collection-prefixed ids leak into content.
400
+ Id-refs were tried and rejected — gpoint references content by
401
+ served path everywhere, and its `/media/**` `resources()` library
402
+ means the entity only exists *because* content references
403
+ `/media/…`. The `files` plugin stamps `meta.url`; the `resources`
404
+ plugin stamps its library's served location; the `assets` plugin
405
+ stamps `meta.presets` in `onProcessed` via the pure `presetUrl()`
406
+ helper. Expanding a ref then yields the served entity's URL set,
407
+ not a string to reconstruct; `lookupUrl` (render.js) is the sync
408
+ Handlebars helper resolving a ref to `meta.url` or a named preset.
409
+ Base-relative in the live catalog, absolute in static renders
410
+ (from `runtime.options.url`). Umbrella term is *served entity*
411
+ (file/resource/preset).
367
412
 
368
413
  ## MCP
369
414
 
@@ -528,11 +573,11 @@ the consumer's own project tree — no duplication, no bug.
528
573
 
529
574
  ## Reference
530
575
 
531
- - `documentation/architecture.md` — module map (audit before relying
576
+ - `docs/architecture.md` — module map (audit before relying
532
577
  on specifics; may have drift)
533
- - `documentation/decisions/` — ADRs
534
- - `documentation/configuration.md` — config reference
535
- - `documentation/api-reference.md` — public API
578
+ - `docs/decisions/` — ADRs
579
+ - `docs/configuration.md` — config reference
580
+ - `docs/api-reference.md` — public API
536
581
  - `test/perf/` — render-pipeline perf rig
537
582
  - Sibling repos: `mikser-io-layouts` (canonical SSG-flavor render-task
538
583
  production policy), `mikser-io-mcp`, `mikser-io-vector`,
package/README.md CHANGED
@@ -2,17 +2,21 @@
2
2
  <img src="mikser-lockup-stacked.svg" alt="mikser" width="198" />
3
3
  </p>
4
4
 
5
+ <p align="center"><strong>The AI-native content engine.</strong></p>
6
+
5
7
  # Mikser
6
8
 
7
- **Mikser is the content layer of your application.** Business logic, user accounts, transactions live in their own services; mikser handles the parts that *are* content pages, docs, the published catalog, multi-format outputs. [Vue](https://github.com/almero-digital-marketing/mikser-io-sdk-vue), [React](https://github.com/almero-digital-marketing/mikser-io-sdk-react), and [Svelte](https://github.com/almero-digital-marketing/mikser-io-sdk-svelte) SDKs are the seam between them same surface across all three (`useDocument`, `useDocuments`, multilingual `useHref`, live SSE updates), each in its framework's idiomatic shape.
9
+ **Mikser is the AI-native content engine.** It mixes content from anywhere — markdown files, Google Sheets, your ERP, a CMS, any API into one live catalog, links it together with references, and ships it to any frontend. Edit a price in your ERP or a cell in a spreadsheet, and every page that uses it updates within seconds in whatever framework you built the site with.
10
+
11
+ **Framework-agnostic on both ends.** Headless CMSes (Sanity, Contentful) free you from the database but lock authoring into their UI. Frameworks like Astro let you bring any frontend but lock you into the framework. Mikser frees both: **any source in, any frontend out.** Provider plugins turn external systems into content sources; the catalog is served over plain HTTP, with idiomatic [Vue](https://github.com/almero-digital-marketing/mikser-io-sdk-vue) / [React](https://github.com/almero-digital-marketing/mikser-io-sdk-react) / [Svelte](https://github.com/almero-digital-marketing/mikser-io-sdk-svelte) SDKs on top (`useDocument`, `useDocuments`, multilingual `useHref`, live SSE). It's also AI-native: agents read and write the same catalog over MCP using the same calls a frontend developer uses — there's no separate "AI API" to keep in sync.
8
12
 
9
- **An AI-native file-based content engine with live graph queries.** Content lives as plain text files you can read, search, and version-control like code. Mikser knows how those files reference each other — who an article's author is, which images a landing page uses, who else mentions a given product. When you fetch an article, you can pull its author and that author's organization along with it in one round-trip. When the author's bio changes, every page subscribed to a reference touching them updates live, without polling. AI agents talk to mikser through the same calls a frontend developer uses — there's no separate "AI API" to keep in sync. The whole graph is queryable from both sides at once.
13
+ **References merge sources into one call.** Mikser tracks how entities reference each other — an article's author, the marketing copy and ERP price behind a product page, the images a landing page uses. Fetch the product and pull its marketing copy, price, and hero image along with it in one round-trip. Change any of them and every page subscribed to a reference touching it re-renders live, without polling and mikser invalidates *exactly* the pages that depend on it, nothing more.
10
14
 
11
- Built for Node.js around a strict lifecycle and a composable plugin system. Every document, asset, and template flows through the same pipeline; plugins hook in at any phase. The same engine runs a single markdown blog and a multi-language publishing platform with PDF / email / AI-augmented asset pipelinessame lifecycle, more plugins.
15
+ **Your content stays yours.** Source files live on disk as `.md` / `.yml` — diffable, version-controllable, portable on day one and year ten. No database lock-in, no proprietary export. And it's the content layer, not your whole backend: business logic, accounts, and transactions stay in their own services; mikser handles the part that's actually content rendered to HTML, PDF, email, and other formats from the same source.
12
16
 
13
- It's MIT-licensed, runs on Node, has zero hosted dependencies, and the entire content tree it manages is a folder of `.md` and `.yml` files you can copy, diff, and version-control. **The portability promise is the architecture, not a feature.**
17
+ Built for Node.js around a strict lifecycle and a composable plugin system: every document, asset, and template flows through the same pipeline, and plugins hook in at any phase. MIT-licensed, runs on Node, zero hosted dependencies. **The portability promise is the architecture, not a feature.**
14
18
 
15
- > **New to mikser?** Read the [Architecture Overview](./documentation/overview.md) — one document, end-to-end walkthrough of how a file becomes a deployed page across all twenty lifecycle phases. It's the doc most projects need first.
19
+ > **New to mikser?** Read the [Architecture Overview](./docs/overview.md) — one document, end-to-end walkthrough of how a file becomes a deployed page across all twenty lifecycle phases. It's the doc most projects need first.
16
20
 
17
21
  ## Where it fits
18
22
 
@@ -163,7 +167,7 @@ The engine is what stays stable — the lifecycle, the catalog, the file-based c
163
167
  | Plugin | What it does |
164
168
  |---|---|
165
169
  | `data` | JSON snapshots of entities / context / catalog, written to disk for static serving |
166
- | `api` | REST endpoints with sift-backed queries, per-endpoint tokens, optional render, opt-in [per-query disk cache](./documentation/caching.md) for reverse-proxy failover |
170
+ | `api` | REST endpoints with sift-backed queries, per-endpoint tokens, optional render, opt-in [per-query disk cache](./docs/caching.md) for reverse-proxy failover |
167
171
  | `preview` | In-memory render cache + `GET /preview/:filename` route. Companion to the `mikser_preview_render` MCP tool (in [`mikser-io-mcp`](https://github.com/almero-digital-marketing/mikser-io-mcp)) — transient render bytes served at a clickable URL, no filesystem footprint |
168
172
 
169
173
  **Integrations:**
@@ -171,7 +175,7 @@ The engine is what stays stable — the lifecycle, the catalog, the file-based c
171
175
  | Plugin | What it does |
172
176
  |---|---|
173
177
  | [`mikser-io-vector`](https://github.com/almero-digital-marketing/mikser-io-vector) | OpenAI embeddings + semantic search (sqlite-vec or pgvector) |
174
- | [`mikser-io-schemas`](https://github.com/almero-digital-marketing/mikser-io-schemas) | Zod-backed entity validation + auto-generated TypeScript declarations for the SDK. Auto-detects `$`-keyed references and warns on broken ones — see [ADR-0007](./documentation/decisions/0007-references-declaration-and-expansion.md) |
178
+ | [`mikser-io-schemas`](https://github.com/almero-digital-marketing/mikser-io-schemas) | Zod-backed entity validation + auto-generated TypeScript declarations for the SDK. Auto-detects `$`-keyed references and warns on broken ones — see [ADR-0007](./docs/decisions/0007-references-declaration-and-expansion.md) |
175
179
  | [`mikser-io-forms`](https://github.com/almero-digital-marketing/mikser-io-forms) | Public form-submission endpoints. POST → captcha + schema validation → write a document file plus uploaded files to disk; the `documents` / `files` plugins pick them up via their normal watch loop. Composes with `mikser-io-schemas` for schema-by-name; built-in captcha providers (Google v2/v3, hCaptcha, Turnstile) plus a custom-verify escape hatch |
176
180
  | [`mikser-io-archive`](https://github.com/almero-digital-marketing/mikser-io-archive) | Persist matching entities to YAML — audit trail, versioned content history, downstream export |
177
181
  | `mapper` | Run config-supplied transforms over matched entities each cycle (in-core, generic transformation layer) |
@@ -217,13 +221,13 @@ npx mikser --watch # incremental dev loop
217
221
  npx mikser --server # build + serve at :3001
218
222
  ```
219
223
 
220
- For a working starter — config with a real plugin set, sample `documents/`, expected output — see [Getting Started](./documentation/getting-started.md). Or skip straight to "add mikser to this app" via the [Claude Code plugin](#built-for-ai-assisted-development) above.
224
+ For a working starter — config with a real plugin set, sample `documents/`, expected output — see [Getting Started](./docs/getting-started.md). Or skip straight to "add mikser to this app" via the [Claude Code plugin](#built-for-ai-assisted-development) above.
221
225
 
222
226
  ## Core Concepts
223
227
 
224
228
  - **Lifecycle** — Processing runs through fixed phases: initialize → load → import → process → persist → render → finalize. Plugins hook into any phase.
225
229
  - **Entities** — Everything is an entity (document, file, layout, asset). Entities flow through the journal and are tracked in the catalog.
226
- - **References between entities** — A front-matter key starting with `$` (e.g. `$author: /authors/dick`) points at another entity. The engine knows the whole graph: templates can follow the links, the schemas plugin checks they resolve, and a single query can pull referenced entities along inline instead of one round trip per link. See [ADR-0007](./documentation/decisions/0007-references-declaration-and-expansion.md).
230
+ - **References between entities** — A front-matter key starting with `$` (e.g. `$author: /authors/dick`) points at another entity. The engine knows the whole graph: templates can follow the links, the schemas plugin checks they resolve, and a single query can pull referenced entities along inline instead of one round trip per link. See [ADR-0007](./docs/decisions/0007-references-declaration-and-expansion.md).
227
231
  - **Plugins** — Functionality is delivered via plugins. Built-in plugins handle common sources (documents, files, layouts, assets). Custom plugins can be added to any project.
228
232
  - **Runtime Singleton** — A plain module-level object holds all global state and coordinates the lifecycle. The ES module cache guarantees every importer gets the same instance.
229
233
  - **Watch Mode** — In watch mode, file changes trigger incremental re-processing without restarting.
@@ -239,16 +243,16 @@ The shape mikser fits cleanly:
239
243
  - **Mixed-output publishing** — the same source document renders to HTML, PDF (via `post-pdf`), MJML email (via `post-mjml`), and JSON snapshots. One catalog, many output formats, all concurrent.
240
244
  - **Headless backends for static frontends** — pair the `api` plugin with `sdk-api` for SSE-driven live frontends; pair the `data` plugin output with any static host for pre-rendered consumption.
241
245
 
242
- The shape mikser **doesn't** fit cleanly: anything with non-technical content authors who can't or won't work with files, anything with non-content business logic at the core, anything needing multi-tenant / per-user auth. Those aren't bugs — they're outside the design envelope. See [`decisions/0001-content-layer-not-the-app.md`](./documentation/decisions/0001-content-layer-not-the-app.md) for the explicit scope decision.
246
+ The shape mikser **doesn't** fit cleanly: anything with non-technical content authors who can't or won't work with files, anything with non-content business logic at the core, anything needing multi-tenant / per-user auth. Those aren't bugs — they're outside the design envelope. See [`decisions/0001-content-layer-not-the-app.md`](./docs/decisions/0001-content-layer-not-the-app.md) for the explicit scope decision.
243
247
 
244
248
  ## Engineering discipline
245
249
 
246
250
  What you get from how this project is built:
247
251
 
248
- - **Every load-bearing decision has an ADR.** The [`decisions/`](./documentation/decisions/) folder names which choices are structural — files-as-source, journal+catalog split, plugin-as-factory, when something goes in core vs. ships as a plugin — and explains what protects them. When you push against one, there's a written answer waiting instead of folklore.
252
+ - **Every load-bearing decision has an ADR.** The [`decisions/`](./docs/decisions/) folder names which choices are structural — files-as-source, journal+catalog split, plugin-as-factory, when something goes in core vs. ships as a plugin — and explains what protects them. When you push against one, there's a written answer waiting instead of folklore.
249
253
  - **Engine stays small; capability ships in plugins.** The 15+ plugin ecosystem adds features without core changes, so your upgrade cost stays low. Probes like `decap` (a full third-party CMS mounted in ~150 lines, zero engine changes) are deliberate evidence the extension model holds where it counts.
250
254
  - **Builds are deterministic; no async middleware layer.** The journal is the only synchronization primitive — no event bus, no IoC container, no orchestrator running plugins in surprising order. The lifecycle is a list of named phases; "what ran when?" has an answer you can read off the source.
251
- - **The whole engine is one read.** The [Architecture Overview](./documentation/overview.md) walks the full pipeline top to bottom. Onboarding a new engineer is an afternoon, not a tour through fifteen reference docs.
255
+ - **The whole engine is one read.** The [Architecture Overview](./docs/overview.md) walks the full pipeline top to bottom. Onboarding a new engineer is an afternoon, not a tour through fifteen reference docs.
252
256
 
253
257
  ## Mikser among static site generators
254
258
 
@@ -274,19 +278,19 @@ Mikser itself has a previous chapter: the [legacy 7.x line](https://github.com/a
274
278
 
275
279
  | Document | Audience | Description |
276
280
  | ----------------------------------------------------- | ------------------ | -------------------------------------------------- |
277
- | [Architecture Overview](./documentation/overview.md) | Everyone | **Start here.** End-to-end walkthrough of how a file becomes a deployed page across all lifecycle phases. |
278
- | [Getting Started](./documentation/getting-started.md) | Users | Installation, first project, basic usage |
279
- | [Configuration](./documentation/configuration.md) | Users | All CLI options and config file reference |
280
- | [Lifecycle](./documentation/lifecycle.md) | Users & Developers | Complete lifecycle phases and hook system |
281
- | [Plugins](./documentation/plugins.md) | Users & Developers | Built-in plugins, writing custom plugins, the assets / resources / AI pipeline |
282
- | [Entities](./documentation/entities.md) | Users & Developers | Entity model, operations, journal, catalog |
283
- | [Rendering](./documentation/rendering.md) | Users & Developers | Render pipeline, render plugins, render modes |
284
- | [Watch Mode](./documentation/watch-mode.md) | Users | File watching, scheduled tasks, incremental builds |
281
+ | [Architecture Overview](./docs/overview.md) | Everyone | **Start here.** End-to-end walkthrough of how a file becomes a deployed page across all lifecycle phases. |
282
+ | [Getting Started](./docs/getting-started.md) | Users | Installation, first project, basic usage |
283
+ | [Configuration](./docs/configuration.md) | Users | All CLI options and config file reference |
284
+ | [Lifecycle](./docs/lifecycle.md) | Users & Developers | Complete lifecycle phases and hook system |
285
+ | [Plugins](./docs/plugins.md) | Users & Developers | Built-in plugins, writing custom plugins, the assets / resources / AI pipeline |
286
+ | [Entities](./docs/entities.md) | Users & Developers | Entity model, operations, journal, catalog |
287
+ | [Rendering](./docs/rendering.md) | Users & Developers | Render pipeline, render plugins, render modes |
288
+ | [Watch Mode](./docs/watch-mode.md) | Users | File watching, scheduled tasks, incremental builds |
285
289
  | [MCP](https://github.com/almero-digital-marketing/mikser-io-mcp#readme) | Users | The `mikser-io-mcp` plugin — tool surface, `mikser://` resources, twelve worked AI-driven scenarios |
286
- | [Caching](./documentation/caching.md) | Users (production) | The `cache: true` disk cache + working nginx config for reverse-proxy failover |
287
- | [Architecture](./documentation/architecture.md) | Developers | Module-level reference — what's in each file |
288
- | [API Reference](./documentation/api-reference.md) | Developers | Complete public API reference |
289
- | [Decisions (ADRs)](./documentation/decisions/) | Developers | Load-bearing architectural choices and what protects them |
290
+ | [Caching](./docs/caching.md) | Users (production) | The `cache: true` disk cache + working nginx config for reverse-proxy failover |
291
+ | [Architecture](./docs/architecture.md) | Developers | Module-level reference — what's in each file |
292
+ | [API Reference](./docs/api-reference.md) | Developers | Complete public API reference |
293
+ | [Decisions (ADRs)](./docs/decisions/) | Developers | Load-bearing architectural choices and what protects them |
290
294
 
291
295
  ## License
292
296