mikser-io 6.23.0 → 6.23.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/README.md CHANGED
@@ -4,9 +4,13 @@
4
4
 
5
5
  # Mikser
6
6
 
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. The SDKs are the seam between them.
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.
8
8
 
9
- Built for Node.js around a strict lifecycle, a composable plugin system, and direct control over every output. Every document, asset, and template flows through the same deterministic pipeline. Plugins hook in at any phase; nothing runs outside the cycle. It scales from a single markdown blog to a multi-language, multi-format publishing platform — and stays predictable in both directions.
9
+ Built for Node.js around a strict lifecycle, a composable plugin system, and direct control over every output. Every document, asset, and template flows through the same deterministic pipeline. Plugins hook in at any phase; nothing runs outside the cycle. It scales from a single markdown blog to a multi-language, multi-format publishing platform with image / video / AI pipelines, live SSE-driven editors, semantic search, and typed frontend contracts — and stays predictable in both directions.
10
+
11
+ It's MIT-licensed, runs on Node 18+, 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.**
12
+
13
+ > **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.
10
14
 
11
15
  ## Where it fits
12
16
 
@@ -30,6 +34,8 @@ Build mikser into the parts of your application that are content-shaped. Keep th
30
34
 
31
35
  **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.
32
36
 
37
+ **Image, video, and AI pipelines authored as plugins — not configured.** The `assets` plugin runs user-written preset modules over binary inputs. A preset is a plain Node module: ~10 lines around `sharp` resize an image, ~10 lines around `fluent-ffmpeg` transcode a video, ~30 lines around the Replicate API run an AI upscaler. The pipeline isn't a fixed menu of operations — it's whatever Node can call. Most SSGs cap your asset processing at "resize and convert format." Mikser caps it at "what can Node do." Compose with the `resources` plugin and your DAM, CDN, or company content server becomes an upstream input — uploaded files end up transcoded, watermarked, AI-enhanced, and deployed without manual handoff. See [the assets / resources docs](./documentation/plugins.md#assets) for end-to-end examples.
38
+
33
39
  **One lifecycle, everything composes.** Plugins hook into 20+ named lifecycle phases. A search-indexing plugin shares the same journal iteration as an email-rendering plugin and a PDF-postprocessing plugin — no glue code, no orchestration layer. The engine doesn't know which plugins are loaded; plugins don't have to know about each other.
34
40
 
35
41
  **Run anywhere.** The same CLI handles one-shot builds, watch-mode dev loops, and a long-running HTTP server with a shared Express app. `npx mikser` ships a static site; `mikser --watch` is the dev loop; `mikser --server` exposes a live admin/API.
@@ -80,7 +86,8 @@ The engine is what stays stable — the lifecycle, the catalog, the file-based c
80
86
 
81
87
  | Plugin | What it does |
82
88
  |---|---|
83
- | `vector` | OpenAI embeddings + semantic search (sqlite-vec or pgvector) |
89
+ | [`mikser-io-vector`](https://github.com/almero-digital-marketing/mikser-io-vector) | OpenAI embeddings + semantic search (sqlite-vec or pgvector) |
90
+ | [`mikser-io-plugin-schemas`](https://github.com/almero-digital-marketing/mikser-io-plugin-schemas) | Zod-backed entity validation + auto-generated TypeScript declarations for the SDK |
84
91
  | `archive`, `mapper`, `live`, `aml` | Specialty integrations |
85
92
 
86
93
  **Integration probes** — wrap a substantial external project as a plugin to confirm the lifecycle is open enough to host it without core changes. Treat these as feasibility evidence, not as a statement about where mikser is heading:
@@ -91,14 +98,24 @@ The engine is what stays stable — the lifecycle, the catalog, the file-based c
91
98
 
92
99
  ## Client SDKs
93
100
 
94
- The `api` and `vector` plugins are paired with small client-side SDKs so a frontend (or another Node app) can talk to a running mikser server without rolling its own `fetch` glue. Zero dependencies, runs in browsers / Node 18+ / Deno / Bun / Workers.
101
+ The `api`, `vector`, and `schemas` plugins are paired with client-side SDKs so a frontend (or another Node app) can talk to a running mikser server without rolling its own `fetch` glue or type contracts. Zero dependencies, runs in browsers / Node 18+ / Deno / Bun / Workers.
102
+
103
+ **Transport-level:**
95
104
 
96
105
  | Package | For the plugin | What you get |
97
106
  |---|---|---|
98
- | [`mikser-io-sdk-api`](https://github.com/almero-digital-marketing/mikser-io-sdk-api) | `api` | `entities(name).list / query / urlFor / pages / update / delete / render` — Mongo-style filter operators backed by sift, sort, projection, pagination |
107
+ | [`mikser-io-sdk-api`](https://github.com/almero-digital-marketing/mikser-io-sdk-api) | `api` | `entities(name).list / query / urlFor / pages / update / delete / render / live` — Mongo-style filter operators backed by sift, sort, projection, pagination, SSE-driven live subscriptions |
99
108
  | [`mikser-io-sdk-vector`](https://github.com/almero-digital-marketing/mikser-io-sdk-vector) | `vector` | `vector(storeName).findSimilar(text, { limit })` — semantic search hits with the original mapped object attached |
100
109
 
101
- Each SDK ships TypeScript declarations so client projects get autocomplete on filters, envelopes, and the `MikserError` thrown on non-2xx responses. Install only the one(s) a project needs.
110
+ **Framework integrations** — all three wrap `mikser-io-sdk-api` in framework-idiomatic shapes. Same surface: `useDocument` / `useDocuments` live data, multilingual `useHref` / `useAlternates`, asset resolution via `useAsset`, generic on entity type so `mikser-io-plugin-schemas`-emitted types compose:
111
+
112
+ | Package | Framework | Notes |
113
+ |---|---|---|
114
+ | [`mikser-io-sdk-vue`](https://github.com/almero-digital-marketing/mikser-io-sdk-vue) | Vue 3 | Composables, vue-router integration (`createMikserRouter`, `generateMikserRoutes`), provide/inject for the client. |
115
+ | [`mikser-io-sdk-react`](https://github.com/almero-digital-marketing/mikser-io-sdk-react) | React 18+ / 19+ | Hooks, `<MikserProvider>` Context, React Router v6+ integration via `useMikserRoutes` → `useRoutes()`. |
116
+ | [`mikser-io-sdk-svelte`](https://github.com/almero-digital-marketing/mikser-io-sdk-svelte) | Svelte 5 (runes) | `$state` / `$effect` reactives, SvelteKit-friendly `generateMikserRoutes` for `entries()` prerender, `useMikserPages` for live nav. |
117
+
118
+ Each SDK ships TypeScript declarations so client projects get autocomplete on filters, envelopes, and the `MikserError` thrown on non-2xx responses. Pair any of the framework SDKs with the `entities.d.ts` emitted by `mikser-io-plugin-schemas` for typed entity meta per layout. Install only the one(s) a project needs.
102
119
 
103
120
  ## Quick Start
104
121
 
@@ -130,19 +147,43 @@ npx mikser --server # build + serve at :3001
130
147
  - **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.
131
148
  - **Watch Mode** — In watch mode, file changes trigger incremental re-processing without restarting.
132
149
 
150
+ ## What you can build with it
151
+
152
+ The shape mikser fits cleanly:
153
+
154
+ - **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.
155
+ - **Multilingual publishing platforms** — the `useHref()` / `useAlternates()` pattern in `sdk-vue` decouples logical references from per-locale URLs. One source tree, many language deployments.
156
+ - **Content-heavy product catalogues** — `documents` + `mikser-io-plugin-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.
157
+ - **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.
158
+ - **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.
159
+ - **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.
160
+
161
+ 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.
162
+
163
+ ## Engineering discipline
164
+
165
+ A few things this project takes seriously:
166
+
167
+ - **ADRs for load-bearing decisions.** The [`decisions/`](./documentation/decisions/) folder names which choices are structural — files-as-source, journal+catalog split, plugin-as-factory, compose-via-protocols — and explains what protects them. Read those before proposing a feature that pushes against one.
168
+ - **Engine stability, plugin churn.** The 15+ plugins in the ecosystem add capability without core changes. The integration probes (e.g. `decap`, mounting a third-party CMS in ~150 lines) are deliberate evidence that the extension model holds.
169
+ - **Deterministic builds.** The journal is the synchronization primitive. There's no event-passing layer, no IoC container, no plugin orchestrator. The engine knows how to fire phases in order; everything else falls out of that.
170
+ - **The mental model is one document.** The [Architecture Overview](./documentation/overview.md) is one read for the full top-to-bottom picture. The reference docs exist for lookup; the overview exists for comprehension.
171
+
133
172
  ## Documentation Index
134
173
 
135
174
  | Document | Audience | Description |
136
175
  | ----------------------------------------------------- | ------------------ | -------------------------------------------------- |
176
+ | [Architecture Overview](./documentation/overview.md) | Everyone | **Start here.** End-to-end walkthrough of how a file becomes a deployed page across all lifecycle phases. |
137
177
  | [Getting Started](./documentation/getting-started.md) | Users | Installation, first project, basic usage |
138
178
  | [Configuration](./documentation/configuration.md) | Users | All CLI options and config file reference |
139
179
  | [Lifecycle](./documentation/lifecycle.md) | Users & Developers | Complete lifecycle phases and hook system |
140
- | [Plugins](./documentation/plugins.md) | Users & Developers | Built-in plugins, writing custom plugins |
180
+ | [Plugins](./documentation/plugins.md) | Users & Developers | Built-in plugins, writing custom plugins, the assets / resources / AI pipeline |
141
181
  | [Entities](./documentation/entities.md) | Users & Developers | Entity model, operations, journal, catalog |
142
182
  | [Rendering](./documentation/rendering.md) | Users & Developers | Render pipeline, render plugins, render modes |
143
183
  | [Watch Mode](./documentation/watch-mode.md) | Users | File watching, scheduled tasks, incremental builds |
144
- | [Architecture](./documentation/architecture.md) | Developers | System design, module structure, extension points |
184
+ | [Architecture](./documentation/architecture.md) | Developers | Module-level reference — what's in each file |
145
185
  | [API Reference](./documentation/api-reference.md) | Developers | Complete public API reference |
186
+ | [Decisions (ADRs)](./documentation/decisions/) | Developers | Load-bearing architectural choices and what protects them |
146
187
 
147
188
  ## License
148
189
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mikser-io",
3
- "version": "6.23.0",
3
+ "version": "6.23.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": {
@@ -289,6 +289,7 @@ export default ({
289
289
  const { render } = useRenderer(runtime, { defaultTimeout: renderTimeout })
290
290
 
291
291
  router.get('/entities', allow('list'), auth, async (req, res) => {
292
+ const t0 = Date.now()
292
293
  try {
293
294
  const parsed = parseQueryString(req.query)
294
295
  const limit = Math.min(100, Math.max(1, parsed.limit ?? pageSize))
@@ -311,8 +312,9 @@ export default ({
311
312
  hasNext: skip + limit < total,
312
313
  hasPrev: skip > 0,
313
314
  })
315
+ logger.trace('Api[%s] list %dms (%d/%d items)', name, Date.now() - t0, items.length, total)
314
316
  } catch (err) {
315
- logger.error('Api[%s] list error: %s', name, err.message)
317
+ logger.error('Api[%s] list error (%dms): %s', name, Date.now() - t0, err.message)
316
318
  res.status(500).json({ error: err.message })
317
319
  }
318
320
  })
@@ -321,6 +323,7 @@ export default ({
321
323
  // doesn't fit cleanly in a URL: $and/$or, nested operators,
322
324
  // regex, projections, etc. Same shape as a Mongo find.
323
325
  router.post('/entities/query', allow('list'), auth, async (req, res) => {
326
+ const t0 = Date.now()
324
327
  try {
325
328
  const { filter = {}, sort, fields, page: rawPage = 1, limit: rawLimit, skip: rawSkip } = req.body ?? {}
326
329
  const limit = Math.min(100, Math.max(1, rawLimit ?? pageSize))
@@ -339,8 +342,9 @@ export default ({
339
342
  hasNext: skip + limit < total,
340
343
  hasPrev: skip > 0,
341
344
  })
345
+ logger.trace('Api[%s] query %dms (%d/%d items)', name, Date.now() - t0, items.length, total)
342
346
  } catch (err) {
343
- logger.error('Api[%s] query error: %s', name, err.message)
347
+ logger.error('Api[%s] query error (%dms): %s', name, Date.now() - t0, err.message)
344
348
  res.status(500).json({ error: err.message })
345
349
  }
346
350
  })