mikser-io 9.0.0 → 9.0.3

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 (48) hide show
  1. package/10.0-PLAN.md +1 -1
  2. package/CLAUDE.md +256 -24
  3. package/README.md +51 -24
  4. package/docs/api-reference.md +701 -0
  5. package/docs/architecture.md +357 -0
  6. package/docs/caching.md +233 -0
  7. package/docs/configuration.md +423 -0
  8. package/docs/decisions/0001-content-layer-not-the-app.md +61 -0
  9. package/docs/decisions/0002-files-as-source-of-truth.md +64 -0
  10. package/docs/decisions/0003-plugins-independent-engine-stable.md +64 -0
  11. package/docs/decisions/0004-compose-via-protocols.md +71 -0
  12. package/docs/decisions/0005-engine-infrastructure-runs-before-plugin-hooks.md +94 -0
  13. package/docs/decisions/0006-when-to-add-to-core.md +98 -0
  14. package/docs/decisions/0007-references-declaration-and-expansion.md +349 -0
  15. package/docs/decisions/0009-database-engine-substrate.md +252 -0
  16. package/docs/decisions/0010-plugin-bundles-and-inline-options.md +226 -0
  17. package/docs/decisions/0011-served-entities-expose-deployed-urls.md +306 -0
  18. package/docs/decisions/README.md +39 -0
  19. package/docs/entities.md +364 -0
  20. package/docs/getting-started.md +294 -0
  21. package/docs/lifecycle.md +417 -0
  22. package/docs/overview.md +224 -0
  23. package/docs/plugins.md +922 -0
  24. package/docs/rendering.md +557 -0
  25. package/docs/watch-mode.md +266 -0
  26. package/index.js +2 -1
  27. package/package.json +4 -3
  28. package/src/catalog.js +11 -4
  29. package/src/database/sift-to-sql.js +1 -0
  30. package/src/engine.js +50 -11
  31. package/src/lifecycle.js +14 -10
  32. package/src/logger.js +103 -16
  33. package/src/plugins/api.js +17 -10
  34. package/src/plugins/assets.js +29 -0
  35. package/src/plugins/files.js +7 -0
  36. package/src/plugins/preview.js +13 -7
  37. package/src/plugins/providers/http.js +179 -0
  38. package/src/plugins/render/hbs.js +111 -0
  39. package/src/plugins/resources.js +8 -2
  40. package/src/postprocess.js +161 -20
  41. package/src/render.js +29 -0
  42. package/src/routes.js +124 -0
  43. package/src/runtime.js +9 -1
  44. package/src/server.js +31 -11
  45. package/src/source.js +44 -12
  46. package/src/utils.js +156 -31
  47. package/9.0-PLAN.md +0 -438
  48. package/src/plugins/layouts.js +0 -818
package/10.0-PLAN.md CHANGED
@@ -37,7 +37,7 @@ When the working signals dominate over a sustained period of real use, v10 desig
37
37
 
38
38
  ## Why v10 (not 9.x)
39
39
 
40
- The 9.0 plan deliberately deferred mikser-to-mikser composition to v10. The reasoning is in `9.0-PLAN.md` under "Out of scope":
40
+ The 9.0 design deliberately deferred mikser-to-mikser composition to v10. The reasoning:
41
41
 
42
42
  > Cross-instance concerns (discovery, peer identity, routing, cross-mikser refs, watch propagation, liveness, partition behavior, peer auth) belong to a distributed-systems substrate that iterates at "we hit a production partition bug" speed, not at engine-substrate speed. Folding any of it into `mikser-io` would re-fail ADR-0006 test #5 (release cadence) the moment it ships.
43
43
 
package/CLAUDE.md CHANGED
@@ -10,22 +10,51 @@ paths. No migration markdown. No `task: pool` legacy aliasing for
10
10
  `task: inline`. Update READMEs and ADRs in place as source-of-truth
11
11
  changes; rewrite, don't supersede. The freedom is the point.
12
12
 
13
- **v9 has to pay out before v10 starts.** Not "ship v9 and start v10."
14
- **Use v9 in real daily work until the workflow pays out**, then
15
- v10. The v10 architecture (party-mikser-io) is more fun to design
16
- than v9 documentation is to write the gate exists because that
17
- asymmetry kills projects. Concrete pass/fail signals in
18
- `10.0-PLAN.md` under "The gate." If a session brings up v10 design
19
- work, check the gate first; if v9 isn't paying out yet, the
20
- question to answer is "what about v9 isn't working in daily use?"
21
- not "what should v10 look like?"
22
-
23
- **Position mikser by what it is, not by speed.** Hugo wins the speed
24
- race; mikser doesn't compete there. Position: AI-native, lifecycle-
25
- observable, files-as-source-of-truth, full-cycle introspection. Speed
26
- claim cap: "fast enough that watch-mode rebuilds feel instant."
27
- Honest range: 200–800 docs/sec depending on corpus size
28
- (see `test/perf/`).
13
+ **v9 shipped. The gate now is daily-use payoff, not completion.**
14
+ The engine, every sibling plugin, the agency template all live.
15
+ The next step isn't "finish v9," it's **use v9 in real daily work
16
+ until the workflow earns the v10 layer**. The v10 architecture
17
+ (party-mikser-io) is more fun to design than v9 dogfooding is to
18
+ sustain the gate exists because that asymmetry kills projects.
19
+ Concrete pass/fail signals in `10.0-PLAN.md` under "The gate." If
20
+ a session brings up v10 design work, check the gate first; if v9
21
+ isn't paying out yet, the question to answer is "what about v9
22
+ isn't working in daily use?" — not "what should v10 look like?"
23
+
24
+ **mikser is a mixer.** The name is the architecture. It combines
25
+ inputs (entities — from files, forms, queries, anywhere) through
26
+ configurable rendering pipelines (renderer plugins + task-production
27
+ policies) into outputs of any kind. Static-site generation is the
28
+ canonical recipe — what mikser ships with and what most projects
29
+ use today — but it's a recipe, not the definition. The same
30
+ substrate runs:
31
+
32
+ - HTML SSG via `layouts` + `renderHbs` (the recipe everything else
33
+ is currently shaped around)
34
+ - PDFs via `renderHbs` + `post-pdf` (puppeteer-driven)
35
+ - Emails via `renderHbs` → `post-mjml` → `post-email` (chain)
36
+ - 3D renders by composing a hypothetical `renderBlender` against
37
+ `.blend` entities — same engine, different renderer + recipe
38
+ - Video / audio / archives / data-export — same shape
39
+
40
+ The engine has no opinion on what a renderer produces. `render.js`
41
+ and `postprocess.js` are abstract worker entries; `manifest.js`
42
+ tracks snapshots of *any* output; refs invalidation re-runs
43
+ *any* render task. SSG is the dominant case in v9 because it's
44
+ where the dogfooding lives, not because the architecture only
45
+ fits there. When proposing changes, hold the broader view: a
46
+ change that helps SSG but blocks the Blender/video/audio/archive
47
+ cases is constraining the engine's actual reach.
48
+
49
+ **Position mikser by what it is, not by speed.** Hugo wins the
50
+ speed race for SSG specifically; mikser doesn't compete there
51
+ (and "fastest mixer" isn't a useful claim because the bottleneck
52
+ is the renderer, not the engine). Secondary attributes that
53
+ follow from the mixer framing: AI-native, lifecycle-observable,
54
+ files-as-source-of-truth, full-cycle introspection. Speed claim
55
+ cap: "fast enough that watch-mode rebuilds feel instant." Honest
56
+ range: 200–800 docs/sec on the SSG-flavor pipeline depending on
57
+ corpus size (see `test/perf/`).
29
58
 
30
59
  **Direct critique preferred.** Skip "great question," skip "solid but,"
31
60
  skip softening. When something didn't pay off, say so. Match user
@@ -100,7 +129,16 @@ brevity.
100
129
  plugins to push onto), late-binding static mount + listen.
101
130
  - `logger.js` — pino + pino-pretty (inline) + gauge progress + custom
102
131
  Writable for progress coordination + `pino.multistream` for
103
- third-party shipping (`runtime.config.logging.transports`).
132
+ third-party shipping. Two registration surfaces for transports:
133
+ - `runtime.config.logging.transports` — declarative; user config.
134
+ - `addLogTransport({ target, options?, level? })` — public export;
135
+ plugins call this from their factory (queues until logger build)
136
+ or any later hook (live-rebuilds the multistream + swaps
137
+ `runtime.engine.logger`). `useLogger()` reads the swap-target
138
+ fresh on each call so new transports start receiving records
139
+ immediately. Both surfaces compose; same `{level, target, options}`
140
+ shape. Enables Better Stack / Datadog / Loki / Axiom / Sentry as
141
+ standard sibling plugins without engine changes per vendor.
104
142
  - `utils.js` — shared pure helpers: `mimeForEntity`, `isLoopback`,
105
143
  `expandEntity`, `projectMeta`, `useCollection`, `useRenderer` (via
106
144
  render.js), `isTextEntity`, `readEntityContent`, `extractRefs`,
@@ -137,15 +175,52 @@ brevity.
137
175
  Strings produce a v9 migration error pointing at the new shape.
138
176
  - `manager.js` — file watching (chokidar) and cron scheduling.
139
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.
140
191
  - `constants.js` — `OPERATION` (CREATE/UPDATE/DELETE/RENDER/
141
192
  POSTPROCESS), `ACTION` (sync action types), `TASKS` (`INLINE`/
142
193
  `SERIAL`/`WORKER` — dispatch modes).
143
194
 
195
+ ## Canonical sibling plugins
196
+
197
+ These ship as separate repos but most projects pull them in. They're
198
+ not bundled because the engine is renderer-agnostic — each plugin
199
+ encodes a particular recipe (SSG, MJML, PDF, email) rather than
200
+ substrate.
201
+
202
+ - **`mikser-io-layouts`** — the canonical SSG-flavor task-production
203
+ policy. Owns multi-match layout assignment (`meta.layout` /
204
+ `meta.layouts` dual key + `autoLayouts` peel ladder), per-layout
205
+ destination Handlebars templates, collision detection, postprocess
206
+ chain parsing (filename `name.html-mjml-email.hbs` → `postprocessors:
207
+ ['mjml','email']` + frontmatter `postprocessor`/`postprocessors`),
208
+ `inspect()` primitive at `runtime.options.layouts.inspect`. The
209
+ engine has no opinion on layouts; this plugin is one way of producing
210
+ render tasks. Other domains (3D rendering, video) would have their
211
+ own task-production plugins.
212
+ - **`mikser-io-render-{hbs,liquid,eta,markdown,metatext}`** — renderer
213
+ plugins. `renderHbs` is bundled in core; the rest are siblings.
214
+ - **`mikser-io-post-{mjml,pdf,email}`** — postprocessor plugins,
215
+ composable in chains (see `docs/rendering.md#postprocess`).
216
+ - **`mikser-io-mcp`** — AI tooling surface (ADR-0006 test #5).
217
+ - **`mikser-io-vector`** — vector index over the catalog.
218
+ - **`mikser-io-forms`** — HTTP form receivers → entities.
219
+ - **`mikser-io-schemas`** — TypeScript type generation from entity meta.
220
+
144
221
  ## Plugin map (`src/plugins/`)
145
222
 
146
223
  - `documents` — file→entity sync for the documents collection
147
- - `layouts` — layout matching + sitemap + `inspect()` primitive
148
- (exposed at `runtime.options.layouts.inspect`)
149
224
  - `files` — file→entity sync for the files collection
150
225
  - `assets` — asset references and copy
151
226
  - `resources` — resource references
@@ -160,11 +235,87 @@ brevity.
160
235
  - `observer` / `mapper` / `validator` / `commands` / `shares` —
161
236
  utility plugins
162
237
 
238
+ ## Built-in content providers (`src/plugins/providers/`)
239
+
240
+ The substrate-side `readEntityContent(entity)` dispatches by URI
241
+ scheme. Two schemes are handled inline by built-in providers
242
+ (no separate package required, ships with mikser-io):
243
+
244
+ - **No scheme / plain path / `file://`** — filesystem read. Lives
245
+ inline in `src/utils.js readEntityContent` because the engine
246
+ already touches the filesystem everywhere.
247
+ - **`http://` / `https://`** — `src/plugins/providers/http.js`.
248
+ Conditional GET with ETag + Last-Modified, in-memory response
249
+ cache, inflight coalescing on the same URL, binary mirror to
250
+ `runtime/http-cache/<sha-of-url>.<ext>`, operator-supplied
251
+ headers via `entity.meta.httpHeaders`, configurable timeout via
252
+ `entity.meta.httpTimeoutMs`. Test affordance:
253
+ `__resetHttpCacheForTests()` clears the module-level caches
254
+ between unit tests.
255
+
256
+ Any other scheme (`gdrive://`, `notion://`, `s3://`, …) routes
257
+ through the dynamic-import-by-name path — `mikser-io-provider-<scheme>`
258
+ must be installed as a sibling package and export a top-level
259
+ `read(entity)` function. See ADR-0010 / the gdrive provider for
260
+ the convention.
261
+
262
+ ## Multi-emitter collections (sweep ownership)
263
+
264
+ Collections like `documents` / `files` / `assets` aren't owned by a
265
+ single plugin. The file source (via `useSource`) emits into them
266
+ from its scanned folder, but other plugins legitimately emit into
267
+ the same collection: `mikser-io-csv` fans CSV rows into `documents`,
268
+ a gdrive sync would emit there too, an API endpoint can push into
269
+ `files`. The catalog has no opinion on emitter identity — only on
270
+ collection.
271
+
272
+ The delete sweep MUST respect that. `sweepDeleted(collection,
273
+ scanned, onDelete, ownerPrefix)` requires `ownerPrefix` (typically
274
+ `absFolder` for file sources, `layoutsFolder` for layouts) and only
275
+ considers entities whose `entity.uri` is rooted under that prefix.
276
+ Foreign-emitter entities have a different `uri` shape:
277
+ - CSV row entities: empty `uri` (synthetic — meta is the content)
278
+ - HTTP-fetched CSV parent: `uri = 'https://…'`
279
+ - gdrive-sourced docs: `uri = 'gdrive://…'`
280
+
281
+ …and the LIKE clause excludes them. Without the scope, every cycle's
282
+ file-source sweep silently wipes every co-collection emitter's
283
+ entities. Throwing instead of silently sweeping the whole collection
284
+ is intentional — this used to be a class-of-bugs landmine. If you're
285
+ writing a new source-shaped plugin, the scoping is mandatory.
286
+
287
+ Test coverage: `test/unit/source-sweep.test.js`.
288
+
163
289
  ## Naming conventions
164
290
 
165
291
  - **Engine state** at `runtime.<name>` — `runtime.refs`, `runtime.catalog`.
166
292
  - **Plugin surfaces** at `runtime.options.<plugin>` —
167
293
  `runtime.options.preview`, `runtime.options.layouts.inspect`.
294
+ - **Reachability:** `runtime.options.url` is the engine's resolved
295
+ public URL (from `--url` or `config.url`, trailing slash stripped,
296
+ validated). Plugins that need external reachability — webhook
297
+ receivers, absolute links in emails, MCP preview URLs returned to
298
+ agents, forms share links — read this. Standard gating pattern:
299
+ ```js
300
+ const canPush = runtime.options.url?.startsWith('https://')
301
+ if (canPush) registerWebhookAt(`${runtime.options.url}/api/X/webhook`)
302
+ else setupPollingFallback()
303
+ ```
304
+ `runtime.options.port` is the internal listener port (kept for
305
+ loopback/dev URL building); `runtime.options.url` is the external
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.
168
319
  - **Engine functions** as module-level exports from `mikser-io`:
169
320
  `import { queryEntities, subscribe, useRenderer, useCollection,
170
321
  readEntityContent, isTextEntity } from 'mikser-io'`.
@@ -238,6 +389,26 @@ brevity.
238
389
  plugin config moved entirely off `runtime.config.<plugin>`; it
239
390
  arrives as factory args, gets stashed on the descriptor, and is
240
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).
241
412
 
242
413
  ## MCP
243
414
 
@@ -326,6 +497,22 @@ There is **no `--mcp` CLI flag**. Activation is plugin-presence only.
326
497
  - **New lifecycle hook?** Almost certainly no. Existing hooks cover
327
498
  all known patterns. If you think you need one, post the use case
328
499
  to ADR-0006 review.
500
+ - **New content source (gdrive, notion, s3, github, …)?** Two
501
+ things ship together:
502
+ - A regular lifecycle plugin (the sync — emits entities into the
503
+ catalog, sets `entity.uri = '<scheme>://...'` so the dispatch
504
+ knows where to route reads).
505
+ - A named export `read(entity)` from the same package, which
506
+ must be named `mikser-io-provider-<scheme>` so the engine can
507
+ find it. `readEntityContent(entity)` parses the scheme out of
508
+ `entity.uri`, dynamic-imports the package, calls its `read`.
509
+ No registry, no descriptor — same package-name convention as
510
+ `mikser-io-render-*` and `mikser-io-post-*`.
511
+ Built-in: plain local paths (no scheme) and `file://` URIs are
512
+ read via `fs.readFile` directly — no provider package needed for
513
+ the canonical local-filesystem case. `entity.content` already
514
+ populated is a fast-path that skips the dispatch entirely (for
515
+ small remote docs eager-fetched at sync time).
329
516
 
330
517
  ## Test suites
331
518
 
@@ -342,13 +529,58 @@ There is **no `--mcp` CLI flag**. Activation is plugin-presence only.
342
529
  + clean-build timing). `SIZE=realistic` + entity count knobs
343
530
  documented in **Perf**.
344
531
 
532
+ ## Dev workspace
533
+
534
+ The siblings live side-by-side under `/Users/dick/Projects/mikser/`
535
+ and share an npm workspace declared at the parent's `package.json`:
536
+
537
+ ```json
538
+ {
539
+ "private": true,
540
+ "workspaces": ["mikser-io", "mikser-io-*"]
541
+ }
542
+ ```
543
+
544
+ `npm install` at that root hoists everything: each sibling's
545
+ `node_modules/mikser-io` (and any other cross-workspace dep)
546
+ becomes a symlink to the working copy. The whole tree runs
547
+ against ONE module instance of mikser-io.
548
+
549
+ **This isn't optional ergonomics — it's correctness.** Plugins
550
+ use `AsyncLocalStorage` (currently `queryContext` for sidecar
551
+ findEntities tracking; the schema-version meta could grow more).
552
+ Without workspace deduplication, npm 7+ auto-installs the peer
553
+ dep into each sibling's own `node_modules`. Layouts'
554
+ `import { queryContext } from 'mikser-io'` then resolves to its
555
+ bundled copy — a different AsyncLocalStorage instance than the
556
+ engine uses. Sidecar queries don't get tracked → no `query`
557
+ edges in `mikser_snapshots.refClosure` →
558
+ `manifest.queryAffected` returns empty → aggregate layouts
559
+ (index pages, sitemaps, RSS feeds) never invalidate when new
560
+ matching entities land. Silent broken-incremental.
561
+
562
+ `test/scenarios/_harness.js` also auto-coalesces (replaces any
563
+ real `mikser-io-layouts/node_modules/mikser-io` directory with a
564
+ symlink to MIKSER_ROOT on every `setupFixture`) as a backstop —
565
+ the scenarios pass with or without the workspace, but the
566
+ workspace is the canonical setup. Same shape applies to any
567
+ future sibling that uses `queryContext` or other module-level
568
+ state from mikser-io.
569
+
570
+ For production consumers (someone `npm install mikser-io
571
+ mikser-io-layouts` in their own project), npm resolves both from
572
+ the consumer's own project tree — no duplication, no bug.
573
+
345
574
  ## Reference
346
575
 
347
- - `documentation/architecture.md` — module map (audit before relying
576
+ - `docs/architecture.md` — module map (audit before relying
348
577
  on specifics; may have drift)
349
- - `documentation/decisions/` — ADRs
350
- - `documentation/configuration.md` — config reference
351
- - `documentation/api-reference.md` — public API
578
+ - `docs/decisions/` — ADRs
579
+ - `docs/configuration.md` — config reference
580
+ - `docs/api-reference.md` — public API
352
581
  - `test/perf/` — render-pipeline perf rig
353
- - Sibling repos: `mikser-io-mcp`, `mikser-io-vector`, `mikser-io-schemas`,
582
+ - Sibling repos: `mikser-io-layouts` (canonical SSG-flavor render-task
583
+ production policy), `mikser-io-mcp`, `mikser-io-vector`,
584
+ `mikser-io-schemas`, `mikser-io-forms`, `mikser-io-post-{mjml,pdf,email}`,
585
+ `mikser-io-render-{eta,liquid,markdown,metatext}`,
354
586
  `mikser-io-sdk-{api,react,svelte,vue,vector}`, `mikser-io-example-blog`
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
 
@@ -50,6 +54,28 @@ Build mikser into the parts of your application that are content-shaped. Keep th
50
54
 
51
55
  **Open source.** MIT-licensed, on GitHub, no telemetry, no auth wall, no SaaS dependency. What you see is what runs.
52
56
 
57
+ ## Mix content from anywhere, ship to anywhere
58
+
59
+ A real content stack pulls from more than one system. Marketing copy lives in a CMS or a spreadsheet. Prices and stock live in an ERP. Hero images live in a DAM. Editorial pages live in markdown files in the repo. Most teams either pick one tool and contort the rest to fit it, or build sync services that copy everything into one database — and then more sync services when things drift out of sync.
60
+
61
+ Mikser is built around a different bet: **one queryable substrate that any source can pour into, and any frontend can read from.**
62
+
63
+ **Any source.** Provider plugins let you treat external systems as content sources. A Google Sheet, an ERP feed, a Drive folder, a Notion database, an HTTP webhook, the local repo's `.md` files — each becomes a stream of entities flowing into mikser's catalog with the same shape. The authoring tool doesn't change. The editorial workflow doesn't change. The team writing product copy in Google Sheets keeps writing product copy in Google Sheets — mikser just notices when they save.
64
+
65
+ **Cross-source composition through references.** Mikser tracks references between entities the way a graph database does. A product page can declare `$marketing` pointing at a row pulled from a spreadsheet, `$pricing` at a record pulled from your ERP, `$hero` at an asset pulled from your DAM. A single API call from your frontend asks for the product page *with* its referenced data — and gets the page, the marketing copy, the price, and the hero image merged into one response. One round trip, not three or four, and no consumer-side join logic to maintain.
66
+
67
+ **Live updates are uniform across every source.** Edit a cell in the spreadsheet, change a price in the ERP, replace an asset in the DAM — the pages depending on those entities update within seconds, in every frontend connected to mikser's live channel. You don't write per-source invalidation; mikser already knows which pages reference what.
68
+
69
+ **Your frontend is whatever you want.** Mikser exposes the catalog over HTTP (and over MCP, for AI agents). You query it from React, Vue, Svelte, SvelteKit, Next.js, an iOS app, a kiosk — anything that speaks HTTP. The framework SDKs (`mikser-io-sdk-react`, `mikser-io-sdk-vue`, `mikser-io-sdk-svelte`) make the calls feel native, but they're optional. Headless CMSes free you from the database lock. Frameworks like Astro free you from one kind of authoring lock. Mikser frees you from both at once: **any source on the input side, any framework on the output side.**
70
+
71
+ What this looks like in practice:
72
+
73
+ - Marketing edits product copy in Google Sheets, prices come from your ERP, both flow into the same product page in your storefront — changes appear within seconds, without your e-commerce team writing webhook handlers or cache-invalidation logic.
74
+ - A SaaS pulls customer-facing release notes from `release-notes/*.md` in the repo and feature flags from LaunchDarkly; both render in a dashboard built in the team's own React stack, not a framework somebody else picked.
75
+ - A magazine pulls editorial articles from local markdown, contributor bios from Notion, sponsorship info from Airtable, and renders the same article surface across web, email (MJML), and PDF — one catalog, one set of references, three outputs.
76
+
77
+ Adding a source is mechanical. See [`mikser-io-csv`](https://github.com/almero-digital-marketing/mikser-io-csv) (any HTTP-served CSV, with live polling), [`mikser-io-provider-gdrive`](https://github.com/almero-digital-marketing/mikser-io-provider-gdrive) (Google Drive), and the built-in `http` provider for the shape. If a system has an API, it can be a mikser source, usually in well under 100 lines.
78
+
53
79
  ## Built for AI-assisted development
54
80
 
55
81
  Files-as-source isn't just a portability story — it makes the project unusually friendly to AI coding agents. There's a static-time half (the agent reads your tree the way it reads any repo — no DB connection, no schema upload, no sandboxed query layer to learn) and a runtime half (when the agent needs to write or render, mikser ships its own MCP server in core, so it talks to the live engine instead of a parallel REST shim you have to maintain).
@@ -141,7 +167,7 @@ The engine is what stays stable — the lifecycle, the catalog, the file-based c
141
167
  | Plugin | What it does |
142
168
  |---|---|
143
169
  | `data` | JSON snapshots of entities / context / catalog, written to disk for static serving |
144
- | `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 |
145
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 |
146
172
 
147
173
  **Integrations:**
@@ -149,7 +175,8 @@ The engine is what stays stable — the lifecycle, the catalog, the file-based c
149
175
  | Plugin | What it does |
150
176
  |---|---|
151
177
  | [`mikser-io-vector`](https://github.com/almero-digital-marketing/mikser-io-vector) | OpenAI embeddings + semantic search (sqlite-vec or pgvector) |
152
- | [`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) |
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 |
153
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 |
154
181
  | `mapper` | Run config-supplied transforms over matched entities each cycle (in-core, generic transformation layer) |
155
182
  | [`mikser-io-live`](https://github.com/almero-digital-marketing/mikser-io-live) | Lightweight dev server with browser auto-refresh — pair with `--watch` for the classic save→reload loop |
@@ -194,13 +221,13 @@ npx mikser --watch # incremental dev loop
194
221
  npx mikser --server # build + serve at :3001
195
222
  ```
196
223
 
197
- 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.
198
225
 
199
226
  ## Core Concepts
200
227
 
201
228
  - **Lifecycle** — Processing runs through fixed phases: initialize → load → import → process → persist → render → finalize. Plugins hook into any phase.
202
229
  - **Entities** — Everything is an entity (document, file, layout, asset). Entities flow through the journal and are tracked in the catalog.
203
- - **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).
204
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.
205
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.
206
233
  - **Watch Mode** — In watch mode, file changes trigger incremental re-processing without restarting.
@@ -216,16 +243,16 @@ The shape mikser fits cleanly:
216
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.
217
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.
218
245
 
219
- 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.
220
247
 
221
248
  ## Engineering discipline
222
249
 
223
250
  What you get from how this project is built:
224
251
 
225
- - **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.
226
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.
227
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.
228
- - **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.
229
256
 
230
257
  ## Mikser among static site generators
231
258
 
@@ -251,19 +278,19 @@ Mikser itself has a previous chapter: the [legacy 7.x line](https://github.com/a
251
278
 
252
279
  | Document | Audience | Description |
253
280
  | ----------------------------------------------------- | ------------------ | -------------------------------------------------- |
254
- | [Architecture Overview](./documentation/overview.md) | Everyone | **Start here.** End-to-end walkthrough of how a file becomes a deployed page across all lifecycle phases. |
255
- | [Getting Started](./documentation/getting-started.md) | Users | Installation, first project, basic usage |
256
- | [Configuration](./documentation/configuration.md) | Users | All CLI options and config file reference |
257
- | [Lifecycle](./documentation/lifecycle.md) | Users & Developers | Complete lifecycle phases and hook system |
258
- | [Plugins](./documentation/plugins.md) | Users & Developers | Built-in plugins, writing custom plugins, the assets / resources / AI pipeline |
259
- | [Entities](./documentation/entities.md) | Users & Developers | Entity model, operations, journal, catalog |
260
- | [Rendering](./documentation/rendering.md) | Users & Developers | Render pipeline, render plugins, render modes |
261
- | [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 |
262
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 |
263
- | [Caching](./documentation/caching.md) | Users (production) | The `cache: true` disk cache + working nginx config for reverse-proxy failover |
264
- | [Architecture](./documentation/architecture.md) | Developers | Module-level reference — what's in each file |
265
- | [API Reference](./documentation/api-reference.md) | Developers | Complete public API reference |
266
- | [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 |
267
294
 
268
295
  ## License
269
296