mikser-io 8.2.0 → 8.3.0
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 +303 -0
- package/README.md +16 -2
- package/index.js +4 -1
- package/package.json +4 -3
- package/src/catalog.js +580 -201
- package/src/database/index.js +251 -0
- package/src/database/query-context.js +18 -0
- package/src/database/sift-to-sql.js +201 -0
- package/src/engine.js +275 -88
- package/src/journal.js +287 -78
- package/src/manifest.js +542 -0
- package/src/plugins/api.js +100 -36
- package/src/plugins/data.js +72 -27
- package/src/plugins/front-matter.js +1 -3
- package/src/plugins/json.js +6 -8
- package/src/plugins/layouts.js +241 -94
- package/src/plugins/mapper.js +1 -3
- package/src/plugins/observer.js +7 -7
- package/src/plugins/preview.js +59 -6
- package/src/plugins/render/hbs.js +64 -8
- package/src/plugins/render/href.js +3 -6
- package/src/plugins/resources.js +1 -3
- package/src/plugins/yaml.js +1 -3
- package/src/postprocess.js +21 -4
- package/src/refs.js +392 -242
- package/src/render.js +62 -3
- package/src/source.js +202 -13
- package/src/track.js +63 -0
- package/src/utils.js +77 -3
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
|
|
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
|
|
|
@@ -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
|
|
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.
|
|
3
|
+
"version": "8.3.0",
|
|
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",
|