mikser-io 8.2.0 → 8.3.1

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.
package/CLAUDE.md ADDED
@@ -0,0 +1,303 @@
1
+ # CLAUDE.md — mikser-io
2
+
3
+ Read this before proposing changes. Posture, architectural conventions,
4
+ and the landmarks that survive across sessions.
5
+
6
+ ## Posture
7
+
8
+ **Until v10, mikser has no users.** No back-compat. No deprecation
9
+ paths. No migration markdown. No `task: pool` legacy aliasing for
10
+ `task: inline`. Update READMEs and ADRs in place as source-of-truth
11
+ changes; rewrite, don't supersede. The freedom is the point.
12
+
13
+ **Position mikser by what it is, not by speed.** Hugo wins the speed
14
+ race; mikser doesn't compete there. Position: AI-native, lifecycle-
15
+ observable, files-as-source-of-truth, full-cycle introspection. Speed
16
+ claim cap: "fast enough that watch-mode rebuilds feel instant."
17
+ Honest range: 200–800 docs/sec depending on corpus size
18
+ (see `test/perf/`).
19
+
20
+ **Direct critique preferred.** Skip "great question," skip "solid but,"
21
+ skip softening. When something didn't pay off, say so. Match user
22
+ brevity.
23
+
24
+ ## Module map (`src/`)
25
+
26
+ - `runtime.js` — singleton, holds engine state (`runtime.catalog`,
27
+ `runtime.refs`) and `runtime.options`. Lifecycle hook arrays live
28
+ here.
29
+ - `lifecycle.js` — hook registration: `onLoad`/`onLoaded`/`onProcess`/
30
+ `onProcessed`/`onPersist`/`onBeforeRender`/`onRender`/`onAfterRender`/
31
+ `onBeforePostprocess`/`onPostprocess`/`onComplete`/`onFinalize`/
32
+ `onFinalized`/`onCancel`/`onCancelled`. Plus `runtime.create`/
33
+ `.update`/`.delete` (journal helpers).
34
+ - `journal.js` — per-cycle queue, persisted to `mikser_journal` rows in
35
+ `runtime/mikser.sqlite`. Drained at onFinalized; survives crashes so
36
+ `--resume` (`-R`) can pick up unfinalized entries on the next start.
37
+ Same public surface: `addEntry`/`addEntries`/`updateEntry`/
38
+ `useJournal`/`clearJournal`. Inserts `JSON.stringify` the row body for
39
+ snapshot isolation (replaces the prior `structuredClone`). Walks are
40
+ chunked (`CHUNK_SIZE=500`) so peak journal memory stays bounded
41
+ regardless of corpus.
42
+ **Auto-persist:** `useJournal` diffs the yielded entity after each
43
+ iteration and UPDATEs the row if it changed. Plugin authors mutate
44
+ the yielded entity and move on — no explicit `updateEntry({id,entity})`
45
+ required. `updateEntry` is still exported for engine-internal writes
46
+ (output, deps) and as a no-op safety valve for plugins that prefer to
47
+ call it; if the entity hasn't drifted, the auto-persist skips.
48
+ - `catalog.js` — entity persistence in the `mikser_entities` table of
49
+ `runtime/mikser.sqlite`. Indexed columns: id (PK), collection,
50
+ type, format, name, meta_href, meta_layout, meta_lang, meta_cache,
51
+ time, uri. Full entity body in `data` (JSON TEXT). 10k-entry LRU
52
+ cache in front of `findById`. Public ops: `findEntity`,
53
+ `findEntities`, `iterateEntities` (streaming async generator over the
54
+ same query shape, seek-paginated — use it when results may be
55
+ corpus-scale and the caller doesn't need an array), `queryEntities`,
56
+ `readEntity`, `subscribe`, `assertExpand`. Expand internals
57
+ (`expandLimits`, `expandAndProject`, `findRef`) are PRIVATE.
58
+ - `subscriptions.js` — `subscribe()` primitive. Two modes: journal-
59
+ walk dispatch (default) and graph dispatch via
60
+ `runtime.refs.subscribeGraph` (when `expand` is set).
61
+ - `refs.js` — inverse-reference graph (`$`-keyed refs per ADR-0007).
62
+ Persisted as `mikser_refs` rows with FK to `mikser_entities`
63
+ (`ON DELETE CASCADE`). Indexed on `target` and `source`. Exposed
64
+ at `runtime.refs.*`: `inboundFor`, `outboundFor`, `allRefs`,
65
+ `size`, `rename`, `subscribeGraph`, `inverseClosureOf`. Plus
66
+ `refExists` module-level. Prepared statements through the shared
67
+ sqlite handle.
68
+ - `engine.js` — `setup()`, lifecycle wiring, render + postprocess
69
+ dispatchers, manifest tracking. Owns the Piscina worker pools
70
+ (`renderWorkers`, `postprocessWorkers`); both are lazy
71
+ (`minThreads: 0` + `idleTimeout: 30_000`) so INLINE-only workloads
72
+ pay no worker overhead. `workerSafeOptions(runtime.options)`
73
+ strips plugin-surface functions before TASKS.WORKER dispatch so
74
+ Piscina's structured clone doesn't choke.
75
+ - `database/` — `createSqliteDatabase()`, `registerSchema()`,
76
+ `useDatabase()` (the `mikser_meta` table stamps schema_version).
77
+ `sift-to-sql.js` translates sift filters to SQL WHERE clauses
78
+ against `INDEXED_COLUMNS`; un-pushed clauses fall through to
79
+ JS-side sift. `query-context.js` is the AsyncLocalStorage that
80
+ lets catalog queries auto-report into the render-time `track`.
81
+ - `manifest.js` — render snapshots in `mikser_snapshots` table
82
+ (PK `(id, destination)`, `refClosure` as JSON, partial index on
83
+ `parent`). `recordedHashes` aggregates dep hashes via
84
+ `json_each` in C rather than parsing every row in JS.
85
+ - `server.js` — Express bring-up: CLI flags (`--server`, `--cors`,
86
+ `--no-cors`), trust-proxy, CORS (with extensible header arrays for
87
+ plugins to push onto), late-binding static mount + listen.
88
+ - `logger.js` — pino + pino-pretty (inline) + gauge progress + custom
89
+ Writable for progress coordination + `pino.multistream` for
90
+ third-party shipping (`runtime.config.logging.transports`).
91
+ - `utils.js` — shared pure helpers: `mimeForEntity`, `isLoopback`,
92
+ `expandEntity`, `projectMeta`, `useCollection`, `useRenderer` (via
93
+ render.js), `isTextEntity`, `readEntityContent`, `extractRefs`,
94
+ `isRefKey`, `writeEntity`, `matchEntity`, `getFormatInfo`,
95
+ `changeExtension`, `checksum`, `normalize`, `formatErrorContext`,
96
+ `formatLogArgs`, `ExpandError`, `AbortError`.
97
+ - `render.js` / `postprocess.js` — Piscina worker entry points AND the
98
+ default-export functions the INLINE/SERIAL dispatcher calls directly.
99
+ Each receives entity + options + config + state; the WORKER path also
100
+ receives a MessageChannel `port` that forwards pino records back to
101
+ the engine's logger. Each worker opens its own read-only sqlite
102
+ handle on first task (`ensureWorkerDb` in render.js) so template
103
+ helpers like `runtime.lookupHref` stay sync. Never touch the journal
104
+ directly.
105
+ - `config.js` — loads `mikser.config.js` at `onLoad`.
106
+ - `plugins.js` — loads user plugins at `onLoad`. Plugin factories
107
+ receive the full `core` exports as their first argument.
108
+ - `manager.js` — file watching (chokidar) and cron scheduling.
109
+ - `source.js` — `useSource` codifies the folder-of-files pattern.
110
+ - `constants.js` — `OPERATION` (CREATE/UPDATE/DELETE/RENDER/
111
+ POSTPROCESS), `ACTION` (sync action types), `TASKS` (`INLINE`/
112
+ `SERIAL`/`WORKER` — dispatch modes).
113
+
114
+ ## Plugin map (`src/plugins/`)
115
+
116
+ - `documents` — file→entity sync for the documents collection
117
+ - `layouts` — layout matching + sitemap + `inspect()` primitive
118
+ (exposed at `runtime.options.layouts.inspect`)
119
+ - `files` — file→entity sync for the files collection
120
+ - `assets` — asset references and copy
121
+ - `resources` — resource references
122
+ - `front-matter` — YAML frontmatter extraction (HTML/MD files)
123
+ - `yaml` — YAML format support (.yml/.yaml entities)
124
+ - `json` — JSON format support
125
+ - `api` — HTTP catalog access. Pure transport — exposes nothing on
126
+ `runtime.options.*`. Per-query disk cache.
127
+ - `preview` — in-memory render cache + GET /preview/:filename route.
128
+ Exposed at `runtime.options.preview = { store, get, stats, config }`.
129
+ - `data` — JSON snapshots of entities/context/catalog to disk
130
+ - `observer` / `mapper` / `validator` / `commands` / `shares` —
131
+ utility plugins
132
+
133
+ ## Naming conventions
134
+
135
+ - **Engine state** at `runtime.<name>` — `runtime.refs`, `runtime.catalog`.
136
+ - **Plugin surfaces** at `runtime.options.<plugin>` —
137
+ `runtime.options.preview`, `runtime.options.layouts.inspect`.
138
+ - **Engine functions** as module-level exports from `mikser-io`:
139
+ `import { queryEntities, subscribe, useRenderer, useCollection,
140
+ readEntityContent, isTextEntity } from 'mikser-io'`.
141
+ - **Plugin packages**: `mikser-io-<name>` (mikser-io-mcp, mikser-io-vector,
142
+ mikser-io-schemas, etc.).
143
+ - **MCP tools**: `mikser_<verb>` or `mikser_<subsystem>_<verb>`:
144
+ `mikser_query_entities`, `mikser_read_entity`, `mikser_update_entity`,
145
+ `mikser_delete_entity`, `mikser_render`, `mikser_refs_inbound`,
146
+ `mikser_refs_outbound`, `mikser_refs_broken`, `mikser_refs_rename`,
147
+ `mikser_layouts_inspect`, `mikser_preview_render`,
148
+ `mikser_preview_ui`, `mikser_ui_action`, `mikser_ping`.
149
+ **Never `mikser_api_*`** — that prefix is dead.
150
+ - **TASKS constants**: `INLINE` (main-thread async), `SERIAL`
151
+ (p-queue concurrency 1), `WORKER` (Piscina pool). **Not** the old
152
+ `POOL`/`QUEUE`/`WORKER` (which misled readers — `POOL` was actually
153
+ main-thread, not Piscina).
154
+
155
+ ## Code style
156
+
157
+ - **No historical-narrative comments.** Describe what's there now.
158
+ "Used to live in X, moved in 8.2.0" → git log territory.
159
+ - **No separator-line comments** (`// ---- Section -----`).
160
+ - **No `_` prefix on engine-managed paths** (`runtimeFolder/foo`,
161
+ not `runtimeFolder/_foo`).
162
+ - **Engine-set entity fields under `entity.options`**, not
163
+ `_`-prefixed top-level.
164
+ - **No cross-plugin imports.** Plugins compose through lifecycle +
165
+ `runtime.options.*`. Shared pure helpers go in `src/utils.js`.
166
+ Audit: `grep -rEn "from '\./|await import\('\./" src/plugins/*.js`
167
+ should return nothing.
168
+ - **Single source of truth fixes.** For cross-plugin bugs, ask
169
+ "what's the canonical copy?" before symptom-site patching.
170
+ - **README claims are promises.** Every line in mikser-io's README
171
+ is a commitment — true today or actively being made true. Stale
172
+ claims are bugs.
173
+
174
+ ## ADRs (canonical decisions)
175
+
176
+ - **0001-0005** — foundational: content-layer-not-the-app,
177
+ files-as-source-of-truth, plugins-independent-engine-stable,
178
+ compose-via-protocols, engine-infrastructure-runs-before-plugin-hooks
179
+ - **0006** — five-test framework for adding to core: (1) substrate,
180
+ (2) strengthens strategy, (3) god-plugin check, (4) composability,
181
+ (5) release cadence. Conjunctive. Express passes; MCP failed test
182
+ #5 and ships as mikser-io-mcp.
183
+ - **0007** — `$`-prefixed reference declaration + `expand`
184
+ resolution. Implemented in `catalog.js` + `refs.js`. Caps
185
+ configurable under `catalog.expand.{maxDepth,maxPaths,maxResolved}`
186
+ (defaults 5/20/100).
187
+ - **0008** — MCP-UI rendering + action delivery. Lives in
188
+ `mikser-io-mcp/documentation/decisions/` (not core — moved with
189
+ MCP).
190
+ - **0009** — Sqlite is the engine's persistence substrate. Single
191
+ `runtime/mikser.sqlite` holds `mikser_entities` / `mikser_refs` /
192
+ `mikser_snapshots` / `mikser_journal` (+ `mikser_meta` for the
193
+ schema-version stamp). Sift→SQL pushdown + LRU for findById;
194
+ worker-side read-only sqlite for sync template helpers.
195
+ `registerSchema(name, sql)` + `useDatabase()` is the plugin-side
196
+ persistence pattern. Journal-on-sqlite (Phase 7) enables `--resume`;
197
+ auto-persist (Phase 9) means plugins mutate the yielded entity and
198
+ the journal writes back without an explicit `updateEntry` call.
199
+
200
+ ## MCP
201
+
202
+ Lives in `mikser-io-mcp` plugin (separate repo). Activate by listing
203
+ `'mcp'` **first** in your `mikser.config.js` plugins array:
204
+
205
+ ```js
206
+ export default {
207
+ plugins: ['mcp', /* ...other plugins */],
208
+ mcp: {
209
+ path: '/mcp', // optional; default '/mcp'
210
+ // endpoints: { ... } // optional; per-endpoint token + scope
211
+ },
212
+ }
213
+ ```
214
+
215
+ Must be first because its factory creates `runtime.options.mcp`
216
+ synchronously, and other plugins gate their MCP tool registration on
217
+ `if (runtime.options.mcp)` in their own `onLoaded`.
218
+
219
+ There is **no `--mcp` CLI flag**. Activation is plugin-presence only.
220
+
221
+ ## Perf
222
+
223
+ - Rig: `npm run test:perf` (generates 10k corpus, runs render-only
224
+ pipeline). Configurable: `node test/perf/generate.js 50000`.
225
+ `SIZE=realistic node test/perf/generate.js 10000` switches to fat
226
+ entities (full SEO meta, hero/gallery image objects, $-refs to
227
+ author/category/related, longer body — ~7KB per catalog entry
228
+ instead of ~3KB). Add `task: worker` to a layout's frontmatter to
229
+ dispatch its renders + postprocess through Piscina.
230
+ - Current honest numbers (Apple Silicon, 4-thread default, INLINE
231
+ dispatch; see ADR-0009 for the substrate the numbers below run on):
232
+ - 14k realistic cold (--clear): 33s, RSS 1.4GB peak
233
+ - 14k realistic warm clean: 2.6s, RSS 156MB peak
234
+ - 14k realistic warm + 1 change: 3.0s, RSS 156MB peak
235
+ - 110k realistic cold (--clear): 5.5 min
236
+ - 110k realistic warm clean: 25s, RSS 3.2GB
237
+ - vs the Map+NDJSON baseline (origin/main 6922b33) at 14k realistic:
238
+ cold ~4× faster, warm ~2× faster, warm RSS ~9× smaller. 110k is a
239
+ workload Map+NDJSON couldn't reach — process OOMed before ADR-0009.
240
+ - Catalog scan cost was the 2024-era objection to sqlite. The
241
+ resolution lives in `src/database/sift-to-sql.js`: indexed sift
242
+ clauses ($eq/$in/$lt/$exists/etc. on collection / type / format /
243
+ name / meta_href / meta_layout / meta_lang / meta_cache / time /
244
+ uri) push down to SQL, so layouts.onLoaded and source.sweep don't
245
+ materialize the table per cycle. `findById` is a 10k-entry LRU in
246
+ front of PK lookup. Without those two, sqlite is strictly slower
247
+ than the old Map (we measured it — see ADR-0009).
248
+ - Piscina is lazy (`minThreads: 0` + `idleTimeout: 30_000`). INLINE
249
+ dispatch is the default for both render and postprocess; layouts
250
+ that opt into TASKS.WORKER (`task: worker` in frontmatter) get a
251
+ thread per first task. At 14k the lazy init dropped peak RSS
252
+ ~130MB on workloads that never use WORKER (which is most).
253
+ - **Profile before optimizing.** `node --cpu-prof app.js
254
+ --working-folder test/perf --clear` produces `.cpuprofile` for
255
+ Chrome DevTools. Intuition has a real miss rate (multiple perf
256
+ hypotheses across this rewrite turned out wrong; the profile
257
+ caught them every time).
258
+ - What actually helps when RSS is too high: trim entity weight
259
+ (don't store source `content` in the catalog if the renderer
260
+ re-reads it; don't keep computed fields you can recompute),
261
+ filter your `data.entities` exports so the catalog isn't
262
+ carrying the rendered shape, or drop `--threads` to 1-2 if the
263
+ build is memory-bound and cold time is acceptable.
264
+
265
+ ## When extending
266
+
267
+ - **New engine capability?** Run through ADR-0006's five tests. Bar
268
+ is high. Express is the only earned addition.
269
+ - **New plugin?** Own repo, named `mikser-io-<name>`. Composes
270
+ against `runtime.options.app` / `runtime.options.mcp` / lifecycle
271
+ hooks. Never imports another plugin's source.
272
+ - **New MCP tool?** Add to `mikser-io-mcp/index.js` via
273
+ `mcp.simpleTool(name, description, zodSchema, handler)`. Tool name
274
+ follows `mikser_*` convention.
275
+ - **New lifecycle hook?** Almost certainly no. Existing hooks cover
276
+ all known patterns. If you think you need one, post the use case
277
+ to ADR-0006 review.
278
+
279
+ ## Test suites
280
+
281
+ - `npm run test:unit` — 363 unit tests across plugins + utilities
282
+ - `npm run test:scenarios` — 18 subprocess-spawned end-to-end runs
283
+ (manifest skip, refs replay, watch-mode change/delete). Spawns
284
+ mikser fresh per scenario so module-level catalog/refs/manifest
285
+ state can't leak between tests.
286
+ - `npm run test:smoke` — full lifecycle build of `test/fixture/`
287
+ (with vector + decap + post-mjml + post-pdf if env supports).
288
+ Exercises both INLINE postprocess (PDF) and WORKER postprocess
289
+ (MJML via `task: worker` on `welcome.yml`).
290
+ - `npm run test:perf` — render-pipeline perf rig (corpus generation
291
+ + clean-build timing). `SIZE=realistic` + entity count knobs
292
+ documented in **Perf**.
293
+
294
+ ## Reference
295
+
296
+ - `documentation/architecture.md` — module map (audit before relying
297
+ on specifics; may have drift)
298
+ - `documentation/decisions/` — ADRs
299
+ - `documentation/configuration.md` — config reference
300
+ - `documentation/api-reference.md` — public API
301
+ - `test/perf/` — render-pipeline perf rig
302
+ - Sibling repos: `mikser-io-mcp`, `mikser-io-vector`, `mikser-io-schemas`,
303
+ `mikser-io-sdk-{api,react,svelte,vue,vector}`, `mikser-io-example-blog`
package/README.md CHANGED
@@ -36,7 +36,7 @@ Build mikser into the parts of your application that are content-shaped. Keep th
36
36
 
37
37
  **Incremental builds that scale.** Mikser tracks every entity in a journal. When a file changes, only the affected entities re-process — not the whole site graph. On 10k+ documents this dramatically outpaces tools that rebuild more on every change.
38
38
 
39
- **Concurrent rendering.** Renders fan out across a worker pool that keeps every CPU core hot. Multi-format outputs (HTML, PDF, MJML email, etc.) generate in parallel from the same source.
39
+ **Concurrent rendering.** Renders run async by default and CPU-heavy layouts (MJML compile, image processing, custom transforms) opt into a Piscina worker pool per layout via `task: worker` in frontmatter. Multi-format outputs (HTML, PDF, MJML email, etc.) generate from the same source; the pool is lazy — no workers spawn until they're asked for.
40
40
 
41
41
  **Asset pipelines are whatever Node can do.** Most static frameworks (Astro, Next.js, Hugo) ship image optimization and stop there — video transcoding, AI upscaling, watermarking all need a separate service. Mikser runs user-written modules over binary inputs: ~10 lines around `sharp` resize an image, ~10 around `fluent-ffmpeg` transcode a video, ~30 around the Replicate API upscale with AI. Anything an npm package can do, your pipeline can do — including pulling uploads from a DAM or CDN through the same flow.
42
42
 
@@ -208,8 +208,8 @@ For a working starter — config with a real plugin set, sample `documents/`, ex
208
208
  The shape mikser fits cleanly:
209
209
 
210
210
  - **Marketing sites with editorial teams** — content authors work in files (via their editor, a Git client, or `mikser-io-decap`), engineers ship features without negotiating with a CMS schema, the site stays portable.
211
- - **Multilingual publishing platforms** — the `useHref()` / `useAlternates()` pattern in `sdk-vue` decouples logical references from per-locale URLs. One source tree, many language deployments.
212
- - **Content-heavy product catalogues** — `documents` + `mikser-io-schemas` + `data` plugin + a Vue frontend = typed product listings with live updates, semantic search via `vector`, and static-CDN-friendly JSON snapshots all at once.
211
+ - **Multilingual publishing platforms** — the `useHref()` / `useAlternates()` pattern decouples logical references from per-locale URLs. One source tree, many language deployments.
212
+ - **Content-heavy product catalogues** — `documents` + `mikser-io-schemas` + `data` plugin + a Frontend Framework = typed product listings with live updates, semantic search via `vector`, and static-CDN-friendly JSON snapshots all at once.
213
213
  - **AI-augmented media pipelines** — `assets` plugin presets call out to Replicate / OpenAI / local models to upscale images, transcribe audio, transcode video. The pipeline is JS code, so anything Node can do is in scope.
214
214
  - **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.
215
215
  - **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.
@@ -225,11 +225,25 @@ What you get from how this project is built:
225
225
  - **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.
226
226
  - **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.
227
227
 
228
+ ## Mikser among static site generators
229
+
230
+ **The only SSG that's both fast enough for daily use and deep enough for AI agents to drive.**
231
+
232
+ | SSG | Speed | Feature surface | The tradeoff |
233
+ |---|---|---|---|
234
+ | Hugo | Fastest full rebuild | Minimal | Fast but limited; rebuilds everything every run |
235
+ | Eleventy | OK | Broad, no introspection | Flexible but slow at corpus scale |
236
+ | Astro | OK | Modern, framework-coupled | Tied to a frontend framework |
237
+ | Next.js SSG | meh | Full framework | Framework first, content second |
238
+ | **Mikser** | Incremental beats Hugo's full rebuild | Broad, deeply observable, AI-native | None of the speed-vs-features kind |
239
+
240
+ Every other SSG asks you to trade something — raw speed for features (Hugo), features for framework lock-in (Astro, Next), introspection for any of the above (Eleventy). Mikser doesn't make that trade. Hugo still wins a full cold rebuild — and that's worth knowing — but most cycles aren't full cold rebuilds. CI deploys, watch-mode edits, "ran mikser, nothing changed" — these are the daily case, where Mikser's persistent manifest skips what's still current while Hugo rebuilds everything from scratch. The rest of the feature surface (MCP introspection on every lifecycle phase, files-as-source-of-truth, 20-phase composability, multi-format outputs from one corpus) is what no other "fast" SSG carries.
241
+
228
242
  ## Acknowledgments
229
243
 
230
244
  The earliest version of mikser was inspired by [DocPad](https://github.com/docpad/docpad) (Benjamin Lupton, with Michael Duane Mooring and Rob Loach). DocPad's "freeway, not a box" philosophy — files on disk, any pre-processor or template engine, plugin-by-convention extension — shaped how mikser started.
231
245
 
232
- Mikser itself has a previous chapter: the [legacy 7.x line](https://github.com/almero-digital-marketing/mikser) (last release 2022) introduced the real-time SSG model the current engine still carries forward. The redesign dropped MongoDB (the catalog lives in-process now, not in a database), modernized to Node ESM with a structured 20-phase lifecycle, added the live SSE channel that powers the framework SDKs, and replaced cluster-based rendering with an async worker pool. Same intent — content as files, real-time previews, multi-format output at scale — clearer foundations.
246
+ Mikser itself has a previous chapter: the [legacy 7.x line](https://github.com/almero-digital-marketing/mikser) (last release 2022) introduced the real-time SSG model the current engine still carries forward. The redesign dropped MongoDB for a single in-process sqlite database, modernized to Node ESM with a structured 20-phase lifecycle, added the live SSE channel that powers the framework SDKs, and replaced cluster-based rendering with a lazy worker pool. Same intent — content as files, real-time previews, multi-format output at scale — clearer foundations.
233
247
 
234
248
  ## Documentation Index
235
249
 
package/index.js CHANGED
@@ -1,10 +1,13 @@
1
1
  export { default as runtime } from './src/runtime.js'
2
2
  export * as constants from './src/constants.js'
3
3
  export * from './src/utils.js'
4
- export * from './src/journal.js'
5
4
  export * from './src/lifecycle.js'
5
+ export * from './src/database/index.js'
6
+ export * from './src/journal.js'
6
7
  export * from './src/catalog.js'
7
8
  export * from './src/refs.js'
9
+ export * from './src/manifest.js'
10
+ export * from './src/track.js'
8
11
  export * from './src/subscriptions.js'
9
12
  export * from './src/config.js'
10
13
  export * from './src/plugins.js'
package/package.json CHANGED
@@ -1,14 +1,15 @@
1
1
  {
2
2
  "name": "mikser-io",
3
- "version": "8.2.0",
3
+ "version": "8.3.1",
4
4
  "description": "<p align=\"center\"> <img src=\"mikser-lockup-stacked.svg\" alt=\"mikser\" width=\"198\" /> </p>",
5
5
  "main": "index.js",
6
6
  "scripts": {
7
7
  "debug": "node --no-warnings app.js --server --watch --working-folder test/fixture",
8
8
  "test:unit": "node --test --test-reporter=spec 'test/unit/**/*.test.js'",
9
9
  "test:smoke": "node --no-warnings app.js --working-folder test/fixture",
10
+ "test:scenarios": "node --test --test-reporter=spec --test-timeout=60000 'test/scenarios/**/*.test.js'",
10
11
  "test:perf": "node test/perf/generate.js && node --no-warnings app.js --working-folder test/perf",
11
- "test": "npm run test:unit && npm run test:smoke"
12
+ "test": "npm run test:unit && npm run test:scenarios && npm run test:smoke"
12
13
  },
13
14
  "bin": {
14
15
  "mikser": "app.js"
@@ -24,6 +25,7 @@
24
25
  "@budibase/handlebars-helpers": "^0.14.3",
25
26
  "await-semaphore": "^0.1.3",
26
27
  "axios": "^1.17.0",
28
+ "better-sqlite3": "^12.10.0",
27
29
  "chokidar": "^5.0.0",
28
30
  "commander": "^15.0.0",
29
31
  "cors": "^2.8.5",
@@ -39,7 +41,6 @@
39
41
  "is-url": "^1.2.4",
40
42
  "line-reader": "^0.4.0",
41
43
  "lodash": "^4.18.1",
42
- "lowdb": "^7.0.1",
43
44
  "minimatch": "^10.2.5",
44
45
  "node-cron": "^4.2.1",
45
46
  "p-map": "^7.0.4",