Initial Commit
This commit is contained in:
117
packages/api-stack/api/api-plugin-cms/src/engine/cms-engine.md
Normal file
117
packages/api-stack/api/api-plugin-cms/src/engine/cms-engine.md
Normal file
@@ -0,0 +1,117 @@
|
||||
# `CmsEngine`
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
`CmsEngine` is the headless content-management core for
|
||||
`@suaveplan/api-plugin-cms`. It manages flexible content models,
|
||||
content entries with a `draft → review → published → archived` status
|
||||
lifecycle, slug uniqueness within a model, and a per-entry version
|
||||
trail. The engine is in-memory by design — three `Map`s back its
|
||||
state — so it is suitable for development, testing, and small-scale
|
||||
single-process deployments. Production-scale persistence is a downstream
|
||||
concern; the engine ships a stable surface that a future Surreal-backed
|
||||
storage port can wrap without modifying any caller code.
|
||||
|
||||
State changes flow through a typed `EventBus<CmsEventMap>` and an
|
||||
OpenTelemetry counter bundle so observability is free at the call site.
|
||||
Two abstractions are injectable for determinism: `Clock` drives every
|
||||
timestamp and `IdGenerator` drives every persisted identifier. Both
|
||||
default to the upstream Suaveplan main behaviour, so test fixtures
|
||||
captured against main remain byte-reproducible against this port.
|
||||
|
||||
## Features
|
||||
|
||||
- Content-model CRUD (`createModel`, `getModel`, `listModels`,
|
||||
`deleteModel`) with referential-integrity guard against orphaning
|
||||
entries.
|
||||
- Content-entry CRUD (`createContent`, `getContent`, `queryContent`,
|
||||
`updateContent`, `deleteContent`) with `(modelId, slug)` uniqueness
|
||||
and `validateContentData` enforcement on create and update.
|
||||
- `queryContent` supports filter by `modelId`, `status`, `locale`,
|
||||
case-insensitive `search` over `title`/`slug`, sort by an arbitrary
|
||||
field with `asc`/`desc`, and `offset`/`limit` paging.
|
||||
- Status transitions consult `VALID_STATUS_TRANSITIONS` and emit one
|
||||
of `content.published` / `content.drafted` / `content.archived` /
|
||||
fallback `content.updated`.
|
||||
- Version history retained per entry; `getVersions` returns the full
|
||||
trail, deletion drops it.
|
||||
- OTel counters fire from every mutating method.
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
```ts
|
||||
import { CmsEngine } from "@suaveplan/api-plugin-cms";
|
||||
|
||||
const engine = new CmsEngine();
|
||||
const model = engine.createModel({
|
||||
name: "Blog post",
|
||||
slug: "post",
|
||||
contentType: "post",
|
||||
fields: [
|
||||
{ name: "title", type: "text", label: "Title", required: true },
|
||||
{ name: "body", type: "richtext", label: "Body" },
|
||||
],
|
||||
});
|
||||
const entry = engine.createContent({
|
||||
modelId: model.id,
|
||||
title: "Hello world",
|
||||
data: { title: "Hello world", body: "Welcome." },
|
||||
});
|
||||
engine.transitionStatus(entry.id, "published");
|
||||
console.log(engine.queryContent({ status: "published" }));
|
||||
```
|
||||
|
||||
## Usage: Advanced
|
||||
|
||||
Inject a `Clock` and `IdGenerator` for deterministic test fixtures, and
|
||||
subscribe to the typed event bus to fan-out into downstream systems
|
||||
(audit log, search indexer, webhook dispatcher):
|
||||
|
||||
```ts
|
||||
import { CmsEngine } from "@suaveplan/api-plugin-cms";
|
||||
import { EventBus } from "@suaveplan/events";
|
||||
|
||||
let n = 0;
|
||||
const engine = new CmsEngine({
|
||||
clock: () => 1_700_000_000_000,
|
||||
idGenerator: () => `ulid_${++n}`,
|
||||
events: new EventBus(),
|
||||
});
|
||||
engine.events.on("content.published", (entry) => {
|
||||
queueWebhook("content.published", entry);
|
||||
});
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
| Member | Description |
|
||||
|---|---|
|
||||
| `new CmsEngine(options?)` | Construct the engine. Optional `clock`, `idGenerator`, `events`. |
|
||||
| `engine.events` | The underlying `EventBus<CmsEventMap>` — subscribe with `.on(...)`. |
|
||||
| `createModel(input)` | Register a new model. Emits `model.created`. |
|
||||
| `getModel(id)` | Look up by id. Throws `CMS.MODEL_NOT_FOUND` on miss. |
|
||||
| `listModels()` | Snapshot of every registered model. |
|
||||
| `deleteModel(id)` | Remove a model. Throws `CMS.MODEL_IN_USE` if entries exist. |
|
||||
| `createContent(input)` | Validate + persist + version + emit `content.created`. |
|
||||
| `getContent(id)` | Look up entry. Throws `CMS.CONTENT_NOT_FOUND` on miss. |
|
||||
| `queryContent(query?)` | Filter / sort / page entries. |
|
||||
| `updateContent(id, input)` | Merge data; re-validate; version; emit `content.updated`. |
|
||||
| `transitionStatus(id, target)` | Status FSM step. Throws `CMS.INVALID_TRANSITION` on illegal edge. |
|
||||
| `deleteContent(id)` | Drop entry + versions; emit `content.deleted`. |
|
||||
| `getVersions(entryId)` | Full version trail. |
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The engine uses three `Map`s (`models`, `entries`, `versions`). The
|
||||
default `IdGenerator` produces `cms_<n>_<clock()>` strings — verbatim
|
||||
from upstream so byte-level test fixtures survive. The default `Clock`
|
||||
is `() => Date.now()`. `EventBus<CmsEventMap>` adapts to the
|
||||
single-payload suaveplan convention (`Record<string, unknown>`); upstream
|
||||
used tuple events, but the listener-side argument shape is identical.
|
||||
OTel counters are resolved lazily inside `getCmsInstruments()` so test
|
||||
harnesses installing a custom `MeterProvider` first capture every
|
||||
observation.
|
||||
Reference in New Issue
Block a user