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 +49 -8
- package/package.json +1 -1
- package/src/plugins/api.js +6 -2
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.
|
|
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 `
|
|
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
|
-
|
|
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 |
|
|
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
package/src/plugins/api.js
CHANGED
|
@@ -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
|
})
|