Initial Commit

This commit is contained in:
2026-05-27 23:29:53 +01:00
commit fbc70f96e6
10000 changed files with 1102152 additions and 0 deletions

View File

@@ -0,0 +1,131 @@
# Add @suaveplan/api-plugin-cms
**Package**: `@suaveplan/api-plugin-cms`
**Status**: PROPOSED
**Type**: new-package
## Why
Headless-CMS primitives for `@suaveplan/api` applications. The upstream
package on main (`/mnt/s/web/suaveplan/packages/server/api-plugin-cms`)
ships a `CmsEngine` that manages flexible `ContentModel` definitions,
`ContentEntry` records with `draft → review → published → archived`
status transitions guarded by `VALID_STATUS_TRANSITIONS`, versioned
`ContentVersion` history, slug uniqueness, an RFC 7807 problem+json
helper, and a `CmsError` hierarchy registered into the shared error
metadata registry on module load. Genesis needs that surface ported at
full functional parity so downstream `@suaveplan/api` apps can author
content models, validate entries, and walk the status lifecycle without
reaching for main.
## What changes
- ADDED: `CmsEngine` — in-memory CMS core managing models, entries, and
version history; emits eight typed events through `@suaveplan/events`.
- ADDED: `CmsError`, `CMS_CODES`, `CmsErrorCode` — `SuaveplanError`
subclass and seven stable error codes (`CONTENT_NOT_FOUND`,
`MODEL_NOT_FOUND`, `INVALID_TRANSITION`, `VALIDATION_FAILED`,
`DUPLICATE_SLUG`, `MODEL_IN_USE`, `INVALID_INPUT`). The first six are
verbatim upstream; `INVALID_INPUT` is raised by the new
`createCmsPlugin` factory when Zod boundary validation fails (genesis
convention, mirrors `api-plugin-audit` / `api-plugin-database`).
- ADDED: `contentNotFound`, `modelNotFound`, `invalidStatusTransition`,
`validationFailed`, `duplicateSlug`, `modelInUse` — error-factory
helpers.
- ADDED: `respondProblemJson`, `formatProblemJson`,
`PROBLEM_JSON_CONTENT_TYPE`, `HttpResponseLike`, `ProblemDetails` —
RFC 7807 problem+json rendering.
- ADDED: `ContentStatus`, `ContentType`, `FieldType`, `ContentField`,
`FieldValidation`, `ContentModel`, `ContentEntry`, `ContentVersion`,
`CreateContentInput`, `UpdateContentInput`, `ContentQuery`,
`CmsEventMap` — content-model types.
- ADDED: `generateSlug`, `validateContentData`,
`VALID_STATUS_TRANSITIONS` — utility surface.
- ADDED: `createCmsPlugin` — `definePlugin()` factory that registers
the constructed `CmsEngine` as the `cms` slot on the host
`ServiceRegistry`, matching the genesis api-plugin convention.
- ADDED: `CmsPluginOptionsSchema`, `CmsPluginOptions`, `CmsPluginConfig`
— Zod-validated plugin options bundle (injectable
`Clock` / `IdGenerator`).
- ADDED: `CmsInstruments`, `getCmsInstruments`, `resetCmsInstruments` —
OTel meter bundle (content created / updated / published / archived /
deleted counters; model created counter; validation-failure counter).
## Out of scope
- SurrealDB storage adapter — upstream is in-memory only; matched.
- HTTP route mounting on `app` — upstream ships `respondProblemJson` but
no actual routes; matched.
- Auth/role gating, audit-log integration, webhook/preview tokens,
rich-text/media handling, full-text search hook — every item listed
under §4 of `enterprise/api-plugin-cms.md` is an aspirational future
enhancement, not in the upstream baseline.
- Counter-based ID replacement with ULID — kept as an injectable
`IdGenerator` so callers wanting ULID supply it; default mirrors
upstream `cms_<n>_<ts>` for byte-identical parity.
## Source mapping
This change derives from
`../suaveplan/enterprise/api-plugin-cms.md` and the upstream source at
`../suaveplan/packages/server/api-plugin-cms/`.
| Requirement | Source lines | Notes |
|---|---|---|
| `CmsEngine` lifecycle (`createModel`, `getModel`, `listModels`, `deleteModel`) | upstream `src/routes/cms-engine.ts` L38–L70 | verbatim port — same method bodies, adapted to `EventBus<CmsEventMap>` instead of upstream `EventEmitter` |
| Content entry CRUD (`createContent`, `getContent`, `queryContent`, `updateContent`, `deleteContent`) | upstream `src/routes/cms-engine.ts` L71–L210 | verbatim port — query filtering / sorting / paging preserved |
| Status-transition guard via `VALID_STATUS_TRANSITIONS` | upstream `src/routes/cms-engine.ts` L179–L202 + `src/types/cms.types.ts` L134–L142 | verbatim |
| Versioned history (`getVersions`, `saveVersion`) | upstream `src/routes/cms-engine.ts` L213–L230 | verbatim |
| `CmsError` + six factory helpers | upstream `src/errors/cms-errors.ts` L1–L71 | adapted from `BaseError<Code, Context>` to genesis `SuaveplanError<Code>` (genesis has no second-generic context — context lives on `options.context`) |
| Stable code registry (`CMS_CODES`) | upstream `src/errors/error-codes.ts` L20–L74 | adapted from upstream `ErrorCodes.registerMany(metadata)` (no equivalent in genesis `@suaveplan/error`) to `defineCodes("CMS", [...])`; HTTP-status metadata folded into the problem-json renderer (mirrors `api-plugin-database`) |
| Side-effect import of code registry on barrel load | upstream `src/index.ts` L3 | matched — `src/index.ts` side-effect-imports `./error-codes/error-codes.js` |
| `respondProblemJson`, `PROBLEM_JSON_CONTENT_TYPE` | upstream `src/routes/problem-json.ts` L1–L47 | adapted — genesis `@suaveplan/error` does not yet ship `HttpError` / `formatErrorAsProblemJson` / `ProblemDetails`, so a local `formatProblemJson` mirrors `api-plugin-database`'s pattern |
| `ContentStatus`, `ContentType`, `FieldType`, `ContentField`, `FieldValidation`, `ContentModel`, `ContentEntry`, `ContentVersion`, `CreateContentInput`, `UpdateContentInput`, `ContentQuery`, `CmsEventMap` | upstream `src/types/cms.types.ts` L1–L131 | verbatim |
| `generateSlug` | upstream `src/types/cms.types.ts` L145–L152 | verbatim |
| `validateContentData` + private `validateField` | upstream `src/types/cms.types.ts` L155–L230 | verbatim |
| `VALID_STATUS_TRANSITIONS` | upstream `src/types/cms.types.ts` L134–L142 | verbatim |
### Requirements with no source line
| Requirement | Justification |
|---|---|
| `createCmsPlugin` factory + `cms` ServiceRegistry slot | Every other genesis `api-plugin-*` ships a `definePlugin()` factory wiring its primary service into the registry. Matching the convention costs <50 lines and aligns with `enterprise/api-plugin-cms.md` §3 "Clarify identity → wrap in `definePlugin()`". |
| `CmsPluginOptionsSchema` (Zod) | Genesis-wide policy: every public option bundle is Zod-validated at the boundary (CLAUDE.md). |
| `Clock` / `IdGenerator` injection | Genesis-wide policy: no `new Date()` in hot paths, no non-deterministic ID generation in tests (CLAUDE.md "Code Organization"). Default factory mirrors upstream behavior byte-for-byte. |
| OTel instrument bundle | Genesis-wide policy: every plugin emits OTel metrics from its production hot path and asserts them with an in-memory `MeterProvider`. |
| 100% test coverage | Genesis-wide policy, not package-specific. |
## Verification approach
Every requirement in the spec delta is pinned by concrete `it(...)`
titles. Test files that will exist after this change ships:
- `src/engine/cms-engine.test.ts` — covers model + content CRUD,
query filtering / sorting / paging, status transitions, version
history, event emission, and every error-throwing path.
- `src/engine/cms-engine.spec-conformance.test.ts` — pins type-level
claims (`expectTypeOf` on `CmsEngine`'s event map), determinism with
injected `Clock` / `IdGenerator`, and the full `CMS_CODES` enumeration.
- `src/error-codes/error-codes.test.ts` — every factory raises the
expected code; `cause` chain is preserved; `instanceof CmsError`
narrows.
- `src/routes/problem-json.test.ts` — covers `CmsError` → 4xx/409
mapping, generic `SuaveplanError` → 500, non-error throwables → 500.
- `src/schemas/options.test.ts` — Zod boundary accept/reject,
default-fill behavior.
- `src/plugin/plugin.test.ts` — factory returns valid `PluginDescriptor`,
`onInit` registers `cms` in the `ServiceRegistry`, `onClose` clears it,
invalid options raise `CMS_CODES.INVALID_INPUT`.
- `src/otel/instruments.test.ts` — instruments are emitted from the
engine and observed via in-memory `MeterProvider`.
- `src/types/cms.types.test.ts` — `generateSlug`, `validateContentData`,
`VALID_STATUS_TRANSITIONS` shape.
- `src/api-plugin-cms.spec-conformance.test.ts` — every `CMS.*` code is
thrown from at least one production path; barrel side-effect-imports
the code registry.
- `tests/dep-graph.test.ts` — pins the eight runtime workspace deps and
forbids drift.
## Migration impact
None — additive only. New package; no existing consumers.