@thebes/cadmus 0.4.1 → 0.4.2
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/dist/cms/index.cjs +42 -0
- package/dist/cms/index.cjs.map +1 -1
- package/dist/cms/index.d.cts +2 -2
- package/dist/cms/index.d.ts +2 -2
- package/dist/cms/index.js +40 -1
- package/dist/cms/index.js.map +1 -1
- package/dist/hono/index.d.cts +1 -1
- package/dist/hono/index.d.ts +1 -1
- package/dist/{index-CWrSoiPh.d.ts → index-D4BRqT26.d.cts} +37 -2
- package/dist/{index-CWrSoiPh.d.cts.map → index-D4BRqT26.d.cts.map} +1 -1
- package/dist/{index-CWrSoiPh.d.cts → index-D4BRqT26.d.ts} +37 -2
- package/dist/{index-CWrSoiPh.d.ts.map → index-D4BRqT26.d.ts.map} +1 -1
- package/dist/index.cjs +3 -0
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/package.json +1 -1
package/dist/cms/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["toSnakeCase","countRows"],"sources":["../../src/cms/blocks.ts","../../src/cms/types.ts","../../src/cms/codegen.ts","../../src/cms/defineCollection.ts","../../src/cms/patch.ts","../../src/cms/validation.ts","../../src/cms/localApi.ts","../../src/cms/meta.ts","../../src/cms/migrate.ts","../../src/cms/schema-gen.ts","../../src/cms/structure.ts","../../src/cms/visual-editing.ts","../../src/cms/webhooks.ts"],"sourcesContent":["// Copyright (c) 2026 BowenLabs. All rights reserved.\n// Cadmus is MIT licensed. See LICENSE in the repo root.\n\n/**\n * Renderer registry for block/document content (issue #13) — adopts the\n * Portable Text + `@portabletext/react` pattern (idea, not code): a content\n * document is a serializable **array of typed blocks**, and rendering is a\n * lookup of `block.type → renderer`, not a hand-rolled `switch`. Adding a\n * new block type becomes \"register a renderer\" with no edit to a central\n * branch.\n *\n * Framework-agnostic on purpose: `TRenderer` is whatever a host wants a\n * block's renderer to be — a string-producing function (SSR/preview HTML),\n * an Astro component, a Solid component, etc. The registry only does lookup,\n * fallback, and introspection; it never assumes a rendering technology.\n */\n\n/**\n * The minimal shape every block must have: a string discriminant under\n * `type`. (Portable Text uses `_type`; Cadmea content is TipTap-JSON-shaped\n * and already keyed on `type`, so the registry keys on `type` to stay\n * drop-in with stored content — the editor can stay TipTap.)\n */\nexport interface PortableBlockLike {\n type: string;\n}\n\nexport interface BlockRegistry<TRenderer> {\n /** Register (or replace) the renderer for a block type. Chainable. */\n register(type: string, renderer: TRenderer): BlockRegistry<TRenderer>;\n /** Register several at once. Chainable. */\n registerMany(renderers: Record<string, TRenderer>): BlockRegistry<TRenderer>;\n /** The renderer registered for `type`, or `undefined`. */\n get(type: string): TRenderer | undefined;\n /** Whether a renderer is registered for `type`. */\n has(type: string): boolean;\n /** Every registered block type, in registration order. */\n types(): string[];\n /** Set the fallback used when a type has no registered renderer. Chainable. */\n setFallback(renderer: TRenderer): BlockRegistry<TRenderer>;\n /** The renderer for `type`, else the fallback, else `undefined`. */\n resolve(type: string): TRenderer | undefined;\n}\n\n/**\n * Create a block renderer registry. Seed it with an initial `type → renderer`\n * map and/or an `options.fallback` for unknown types.\n *\n * ```ts\n * const registry = createBlockRegistry<StringBlockRenderer>({\n * divider: () => \"<hr>\",\n * });\n * registry.register(\"hero\", (b) => `<h1>${b.heading}</h1>`);\n * renderBlocksToString(blocks, registry);\n * ```\n */\nexport function createBlockRegistry<TRenderer>(\n initial: Record<string, TRenderer> = {},\n options: { fallback?: TRenderer } = {},\n): BlockRegistry<TRenderer> {\n const renderers = new Map<string, TRenderer>(Object.entries(initial));\n let fallback = options.fallback;\n\n const registry: BlockRegistry<TRenderer> = {\n register(type, renderer) {\n renderers.set(type, renderer);\n return registry;\n },\n registerMany(map) {\n for (const [type, renderer] of Object.entries(map)) {\n renderers.set(type, renderer);\n }\n return registry;\n },\n get(type) {\n return renderers.get(type);\n },\n has(type) {\n return renderers.has(type);\n },\n types() {\n return [...renderers.keys()];\n },\n setFallback(renderer) {\n fallback = renderer;\n return registry;\n },\n resolve(type) {\n return renderers.get(type) ?? fallback;\n },\n };\n return registry;\n}\n\n/**\n * A renderer that turns one block into an HTML string — the registry value\n * type for SSR/preview paths that build markup as strings (e.g. a Hono\n * preview route) rather than mounting components.\n */\nexport type StringBlockRenderer<\n TBlock extends PortableBlockLike = PortableBlockLike,\n> = (block: TBlock) => string;\n\n/**\n * Render an array of blocks to a single HTML string via a registry of\n * {@link StringBlockRenderer}s. Blocks whose type resolves to no renderer\n * (and no fallback) contribute the empty string — the same forgiving\n * behavior the old hand-rolled `switch` had for unknown types.\n */\nexport function renderBlocksToString<TBlock extends PortableBlockLike>(\n blocks: readonly TBlock[],\n registry: BlockRegistry<StringBlockRenderer<TBlock>>,\n): string {\n return blocks\n .map((block) => {\n const renderer = registry.resolve(block.type);\n return renderer ? renderer(block) : \"\";\n })\n .join(\"\");\n}\n","// Copyright (c) 2026 BowenLabs. All rights reserved.\n// Cadmus is MIT licensed. See LICENSE in the repo root.\n\nimport type { ValidationBuilder } from \"./validation.js\";\n\n/**\n * Editor-only presentation hints for a single field (issue #16 follow-on) —\n * the field-level counterpart to {@link CollectionAdminConfig}. Purely about\n * how the studio renders the field; absent → sensible defaults (a humanized\n * key for the label, no help text, full width, always shown, editable). None\n * of this touches the DB schema or the Local API.\n */\nexport interface FieldAdminConfig {\n /** Human-friendly label; defaults to a humanized field key (`metaDescription` → \"Meta description\"). */\n label?: string;\n /** Help text rendered beneath the label. */\n description?: string;\n /** Placeholder for text-like inputs. */\n placeholder?: string;\n /** Groups the field into a titled fieldset in the editor, in first-seen order. */\n group?: string;\n /** Editor column width on >= md screens. Defaults to \"full\". */\n width?: \"full\" | \"half\";\n /**\n * Show the field only when this predicate — given the whole in-progress\n * form value — returns true (Payload's `admin.condition`). A function, so\n * it's evaluated by the studio directly from the imported config, not from\n * a serialized meta payload.\n */\n condition?: (values: Record<string, unknown>) => boolean;\n /** Render the field read-only in the editor. */\n readOnly?: boolean;\n}\n\nexport interface BaseFieldConfig {\n /** column name override; defaults to the config key */\n name?: string;\n required?: boolean;\n unique?: boolean;\n defaultValue?: unknown;\n /** Editor-only presentation hints — see {@link FieldAdminConfig}. */\n admin?: FieldAdminConfig;\n /**\n * Chainable validation rules (issue #16), Sanity's `defineField`\n * `validation` analogue: `validation: (rule) => rule.required().min(2)`.\n * Evaluated server-side by createLocalApi on every create/update (and by\n * the studio for client-side feedback). Independent of the `required`/\n * `unique` flags above — those still drive the DB schema; these drive\n * value-level checks with clear, per-field error messages. See\n * {@link ValidationBuilder} and `validation.ts`.\n */\n validation?: ValidationBuilder;\n}\n\nexport interface TextFieldConfig extends BaseFieldConfig {\n type: \"text\";\n defaultValue?: string;\n}\n\nexport interface SelectFieldConfig<TOption extends string = string>\n extends BaseFieldConfig {\n type: \"select\";\n options: readonly TOption[];\n defaultValue?: TOption;\n}\n\nexport interface NumberFieldConfig extends BaseFieldConfig {\n type: \"number\";\n /** marks this field as the table's auto-incrementing primary key */\n autoIncrement?: boolean;\n defaultValue?: number;\n}\n\nexport interface DateFieldConfig extends BaseFieldConfig {\n type: \"date\";\n /** mirrors drizzle's integer(..., { mode: \"timestamp\" | \"timestamp_ms\" }) */\n mode?: \"timestamp\" | \"timestamp_ms\";\n defaultValue?: \"now\" | Date;\n}\n\n// Full field-type matrix from issue #16. All six are implemented in\n// codegen.ts: richText/array → JSON column, upload → text column,\n// relationship → integer column (hasMany:false) or join table\n// (hasMany:true), checkbox → integer column with drizzle's boolean mode.\nexport interface RichTextFieldConfig extends BaseFieldConfig {\n type: \"richText\";\n}\n\n/**\n * The TS type every JSON-mode column (`richText`/`array` fields,\n * `versionData`) is given via drizzle's `.$type<JsonValue>()` — see\n * codegen.ts's and schema-gen.ts's richText/array cases. Without it,\n * drizzle infers a JSON column as `unknown`, which TanStack Start's\n * server-function return-type validator rejects outright (`unknown`\n * doesn't structurally match its `Serializable` check the way a plain\n * object/array/primitive union does). Recursive on purpose — that's what\n * lets the validator recurse through it instead of bottoming out at\n * `unknown`.\n */\nexport type JsonValue =\n | string\n | number\n | boolean\n | null\n | JsonValue[]\n | { [key: string]: JsonValue };\n\nexport interface CheckboxFieldConfig extends BaseFieldConfig {\n type: \"checkbox\";\n defaultValue?: boolean;\n}\n\nexport interface RelationshipFieldConfig extends BaseFieldConfig {\n type: \"relationship\";\n relationTo: string;\n /**\n * `false` (default): a plain integer column on this collection's own\n * table storing the related row's id.\n * `true`: no column on this table — represented by a generated join\n * table instead (see codegen.ts's relationshipJoinTables).\n */\n hasMany?: boolean;\n}\n\n/**\n * `0` (default): a relationship field's column comes back as the bare\n * related-row id. `1`: `createLocalApi`'s `registry` param is used to\n * batch-resolve `hasMany: false` relationship fields into the related\n * row's full document — see localApi.ts's `resolveRelationships`. Depths\n * beyond 1 (resolving a relationship's own relationships) aren't\n * implemented; there's no nested-relationship fixture yet to validate\n * that design against.\n */\nexport type RelationshipDepth = 0 | 1;\n\nexport interface ArrayFieldConfig extends BaseFieldConfig {\n type: \"array\";\n /**\n * Fields shown for every item, regardless of variant — must include\n * `discriminator.key`'s own field (typically a `select`) if set.\n */\n fields: Record<string, FieldConfig>;\n /**\n * Lets one array field model a union of item shapes (e.g. page-builder\n * blocks: image vs hero vs richText vs...) instead of one fixed field\n * set for every item. `key` names a field already present in `fields`\n * (rendered as the item's type switcher); `variants` maps each of that\n * field's possible values to *additional* fields layered on top, shown\n * only for items whose `key` field currently holds that value. Fields\n * not listed under any variant (i.e. everything in `fields`) render\n * unconditionally — that's the place for fields shared across every\n * variant (e.g. a `caption` every block type has).\n *\n * Storage is unaffected either way — `array` is always one JSON column\n * (see codegen.ts); this only changes what `CollectionEdit` renders.\n */\n discriminator?: {\n key: string;\n variants: Record<string, Record<string, FieldConfig>>;\n /**\n * Optional per-variant presentation for the studio's \"Add block\" picker\n * (the visual block builder). `label` defaults to a humanized variant\n * name; `icon` is an opaque CSS class the studio applies to an `<i>`\n * (e.g. a Phosphor `\"ph ph-image\"`), keeping cadmea icon-library-agnostic.\n */\n variantsAdmin?: Record<string, { label?: string; icon?: string }>;\n };\n}\n\nexport interface UploadFieldConfig extends BaseFieldConfig {\n type: \"upload\";\n defaultValue?: string;\n}\n\n/**\n * A freeform JSON-blob column — the `json` field type from Section 3 (issue\n * #20-adjacent field-type gap, see DECISIONS.md). Storage-identical to\n * `richText`/`array` (one `.$type<JsonValue>()` text column, see\n * codegen.ts's `fieldToColumn`) but with no TipTap/array-item connotation —\n * use this for genuinely unstructured data (webhook audit payloads, CRM\n * activity metadata), not page-builder content.\n */\nexport interface JsonFieldConfig extends BaseFieldConfig {\n type: \"json\";\n defaultValue?: JsonValue;\n}\n\n/**\n * A fixed-shape, queryable sub-object — the `group` field type from Section\n * 3. Unlike `array` (JSON-blob storage, variable length), `group` flattens\n * to real prefixed columns at the Drizzle level (`<key>_<subKey>`, see\n * codegen.ts's `collectionToTable` and `flattenFields` below) so SQL-level\n * querying/sorting on a subfield (e.g. `shippingAddress.city`) still works.\n * `required`/`unique`/`defaultValue` on the group itself are meaningless —\n * set them on the individual nested `fields` instead; codegen ignores them\n * at the group level.\n */\nexport interface GroupFieldConfig extends BaseFieldConfig {\n type: \"group\";\n fields: Record<string, FieldConfig>;\n}\n\nexport type FieldConfig =\n | TextFieldConfig\n | SelectFieldConfig\n | NumberFieldConfig\n | DateFieldConfig\n | RichTextFieldConfig\n | CheckboxFieldConfig\n | RelationshipFieldConfig\n | ArrayFieldConfig\n | UploadFieldConfig\n | JsonFieldConfig\n | GroupFieldConfig;\n\n/**\n * Expands every `group` field in `fields` into its flattened equivalents\n * (`<key>_<subKey>`, recursively — a group nested inside a group flattens\n * all the way down), and passes every other field through unchanged. This\n * is the single canonicalization step codegen, schema-gen, and the Local\n * API's field-shape validation (`validateRequiredFields`/\n * `rejectUnknownFields`) all run before touching `group` fields, so none of\n * them need their own group-aware branch — see localApi.ts's `flattenDoc`/\n * `nestDoc` for the matching document-level transform.\n *\n * Known limitation: a flattened key can collide if two different group\n * nestings produce the same combined name (e.g. a group `a_b` containing\n * field `c` collides with group `a` containing field `b_c`) — not guarded\n * against, since no current collection nests groups deeply enough to hit\n * it.\n */\nexport function flattenFields(\n fields: Record<string, FieldConfig>,\n): Record<string, FieldConfig> {\n const result: Record<string, FieldConfig> = {};\n for (const [key, field] of Object.entries(fields)) {\n if (field.type === \"group\") {\n for (const [subKey, subField] of Object.entries(\n flattenFields(field.fields),\n )) {\n result[`${key}_${subKey}`] = subField;\n }\n } else {\n result[key] = field;\n }\n }\n return result;\n}\n\n/**\n * The document-level counterpart to `flattenFields` — turns a `group`\n * field's nested object value (`{ shippingAddress: { city: \"...\" } }`) into\n * its flattened equivalent (`{ shippingAddress_city: \"...\" }`) for writing\n * to the DB, recursively. Fields not present in `doc` are simply omitted\n * from the result (lets `update()`'s partial inputs flatten correctly —\n * an absent group means every one of its flattened keys is absent too, not\n * `undefined`-valued). See `nestDoc` for the inverse, used on read.\n */\nexport function flattenDoc(\n fields: Record<string, FieldConfig>,\n doc: Record<string, unknown>,\n): Record<string, unknown> {\n const result: Record<string, unknown> = {};\n for (const [key, field] of Object.entries(fields)) {\n if (field.type === \"group\") {\n if (!(key in doc)) continue;\n const nested = (doc[key] ?? {}) as Record<string, unknown>;\n for (const [subKey, subValue] of Object.entries(\n flattenDoc(field.fields, nested),\n )) {\n result[`${key}_${subKey}`] = subValue;\n }\n } else if (key in doc) {\n result[key] = doc[key];\n }\n }\n return result;\n}\n\n/**\n * The inverse of `flattenDoc` — re-nests a flat DB row's `<key>_<subKey>`\n * columns back into `{ key: { subKey: ... } }` for everything the Local\n * API returns to a caller, so a `group` field's document shape always\n * matches its config shape regardless of how it's actually stored.\n */\nexport function nestDoc(\n fields: Record<string, FieldConfig>,\n flatRow: Record<string, unknown>,\n): Record<string, unknown> {\n const result: Record<string, unknown> = {};\n for (const [key, field] of Object.entries(fields)) {\n if (field.type === \"group\") {\n const prefix = `${key}_`;\n const nestedFlat: Record<string, unknown> = {};\n for (const [flatKey, value] of Object.entries(flatRow)) {\n if (flatKey.startsWith(prefix)) {\n nestedFlat[flatKey.slice(prefix.length)] = value;\n }\n }\n result[key] = nestDoc(field.fields, nestedFlat);\n } else if (key in flatRow) {\n result[key] = flatRow[key];\n }\n }\n return result;\n}\n\n/**\n * Per-operation access check, modeled on Payload's own `access` shape.\n * @returns whether the operation is allowed. Implementations decide their\n * own context shape (auth/session info isn't standardized by Cadmus) — see\n * {@link LocalApi}'s `TContext` generic, which every operation now requires\n * a value for.\n *\n * Enforced by `createLocalApi` since Section 2 — see\n * {@link CollectionConfig.access}.\n */\n// biome-ignore lint/suspicious/noExplicitAny: the context shape is intentionally caller-defined; Cadmus doesn't standardize auth/session info\nexport type AccessFn<TContext = any> = (\n context: TContext,\n) => boolean | Promise<boolean>;\n\nexport interface CollectionAccess {\n create?: AccessFn;\n read?: AccessFn;\n update?: AccessFn;\n delete?: AccessFn;\n /**\n * Gates `VersionedLocalApi.publish`/`unpublish` (see createVersionedLocalApi\n * in localApi.ts). Separate from `update` — publishing is a distinct\n * privilege from editing a draft, matching Payload's own model.\n */\n publish?: AccessFn;\n}\n\n/**\n * Lifecycle hooks, modeled on Payload's own hook points. Each is an\n * ordered array, run in sequence. Enforced by `createLocalApi` — see\n * {@link CollectionConfig.hooks}.\n */\nexport interface CollectionHooks<TDoc = Record<string, unknown>> {\n beforeChange?: Array<\n (args: { data: Partial<TDoc> }) => Partial<TDoc> | Promise<Partial<TDoc>>\n >;\n /**\n * `operation` distinguishes a freshly-inserted doc from an edited one —\n * `publish()` (versioned collections) counts as `\"update\"`, since it\n * writes to an already-existing row rather than creating one. Lets\n * webhook config (see `cms/webhooks.ts`) filter which events it fires\n * on without the hook itself tracking state.\n */\n afterChange?: Array<\n (args: {\n doc: TDoc;\n operation: \"create\" | \"update\";\n }) => void | Promise<void>\n >;\n beforeRead?: Array<(args: { doc: TDoc }) => TDoc | Promise<TDoc>>;\n afterRead?: Array<(args: { doc: TDoc }) => TDoc | Promise<TDoc>>;\n beforeDelete?: Array<(args: { id: number }) => void | Promise<void>>;\n afterDelete?: Array<(args: { id: number }) => void | Promise<void>>;\n}\n\n/**\n * Studio-presentation hints for a collection, modeled on Sanity's Structure\n * Builder (`sanity/structure`). Purely about how the admin sidebar/editor\n * *presents* a collection — never affects the DB schema, the Local API, or\n * access control. Consumed by {@link buildStudioStructure} (see\n * `structure.ts`); a collection with no `admin` block falls back to sensible\n * defaults (visible, editable, listed, grouped under the default group,\n * label = capitalized slug). Plugin-injected collections can't carry an\n * `admin` block in hand-written config, so `buildStudioStructure` also\n * accepts per-slug overrides at the call site — see its `overrides` option.\n */\nexport interface CollectionAdminConfig {\n /**\n * Sidebar group heading this collection appears under (e.g. \"Content\",\n * \"Store\"). Collections without a group fall into the builder's default\n * group. Decoupling nav grouping from the raw collection list is the whole\n * point of the Structure Builder.\n */\n group?: string;\n /**\n * Sort order within a group — lower sorts first. Ties (and the absence of\n * an explicit order) break by the collection's position in the config\n * array, so config order is the stable default.\n */\n order?: number;\n /**\n * Drop this collection from the sidebar entirely. For pure system/log\n * tables a human never browses (e.g. `webhook_events`).\n */\n hidden?: boolean;\n /**\n * Mark as read-only in the studio — still navigable/viewable, but the UI\n * suppresses create/edit/delete affordances. For machine-written tables a\n * human should inspect but never edit (e.g. `payments`).\n */\n readOnly?: boolean;\n /**\n * Singleton: exactly one document. The sidebar links straight to its\n * editor (`/admin/<slug>`) instead of a list-then-create flow — Sanity's\n * singleton-document structure pattern. (Storage is unchanged; this only\n * changes navigation.)\n */\n singleton?: boolean;\n /** Display label override; defaults to a capitalized slug. */\n label?: string;\n /**\n * Optional icon identifier passed through to the sidebar renderer (e.g. a\n * Phosphor icon name). The builder treats it as an opaque string.\n */\n icon?: string;\n}\n\nexport interface CollectionConfig {\n /** table name in D1; also the Local API's collection slug (later step) */\n slug: string;\n fields: Record<string, FieldConfig>;\n /**\n * Per-operation access control, enforced by `createLocalApi` (Section 2).\n * Reserved per issue #16 step 7 (\"reserve typed config keys now,\n * implementation deferred to Section 2+\") — that deferral is over: every\n * `LocalApi` method now requires a `context` argument and runs the\n * matching access function (`read` for `find`/`findByID`, `create` for\n * `create`, etc.) before touching the database. No access function\n * configured for an operation means that operation is unconditionally\n * allowed, matching the pre-enforcement default.\n */\n access?: CollectionAccess;\n /** Lifecycle hooks, enforced by `createLocalApi`. See {@link CollectionHooks}. */\n hooks?: CollectionHooks;\n /**\n * Opts this collection into draft/version history. When `drafts` is\n * true, codegen (see codegen.ts/schema-gen.ts) generates a companion\n * `${slug}_versions` table and a nullable `published_version_id` pointer\n * column on the main table, and `createVersionedLocalApi` (localApi.ts)\n * becomes usable against it. Collections without this stay exactly as\n * before — no versions table, no extra column, only `createLocalApi`.\n */\n versions?: {\n drafts?: boolean;\n /** Reserved for future pruning of old versions; not enforced yet. */\n maxPerDoc?: number;\n };\n /**\n * Opts this collection into full-text search (issue #29). `fields` names\n * which of this collection's own `text`/`richText`/`upload` fields are\n * indexed — `defineCmsConfig`/`defineCollection` reject any other field\n * type or an unknown key. When set, codegen (see codegen.ts's\n * `collectionSearchTableSQL`) describes a companion `${slug}_fts` SQLite\n * FTS5 virtual table, and `createLocalApi` both becomes able to run\n * `.search()` and keeps that table in sync on every create/update/delete\n * — see localApi.ts's `syncSearchIndex`. `richText` fields are flattened\n * to plain text (TipTap JSON's `text` leaves, concatenated) before being\n * indexed; nested `array`/block content is out of scope for this phase.\n */\n search?: {\n fields: readonly string[];\n };\n /**\n * Studio-presentation hints — grouping, ordering, hidden/read-only,\n * singleton, label, icon. Consumed only by {@link buildStudioStructure}\n * (the Structure Builder); never affects schema, Local API, or access.\n * See {@link CollectionAdminConfig}.\n */\n admin?: CollectionAdminConfig;\n}\n\n/**\n * A Cadmea plugin — a synchronous transform over the whole CMS config,\n * modeled on Payload's `plugins: [(config) => config]` shape. A plugin may\n * add or modify collections, inject fields, or register lifecycle hooks.\n * `defineCmsConfig` runs plugins in array order, each receiving the output\n * of the previous one, *before* validation — so a plugin's output is held\n * to the same rules as a hand-written config.\n *\n * Synchronous in Section 2 by design: the resolved config is consumed by\n * schema codegen and runtime config loading, both of which are sync. An\n * async variant is a deliberate later extension, not an oversight.\n */\nexport type CadmeaPlugin = (config: CmsConfig) => CmsConfig;\n\nexport interface CmsConfig {\n collections: CollectionConfig[];\n /**\n * Config transforms run in order by `defineCmsConfig` before validation.\n * See {@link CadmeaPlugin}. Omit for a plain, plugin-free config.\n */\n plugins?: CadmeaPlugin[];\n}\n","// Copyright (c) 2026 BowenLabs. All rights reserved.\n// Cadmus is MIT licensed. See LICENSE in the repo root.\n\nimport {\n integer,\n real,\n type SQLiteColumnBuilderBase,\n sqliteTable,\n text,\n} from \"drizzle-orm/sqlite-core\";\nimport { CadmusCmsError } from \"../errors.js\";\nimport {\n type CmsConfig,\n type CollectionConfig,\n type FieldConfig,\n flattenFields,\n type JsonValue,\n} from \"./types.js\";\n\nfunction toSnakeCase(value: string): string {\n return value.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`);\n}\n\n// relationship fields with hasMany never reach fieldToColumn — they're\n// filtered out in collectionToTable and represented by a join table\n// instead (see relationshipJoinTables below). hasMany:false relationship\n// fields below store the related row's id as a plain integer, with no\n// SQL .references() FK constraint — building real cross-collection FK\n// wiring would require a whole-schema pass, not a per-field switch; with\n// no real relationship field to validate that design against yet, it's\n// deferred rather than half-built.\nfunction fieldToColumn(\n key: string,\n field: FieldConfig,\n): SQLiteColumnBuilderBase {\n const columnName = field.name ?? toSnakeCase(key);\n\n switch (field.type) {\n case \"text\":\n case \"upload\": {\n let column = text(columnName);\n if (field.required) column = column.notNull();\n if (field.unique) column = column.unique();\n if (field.defaultValue !== undefined) {\n column = column.default(field.defaultValue);\n }\n return column;\n }\n case \"richText\":\n case \"array\":\n case \"json\": {\n // TipTap JSON / array-group content / freeform json blobs — all a\n // single JSON column. The nested `fields` on an array config\n // describe the JSON shape for introspection only; not enforced at\n // write time in this step. `group` fields never reach here — they're\n // expanded into their flattened equivalents by `flattenFields`\n // before `collectionToTable` builds columns.\n // `.$type<JsonValue>()` overrides drizzle's inferred `unknown` —\n // see types.ts's JsonValue doc comment for why that matters.\n let column = text(columnName, { mode: \"json\" }).$type<JsonValue>();\n if (field.required) column = column.notNull();\n if (field.defaultValue !== undefined) {\n // BaseFieldConfig types `defaultValue` as `unknown` (it's shared\n // across every field type); a richText/array default is always\n // JSON-shaped data by construction, so this is a narrowing cast,\n // not an escape hatch.\n column = column.default(field.defaultValue as JsonValue);\n }\n return column;\n }\n case \"relationship\": {\n let column = integer(columnName);\n if (field.required) column = column.notNull();\n return column;\n }\n case \"select\": {\n let column = text(columnName, {\n enum: field.options as [string, ...string[]],\n });\n if (field.required) column = column.notNull();\n if (field.defaultValue !== undefined) {\n column = column.default(field.defaultValue);\n }\n return column;\n }\n case \"number\": {\n // autoIncrement marks the table's PK — SQLite's \"INTEGER PRIMARY\n // KEY AUTOINCREMENT\" rowid-alias behavior requires the literal\n // column type INTEGER, so this case never uses `real`, regardless\n // of the general number-field mapping below.\n if (field.autoIncrement) {\n return integer(columnName).primaryKey({ autoIncrement: true });\n }\n let column = real(columnName);\n if (field.required) column = column.notNull();\n if (field.defaultValue !== undefined) {\n column = column.default(field.defaultValue);\n }\n return column;\n }\n case \"date\": {\n let column =\n field.mode === \"timestamp_ms\"\n ? integer(columnName, { mode: \"timestamp_ms\" })\n : integer(columnName, { mode: \"timestamp\" });\n if (field.required) column = column.notNull();\n if (field.defaultValue === \"now\") {\n column = column.$defaultFn(() => new Date());\n } else if (field.defaultValue instanceof Date) {\n const defaultDate = field.defaultValue;\n column = column.$defaultFn(() => defaultDate);\n }\n return column;\n }\n case \"checkbox\": {\n // Same SQLite-integer-as-boolean mapping as the hand-written\n // boolean columns in app/core/db/schema.ts (darkMode, etc.) — kept\n // consistent rather than inventing a second boolean convention.\n let column = integer(columnName, { mode: \"boolean\" });\n if (field.required) column = column.notNull();\n if (field.defaultValue !== undefined) {\n column = column.default(field.defaultValue);\n }\n return column;\n }\n default:\n throw new CadmusCmsError(\n `Field type \"${(field as FieldConfig).type}\" is not yet supported by cadmus/cms codegen`,\n );\n }\n}\n\nexport function collectionToTable(config: CollectionConfig) {\n const columns: Record<string, SQLiteColumnBuilderBase> = {};\n // `group` fields are expanded into `<key>_<subKey>` flattened columns\n // here, before fieldToColumn ever sees them — see types.ts's\n // `flattenFields` doc comment for why this is the one canonicalization\n // step every consumer (codegen, schema-gen, Local API validation) shares.\n for (const [key, field] of Object.entries(flattenFields(config.fields))) {\n // hasMany relationships have no column on this table — they're\n // represented by a join table (see relationshipJoinTables).\n if (field.type === \"relationship\" && field.hasMany) continue;\n columns[key] = fieldToColumn(key, field);\n }\n // Bookkeeping column, not a content field — absent from config.fields so\n // admin-UI introspection (meta.ts) never sees it. Null until the first\n // publish; createVersionedLocalApi.publish() sets it, .unpublish() clears\n // it. See collectionVersionsTable below for the table it points into.\n if (config.versions?.drafts) {\n columns.publishedVersionId = integer(\"published_version_id\");\n }\n return sqliteTable(config.slug, columns);\n}\n\n// One row per saved version (draft or published) of a document, keyed by\n// `parentId` (the main table's row id — no SQL FK constraint, same\n// deferred-FK precedent as relationship fields above). `versionData` is\n// the full document snapshot as JSON, independent of the main table's\n// columns — so a draft can hold an incomplete/invalid-for-publish shape\n// without touching the main row at all.\nexport function collectionVersionsTable(config: CollectionConfig) {\n return sqliteTable(`${config.slug}_versions`, {\n id: integer(\"id\").primaryKey({ autoIncrement: true }),\n parentId: integer(\"parent_id\").notNull(),\n versionData: text(\"version_data\", { mode: \"json\" })\n .$type<JsonValue>()\n .notNull(),\n status: text(\"status\", { enum: [\"draft\", \"published\"] }).notNull(),\n createdAt: integer(\"created_at\", { mode: \"timestamp\" }).$defaultFn(\n () => new Date(),\n ),\n });\n}\n\n// For each hasMany relationship field in a collection, builds a join\n// table named `${collectionSlug}_${fieldKey}` with two plain integer\n// columns. No composite primary key in this step — see codegen.ts's\n// module comment on deferred FK enforcement. Known limitation: a\n// self-referential relationship (relationTo === the collection's own\n// slug) would collide both column names into one — not handled, since\n// no collection needs a self-relation yet.\nexport function relationshipJoinTables(\n config: CollectionConfig,\n): Record<string, ReturnType<typeof sqliteTable>> {\n const joinTables: Record<string, ReturnType<typeof sqliteTable>> = {};\n for (const [key, field] of Object.entries(config.fields)) {\n if (field.type !== \"relationship\" || !field.hasMany) continue;\n const tableName = `${config.slug}_${key}`;\n const ownColumn = `${config.slug}_id`;\n const relatedColumn = `${field.relationTo}_id`;\n const columns: Record<string, SQLiteColumnBuilderBase> = {};\n columns[ownColumn] = integer(ownColumn).notNull();\n columns[relatedColumn] = integer(relatedColumn).notNull();\n joinTables[tableName] = sqliteTable(tableName, columns);\n }\n return joinTables;\n}\n\n// FTS5 virtual tables aren't representable as a drizzle-orm sqliteTable —\n// drizzle has no virtual-table column builder, so unlike collectionToTable/\n// collectionVersionsTable above this emits raw SQL text rather than a\n// runtime table object. The migration itself is hand-authored (drizzle-kit\n// can't diff a TS schema it was never given), this function just keeps that\n// migration's SQL in one place, generated from the same config that drives\n// the rest of codegen — see app/core/db/migrations/0006_pages_search_fts.sql.\nexport function collectionSearchTableName(config: CollectionConfig): string {\n return `${config.slug}_fts`;\n}\n\nexport function collectionSearchTableSQL(config: CollectionConfig): string {\n const fields = config.search?.fields ?? [];\n if (fields.length === 0) return \"\";\n const columns = fields.map((key) => `\"${key}\"`).join(\", \");\n return `CREATE VIRTUAL TABLE IF NOT EXISTS \"${collectionSearchTableName(config)}\" USING fts5(${columns});`;\n}\n\n// Flattens a richText field's TipTap JSON into plain text for FTS5\n// indexing — walks every node's `text` leaves (TipTap's own shape for a\n// run of plain text) and joins them with spaces, ignoring marks/attrs\n// entirely since FTS5 only ever sees plain text. Anything that isn't\n// TipTap JSON (a bare string, a non-object) is coerced to its own string\n// form rather than throwing — search indexing is best-effort, not a\n// validation pass.\nfunction flattenRichText(value: unknown): string {\n if (value === null || value === undefined) return \"\";\n if (typeof value === \"string\") return value;\n if (typeof value !== \"object\") return String(value);\n if (Array.isArray(value)) {\n return value.map(flattenRichText).filter(Boolean).join(\" \");\n }\n const node = value as Record<string, unknown>;\n const parts: string[] = [];\n if (typeof node.text === \"string\") parts.push(node.text);\n if (Array.isArray(node.content)) parts.push(flattenRichText(node.content));\n return parts.filter(Boolean).join(\" \");\n}\n\n// Builds the row inserted into a collection's FTS5 table from a freshly\n// written document — one column per `search.fields` entry, in that order,\n// matching collectionSearchTableSQL's column list. `text`/`upload` fields\n// are indexed as-is; `richText` fields go through flattenRichText first.\nexport function extractSearchText(\n config: CollectionConfig,\n doc: Record<string, unknown>,\n): string[] {\n const fields = config.search?.fields ?? [];\n return fields.map((key) => {\n const field = config.fields[key];\n const raw = doc[key];\n if (field?.type === \"richText\") return flattenRichText(raw);\n return typeof raw === \"string\" ? raw : \"\";\n });\n}\n\nexport function cmsConfigToSchema(\n config: CmsConfig,\n): Record<\n string,\n | ReturnType<typeof collectionToTable>\n | ReturnType<typeof collectionVersionsTable>\n> {\n const schema: Record<\n string,\n | ReturnType<typeof collectionToTable>\n | ReturnType<typeof collectionVersionsTable>\n > = {};\n for (const collection of config.collections) {\n schema[collection.slug] = collectionToTable(collection);\n Object.assign(schema, relationshipJoinTables(collection));\n if (collection.versions?.drafts) {\n schema[`${collection.slug}_versions`] =\n collectionVersionsTable(collection);\n }\n }\n return schema;\n}\n","// Copyright (c) 2026 BowenLabs. All rights reserved.\n// Cadmus is MIT licensed. See LICENSE in the repo root.\n\nimport { CadmusCmsError } from \"../errors.js\";\nimport type { CmsConfig, CollectionConfig, FieldConfig } from \"./types.js\";\n\nconst KNOWN_FIELD_TYPES: ReadonlySet<FieldConfig[\"type\"]> = new Set([\n \"text\",\n \"select\",\n \"number\",\n \"date\",\n \"richText\",\n \"checkbox\",\n \"relationship\",\n \"array\",\n \"upload\",\n \"json\",\n \"group\",\n]);\n\n// Validates one field's own shape (type known, relationship/array/group\n// invariants) without descending into a collection's broader rules\n// (search.fields etc, which only make sense at the top level). Recurses\n// into `group`'s nested fields so a group can't smuggle in an unrecognized\n// or malformed nested field — same checks, one level down.\nfunction validateField(slug: string, key: string, field: FieldConfig): void {\n if (!KNOWN_FIELD_TYPES.has(field.type)) {\n throw new CadmusCmsError(\n `Collection \"${slug}\" field \"${key}\" has unrecognized type \"${field.type}\"`,\n );\n }\n\n if (field.type === \"relationship\" && !field.relationTo) {\n throw new CadmusCmsError(\n `Collection \"${slug}\" field \"${key}\" is a relationship field and requires \"relationTo\"`,\n );\n }\n\n if (field.type === \"array\" && Object.keys(field.fields ?? {}).length === 0) {\n throw new CadmusCmsError(\n `Collection \"${slug}\" field \"${key}\" is an array field and must define at least one nested field`,\n );\n }\n\n if (field.type === \"group\") {\n const nestedEntries = Object.entries(field.fields ?? {});\n if (nestedEntries.length === 0) {\n throw new CadmusCmsError(\n `Collection \"${slug}\" field \"${key}\" is a group field and must define at least one nested field`,\n );\n }\n for (const [nestedKey, nestedField] of nestedEntries) {\n validateField(slug, `${key}.${nestedKey}`, nestedField);\n }\n }\n}\n\nfunction validateCollectionConfig(config: CollectionConfig): void {\n if (!config.slug || config.slug.trim().length === 0) {\n throw new CadmusCmsError(\"Collection config requires a non-empty slug\");\n }\n\n const fieldEntries = Object.entries(config.fields ?? {});\n if (fieldEntries.length === 0) {\n throw new CadmusCmsError(\n `Collection \"${config.slug}\" must define at least one field`,\n );\n }\n\n for (const [key, field] of fieldEntries) {\n validateField(config.slug, key, field);\n }\n\n const SEARCHABLE_FIELD_TYPES: ReadonlySet<FieldConfig[\"type\"]> = new Set([\n \"text\",\n \"richText\",\n \"upload\",\n ]);\n for (const key of config.search?.fields ?? []) {\n const field = config.fields[key];\n if (!field) {\n throw new CadmusCmsError(\n `Collection \"${config.slug}\" search.fields references unknown field \"${key}\"`,\n );\n }\n if (!SEARCHABLE_FIELD_TYPES.has(field.type)) {\n throw new CadmusCmsError(\n `Collection \"${config.slug}\" search.fields field \"${key}\" has type \"${field.type}\" — only \"text\", \"richText\", and \"upload\" fields can be indexed`,\n );\n }\n }\n}\n\nfunction validateUniqueSlugs(collections: readonly CollectionConfig[]): void {\n const seen = new Set<string>();\n for (const collection of collections) {\n if (seen.has(collection.slug)) {\n throw new CadmusCmsError(\n `Duplicate collection slug \"${collection.slug}\" — collection slugs must be unique`,\n );\n }\n seen.add(collection.slug);\n }\n}\n\nexport function defineCollection(config: CollectionConfig): CollectionConfig {\n validateCollectionConfig(config);\n return config;\n}\n\nexport function defineCmsConfig(config: CmsConfig): CmsConfig {\n // Run plugins in array order, each fed the previous one's output, before\n // any validation — a plugin's emitted config is held to exactly the same\n // rules as a hand-written one. The resolved config (not the raw input) is\n // what every downstream consumer reads: schema codegen, admin meta, and\n // the Local API. Plugins must not mutate their input; treat `config` as\n // immutable and return a new object (the SEO plugin does this).\n let resolved: CmsConfig = config;\n for (const plugin of config.plugins ?? []) {\n resolved = plugin(resolved);\n }\n\n for (const collection of resolved.collections) {\n validateCollectionConfig(collection);\n }\n validateUniqueSlugs(resolved.collections);\n return resolved;\n}\n","// Copyright (c) 2026 BowenLabs. All rights reserved.\n// Cadmus is MIT licensed. See LICENSE in the repo root.\n\nimport type { JsonValue } from \"./types.js\";\n\n/**\n * Patch model + field-level diff (issue #14) — adopts Sanity's mutation/patch\n * idea (pattern, not code): represent a content change as a small set of\n * field operations, and compute a field-level diff between two document\n * snapshots. Underpins version-history display (what changed between two\n * versions) and the content-migration runner (#18), which expresses a\n * transform's effect as a {@link Patch} and applies it.\n *\n * Scope: operations are keyed by **top-level field** (a document's own\n * fields), matching \"field-level diff\" — a changed `blocks` array reads as\n * one changed field, not a deep per-node diff. Deep/array-aware diffing is a\n * deliberate later extension, not built here.\n */\n\n/** A single field operation. `set` writes a value; `unset` removes the field. */\nexport type PatchOp =\n | { op: \"set\"; path: string; value: JsonValue }\n | { op: \"unset\"; path: string };\n\n/** An ordered set of field operations transforming one document into another. */\nexport type Patch = PatchOp[];\n\nexport type FieldChangeKind = \"added\" | \"removed\" | \"changed\";\n\n/** One field's difference between two document snapshots. */\nexport interface FieldChange {\n /** Top-level field key. */\n path: string;\n kind: FieldChangeKind;\n /** Value in the \"before\" snapshot (absent for `added`). */\n before?: JsonValue;\n /** Value in the \"after\" snapshot (absent for `removed`). */\n after?: JsonValue;\n}\n\ntype Doc = Record<string, JsonValue>;\n\n// Structural deep-equality over JSON values — order-sensitive for arrays\n// (a reordered blocks array is a real change), key-order-insensitive for\n// objects.\nfunction deepEqual(a: JsonValue, b: JsonValue): boolean {\n if (a === b) return true;\n if (a === null || b === null) return false;\n if (typeof a !== typeof b) return false;\n if (Array.isArray(a) || Array.isArray(b)) {\n if (!Array.isArray(a) || !Array.isArray(b)) return false;\n if (a.length !== b.length) return false;\n return a.every((item, i) => deepEqual(item, b[i] as JsonValue));\n }\n if (typeof a === \"object\" && typeof b === \"object\") {\n const ak = Object.keys(a);\n const bk = Object.keys(b);\n if (ak.length !== bk.length) return false;\n return ak.every(\n (key) =>\n Object.hasOwn(b, key) && deepEqual((a as Doc)[key], (b as Doc)[key]),\n );\n }\n return false;\n}\n\nexport interface DiffOptions {\n /**\n * Restrict the diff to these field keys. Omit to diff the union of both\n * documents' own keys. Useful for ignoring bookkeeping columns\n * (`id`/`createdAt`/`publishedVersionId`) in a version-history view.\n */\n fields?: readonly string[];\n /** Field keys to skip (e.g. `[\"id\", \"createdAt\"]`). */\n ignore?: readonly string[];\n}\n\n/**\n * Field-level diff between two document snapshots — the per-field\n * added/removed/changed list a version-history UI renders. Values are\n * compared structurally (deep-equal), so a field only shows as `changed`\n * when its content actually differs.\n */\nexport function diffDocuments(\n before: Doc,\n after: Doc,\n options: DiffOptions = {},\n): FieldChange[] {\n const ignore = new Set(options.ignore ?? []);\n const keys = options.fields\n ? options.fields\n : [...new Set([...Object.keys(before), ...Object.keys(after)])];\n\n const changes: FieldChange[] = [];\n for (const path of keys) {\n if (ignore.has(path)) continue;\n const inBefore = Object.hasOwn(before, path);\n const inAfter = Object.hasOwn(after, path);\n if (inBefore && !inAfter) {\n changes.push({ path, kind: \"removed\", before: before[path] });\n } else if (!inBefore && inAfter) {\n changes.push({ path, kind: \"added\", after: after[path] });\n } else if (inBefore && inAfter && !deepEqual(before[path], after[path])) {\n changes.push({\n path,\n kind: \"changed\",\n before: before[path],\n after: after[path],\n });\n }\n }\n return changes;\n}\n\n/**\n * The {@link Patch} that transforms `before` into `after`: `set` for each\n * added/changed field, `unset` for each removed field. `applyPatch(before,\n * computePatch(before, after))` deep-equals `after`.\n */\nexport function computePatch(before: Doc, after: Doc): Patch {\n return diffDocuments(before, after).map((change) =>\n change.kind === \"removed\"\n ? { op: \"unset\", path: change.path }\n : { op: \"set\", path: change.path, value: change.after as JsonValue },\n );\n}\n\n/**\n * Apply a {@link Patch} to a document, returning a new document (the input is\n * never mutated). Unknown ops are ignored defensively.\n */\nexport function applyPatch(doc: Doc, patch: Patch): Doc {\n const next: Doc = { ...doc };\n for (const op of patch) {\n if (op.op === \"set\") {\n next[op.path] = op.value;\n } else if (op.op === \"unset\") {\n delete next[op.path];\n }\n }\n return next;\n}\n","// Copyright (c) 2026 BowenLabs. All rights reserved.\n// Cadmus is MIT licensed. See LICENSE in the repo root.\n\nimport { and, eq, ne } from \"drizzle-orm\";\nimport type {\n BaseSQLiteDatabase,\n SQLiteTableWithColumns,\n} from \"drizzle-orm/sqlite-core\";\nimport { CadmusValidationError, type ValidationViolation } from \"../errors.js\";\nimport type { CmsRegistry } from \"./localApi.js\";\nimport type { CollectionConfig, FieldConfig } from \"./types.js\";\nimport { flattenDoc, flattenFields } from \"./types.js\";\n\n// Mirrors localApi.ts's own local alias — drizzle's default table generic.\n// biome-ignore lint/suspicious/noExplicitAny: matches drizzle-orm's own SQLiteTableWithColumns default generic usage\ntype AnyTable = SQLiteTableWithColumns<any>;\n\n/**\n * Chainable field validation for Cadmea (issue #16) — adopts Sanity's\n * `defineField`/`Rule` validation API (pattern, not code). A field declares\n * `validation: (rule) => rule.required().min(2).custom(...)`; this module\n * turns that chain into a list of declarative checks and evaluates them at\n * write time (server-side, in createLocalApi) as well as anywhere the studio\n * wants synchronous feedback.\n *\n * Design notes:\n * - The builder is **immutable** — every method returns a new {@link Rule}\n * with one more check appended, so a shared base rule can't be mutated by\n * a consumer's chain (mirrors Sanity).\n * - Most checks are synchronous and pure (min/max/regex/custom over the\n * value alone). Two — `unique` and `reference` — need the database and so\n * only run where {@link validateDocument} is given a `db` (i.e. the Local\n * API); they're skipped (not failed) in a pure client-side pass.\n */\n\nexport type ValidationSeverity = \"error\" | \"warning\";\n\n/**\n * What a {@link CustomValidator} may return:\n * - `true` / `undefined` → valid\n * - `false` → invalid, generic message\n * - `string` → invalid, that message\n * - `{ message, severity? }` → invalid, that message at the given severity\n */\nexport type CustomValidatorResult =\n | boolean\n | undefined\n | string\n | { message: string; severity?: ValidationSeverity };\n\nexport interface ValidationFieldContext {\n /** The whole document being validated (nested shape, post-hooks). */\n document: Record<string, unknown>;\n /** This field's flattened key (e.g. `slug`, `shippingAddress_city`). */\n path: string;\n /** Whether this is a create or an update. */\n operation: \"create\" | \"update\";\n /** The document's id on update — lets `unique` exclude the row itself. */\n id?: number;\n}\n\nexport type CustomValidator = (\n value: unknown,\n context: ValidationFieldContext,\n) => CustomValidatorResult | Promise<CustomValidatorResult>;\n\n// Internal check descriptors. `message`/`severity` are per-check overrides\n// applied by `.error()`/`.warning()` to the most recently added check.\ntype Check = (\n | { kind: \"required\" }\n | { kind: \"min\"; n: number }\n | { kind: \"max\"; n: number }\n | { kind: \"length\"; n: number }\n | { kind: \"regex\"; re: RegExp; label: string }\n | { kind: \"integer\" }\n | { kind: \"positive\" }\n | { kind: \"unique\" }\n | { kind: \"reference\" }\n | { kind: \"custom\"; fn: CustomValidator }\n) & { message?: string; severity?: ValidationSeverity };\n\n// Pre-baked formats so consumers don't hand-roll the same regexes.\nconst EMAIL_RE = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/;\n// Lowercase kebab slug: letters/digits separated by single hyphens.\nconst SLUG_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;\n\n/**\n * Immutable, chainable rule builder — the value a field's `validation`\n * function receives and returns. Build a `Rule` with the module-level\n * {@link rule} factory, or accept the one passed to your `validation`\n * callback.\n */\nexport class Rule {\n // Frozen on construction; every builder method returns a fresh Rule.\n private readonly checks: readonly Check[];\n\n constructor(checks: readonly Check[] = []) {\n this.checks = checks;\n }\n\n private add(check: Check): Rule {\n return new Rule([...this.checks, check]);\n }\n\n /** Override the message of the most recently added check. */\n error(message: string): Rule {\n return this.withLast({ message, severity: \"error\" });\n }\n\n /**\n * Demote the most recently added check to a warning (non-blocking),\n * optionally with a message. Sanity's `Rule.warning()` analogue.\n */\n warning(message?: string): Rule {\n return this.withLast({\n severity: \"warning\",\n ...(message ? { message } : {}),\n });\n }\n\n private withLast(patch: Partial<Check>): Rule {\n if (this.checks.length === 0) return this;\n const next = this.checks.slice();\n next[next.length - 1] = { ...next[next.length - 1], ...patch } as Check;\n return new Rule(next);\n }\n\n required(): Rule {\n return this.add({ kind: \"required\" });\n }\n\n /** Minimum string length / array length / numeric value. */\n min(n: number): Rule {\n return this.add({ kind: \"min\", n });\n }\n\n /** Maximum string length / array length / numeric value. */\n max(n: number): Rule {\n return this.add({ kind: \"max\", n });\n }\n\n /** Exact string/array length. */\n length(n: number): Rule {\n return this.add({ kind: \"length\", n });\n }\n\n regex(re: RegExp, label = \"match the required format\"): Rule {\n return this.add({ kind: \"regex\", re, label });\n }\n\n email(): Rule {\n return this.add({ kind: \"regex\", re: EMAIL_RE, label: \"be a valid email\" });\n }\n\n /** Lowercase kebab-case slug format. Pair with `.unique()` for slugs. */\n slug(): Rule {\n return this.add({\n kind: \"regex\",\n re: SLUG_RE,\n label: \"be a lowercase, hyphen-separated slug\",\n });\n }\n\n integer(): Rule {\n return this.add({ kind: \"integer\" });\n }\n\n positive(): Rule {\n return this.add({ kind: \"positive\" });\n }\n\n /**\n * Value must be unique across the collection (DB-backed; skipped in a\n * pure client-side pass). A first-class rule rather than the hand-rolled\n * column `unique` flag, so the failure is a clear field message instead of\n * a raw UNIQUE-constraint write error.\n */\n unique(): Rule {\n return this.add({ kind: \"unique\" });\n }\n\n /**\n * For a `relationship` field: the referenced id must exist in the related\n * collection (DB-backed; skipped client-side).\n */\n reference(): Rule {\n return this.add({ kind: \"reference\" });\n }\n\n custom(fn: CustomValidator): Rule {\n return this.add({ kind: \"custom\", fn });\n }\n\n /** Internal: the accumulated checks, read by {@link validateDocument}. */\n toChecks(): readonly Check[] {\n return this.checks;\n }\n}\n\n/** Fresh, empty rule — the root of a chain. */\nexport function rule(): Rule {\n return new Rule();\n}\n\n/**\n * A field's `validation` value: a function from a fresh Rule to the\n * configured chain (Sanity's signature). Returning an array lets a field\n * carry several independent rule chains.\n */\nexport type ValidationBuilder = (r: Rule) => Rule | Rule[];\n\n/**\n * Identity helper mirroring Sanity's `defineField` — returns the field\n * config unchanged but gives editors autocomplete and a single, greppable\n * call site for field definitions. Optional: a plain object literal is still\n * a valid field.\n */\nexport function defineField<T extends FieldConfig>(field: T): T {\n return field;\n}\n\nfunction resolveChecks(field: FieldConfig): readonly Check[] {\n if (!field.validation) return [];\n const built = field.validation(new Rule());\n const rules = Array.isArray(built) ? built : [built];\n return rules.flatMap((r) => r.toChecks());\n}\n\nfunction isEmpty(value: unknown): boolean {\n return (\n value === undefined ||\n value === null ||\n (typeof value === \"string\" && value.length === 0)\n );\n}\n\nfunction sizeOf(value: unknown): { size: number; unit: string } | null {\n if (typeof value === \"string\")\n return { size: value.length, unit: \"character\" };\n if (Array.isArray(value)) return { size: value.length, unit: \"item\" };\n if (typeof value === \"number\") return { size: value, unit: \"\" };\n return null;\n}\n\nexport interface ValidateDocumentOptions {\n operation: \"create\" | \"update\";\n /** Document id (update only) — passed to `unique`/custom validators. */\n id?: number;\n /**\n * Restrict validation to these flattened field keys. Used by update(),\n * which only receives a partial document — validating absent fields would\n * spuriously fail their rules. Omit to validate every field (create).\n */\n onlyFields?: ReadonlySet<string>;\n /**\n * Database handle for DB-backed rules (`unique`, `reference`). When\n * omitted, those rules are skipped — so the same function powers a pure\n * client-side validation pass.\n */\n db?: BaseSQLiteDatabase<\"async\", unknown>;\n /** This collection's own table (for `unique`). */\n table?: AnyTable;\n /** Registry of tables by slug (for `reference` target lookups). */\n registry?: CmsRegistry;\n}\n\n/**\n * Evaluate every field's validation rules against `doc`, returning all\n * violations (both errors and warnings). `doc` is the nested document; field\n * values are read from its flattened form so group subfields validate too.\n */\nexport async function validateDocument(\n config: CollectionConfig,\n doc: Record<string, unknown>,\n options: ValidateDocumentOptions,\n): Promise<ValidationViolation[]> {\n const flatFields = flattenFields(config.fields);\n const flatDoc = flattenDocShallow(config, doc);\n const violations: ValidationViolation[] = [];\n\n for (const [path, field] of Object.entries(flatFields)) {\n if (options.onlyFields && !options.onlyFields.has(path)) continue;\n const checks = resolveChecks(field);\n if (checks.length === 0) continue;\n\n const value = flatDoc[path];\n const ctx: ValidationFieldContext = {\n document: doc,\n path,\n operation: options.operation,\n ...(options.id !== undefined ? { id: options.id } : {}),\n };\n\n for (const check of checks) {\n const violation = await evaluateCheck(check, value, field, ctx, options);\n if (violation) violations.push(violation);\n }\n }\n\n return violations;\n}\n\n// Reuse the Local API's own flattening so a group subfield's value is read\n// from the same `<key>_<subKey>` shape it's stored under. Skip the round\n// trip when the collection has no group fields (the common case).\nfunction flattenDocShallow(\n config: CollectionConfig,\n doc: Record<string, unknown>,\n): Record<string, unknown> {\n const hasGroup = Object.values(config.fields).some((f) => f.type === \"group\");\n return hasGroup ? flattenDoc(config.fields, doc) : doc;\n}\n\nasync function evaluateCheck(\n check: Check,\n value: unknown,\n field: FieldConfig,\n ctx: ValidationFieldContext,\n options: ValidateDocumentOptions,\n): Promise<ValidationViolation | null> {\n const fail = (defaultMessage: string): ValidationViolation => ({\n path: ctx.path,\n message: check.message ?? `${ctx.path} must ${defaultMessage}`,\n severity: check.severity ?? \"error\",\n });\n\n switch (check.kind) {\n case \"required\":\n return isEmpty(value) ? fail(\"not be empty\") : null;\n\n case \"min\": {\n if (isEmpty(value)) return null;\n const s = sizeOf(value);\n if (s && s.size < check.n) {\n return fail(\n s.unit\n ? `have at least ${check.n} ${s.unit}${check.n === 1 ? \"\" : \"s\"}`\n : `be at least ${check.n}`,\n );\n }\n return null;\n }\n\n case \"max\": {\n if (isEmpty(value)) return null;\n const s = sizeOf(value);\n if (s && s.size > check.n) {\n return fail(\n s.unit\n ? `have at most ${check.n} ${s.unit}${check.n === 1 ? \"\" : \"s\"}`\n : `be at most ${check.n}`,\n );\n }\n return null;\n }\n\n case \"length\": {\n if (isEmpty(value)) return null;\n const s = sizeOf(value);\n if (s?.unit && s.size !== check.n) {\n return fail(\n `be exactly ${check.n} ${s.unit}${check.n === 1 ? \"\" : \"s\"}`,\n );\n }\n return null;\n }\n\n case \"regex\": {\n if (isEmpty(value)) return null;\n if (typeof value !== \"string\" || !check.re.test(value)) {\n return fail(check.label);\n }\n return null;\n }\n\n case \"integer\":\n if (isEmpty(value)) return null;\n return typeof value === \"number\" && Number.isInteger(value)\n ? null\n : fail(\"be an integer\");\n\n case \"positive\":\n if (isEmpty(value)) return null;\n return typeof value === \"number\" && value > 0\n ? null\n : fail(\"be a positive number\");\n\n case \"unique\":\n return evaluateUnique(value, ctx, options, check);\n\n case \"reference\":\n return evaluateReference(value, field, ctx, options, check);\n\n case \"custom\": {\n const result = await check.fn(value, ctx);\n if (result === true || result === undefined) return null;\n if (result === false) return fail(\"be valid\");\n if (typeof result === \"string\") {\n return {\n path: ctx.path,\n message: result,\n severity: check.severity ?? \"error\",\n };\n }\n return {\n path: ctx.path,\n message: result.message,\n severity: result.severity ?? check.severity ?? \"error\",\n };\n }\n }\n}\n\nasync function evaluateUnique(\n value: unknown,\n ctx: ValidationFieldContext,\n options: ValidateDocumentOptions,\n check: Check,\n): Promise<ValidationViolation | null> {\n if (isEmpty(value) || !options.db || !options.table) return null;\n const column = (options.table as Record<string, unknown>)[ctx.path];\n if (!column) return null;\n // On update, exclude the row itself so re-saving an unchanged value passes.\n const where =\n ctx.id !== undefined\n ? and(eq(column as never, value as never), ne(options.table.id, ctx.id))\n : eq(column as never, value as never);\n const existing = await options.db\n .select({ id: options.table.id })\n .from(options.table as never)\n .where(where)\n .limit(1);\n if (existing.length > 0) {\n return {\n path: ctx.path,\n message:\n check.message ?? `${ctx.path} \"${String(value)}\" is already taken`,\n severity: check.severity ?? \"error\",\n };\n }\n return null;\n}\n\nasync function evaluateReference(\n value: unknown,\n field: FieldConfig,\n ctx: ValidationFieldContext,\n options: ValidateDocumentOptions,\n check: Check,\n): Promise<ValidationViolation | null> {\n if (isEmpty(value) || !options.db || !options.registry) return null;\n if (field.type !== \"relationship\") return null;\n const target = options.registry.tables[field.relationTo];\n if (!target) return null;\n const found = await options.db\n .select({ id: target.id })\n .from(target as never)\n .where(eq(target.id, value as never))\n .limit(1);\n if (found.length === 0) {\n return {\n path: ctx.path,\n message:\n check.message ??\n `${ctx.path} references a \"${field.relationTo}\" that does not exist`,\n severity: check.severity ?? \"error\",\n };\n }\n return null;\n}\n\n/**\n * Run {@link validateDocument} and throw {@link CadmusValidationError} if any\n * `\"error\"`-severity violations are found. Warnings are returned (never\n * thrown) so a caller can still surface them. The thrown error's message is\n * a readable, joined summary of every blocking violation.\n */\nexport async function assertValid(\n config: CollectionConfig,\n doc: Record<string, unknown>,\n options: ValidateDocumentOptions,\n): Promise<ValidationViolation[]> {\n const violations = await validateDocument(config, doc, options);\n const errors = violations.filter((v) => v.severity === \"error\");\n if (errors.length > 0) {\n const summary = errors.map((v) => v.message).join(\"; \");\n throw new CadmusValidationError(\n `Validation failed for collection \"${config.slug}\": ${summary}`,\n violations,\n );\n }\n return violations;\n}\n","// Copyright (c) 2026 BowenLabs. All rights reserved.\n// Cadmus is MIT licensed. See LICENSE in the repo root.\n\nimport {\n count as countRows,\n desc,\n eq,\n type InferInsertModel,\n type InferSelectModel,\n inArray,\n type SQL,\n sql,\n} from \"drizzle-orm\";\nimport type {\n BaseSQLiteDatabase,\n SQLiteTableWithColumns,\n} from \"drizzle-orm/sqlite-core\";\nimport { CadmusAccessDeniedError, CadmusCmsError } from \"../errors.js\";\nimport { collectionSearchTableName, extractSearchText } from \"./codegen.js\";\nimport { diffDocuments, type FieldChange } from \"./patch.js\";\nimport {\n type CollectionAccess,\n type CollectionConfig,\n flattenDoc,\n flattenFields,\n type JsonValue,\n nestDoc,\n type RelationshipDepth,\n} from \"./types.js\";\nimport { assertValid } from \"./validation.js\";\n\n// biome-ignore lint/suspicious/noExplicitAny: matches drizzle-orm's own SQLiteTableWithColumns default generic usage\ntype AnyTable = SQLiteTableWithColumns<any>;\n\n/**\n * `TContext` is the per-request value passed to every method and forwarded\n * unchanged to the collection's `access` functions (see {@link CollectionAccess}).\n * Cadmus doesn't standardize its shape — Cadmea types it as `{ session }`,\n * other consumers may type it differently. `context` is a required first\n * argument on every method (not optional) so a call site can't forget it.\n */\nexport interface LocalApi<TTable extends AnyTable, TContext = unknown> {\n /**\n * `depth: 0` (default) returns relationship fields as bare ids; `depth: 1`\n * batch-resolves `hasMany: false` relationship fields into the related\n * row, gated by that collection's own `read` access fn — see\n * `resolveRelationships` below. Requires `createLocalApi`'s `registry`\n * param; throws CadmusCmsError if `depth: 1` is requested without one.\n */\n find(\n context: TContext,\n options?: {\n where?: SQL;\n depth?: RelationshipDepth;\n /** Row cap, applied after `where` — for paginated list views. */\n limit?: number;\n /** Rows to skip, applied after `where` — pairs with `limit`. */\n offset?: number;\n /** One or more `asc(table.col)`/`desc(table.col)` expressions. */\n orderBy?: SQL | SQL[];\n },\n ): Promise<InferSelectModel<TTable>[]>;\n findByID(\n context: TContext,\n id: number,\n options?: { depth?: RelationshipDepth },\n ): Promise<InferSelectModel<TTable>>;\n /**\n * Total row count for `where` (ignoring `limit`/`offset`) — pairs with\n * `find` to compute page counts/next-page availability without fetching\n * every row. Gated by the same `read` access check as `find`.\n */\n count(context: TContext, options?: { where?: SQL }): Promise<number>;\n /**\n * Full-text search over this collection's `search.fields`-configured\n * companion FTS5 table — see types.ts's `CollectionConfig.search` and\n * codegen.ts's `collectionSearchTableSQL`. Gated by `read` access, same\n * as `find`/`findByID`. Throws `CadmusCmsError` if the collection has no\n * `search` config.\n */\n search(\n context: TContext,\n query: string,\n options?: { limit?: number },\n ): Promise<InferSelectModel<TTable>[]>;\n create(\n context: TContext,\n input: InferInsertModel<TTable>,\n ): Promise<InferSelectModel<TTable>>;\n update(\n context: TContext,\n id: number,\n input: Partial<InferInsertModel<TTable>>,\n ): Promise<InferSelectModel<TTable>>;\n deleteByID(context: TContext, id: number): Promise<InferSelectModel<TTable>>;\n}\n\n// `input` here is always already-flattened (group fields expanded to\n// `<key>_<subKey>`) — both callers in create()/update() flatten via\n// flattenDoc before reaching these, so flattening config.fields too means\n// every key in input lines up with a key in this flattened field map.\nfunction validateRequiredFields(\n config: CollectionConfig,\n input: Record<string, unknown>,\n): void {\n for (const [key, field] of Object.entries(flattenFields(config.fields))) {\n const hasDefault = field.defaultValue !== undefined;\n if (field.required && !hasDefault && input[key] === undefined) {\n throw new CadmusCmsError(\n `Missing required field \"${key}\" for collection \"${config.slug}\"`,\n );\n }\n }\n}\n\nfunction rejectUnknownFields(\n config: CollectionConfig,\n input: Record<string, unknown>,\n): void {\n const flatFields = flattenFields(config.fields);\n for (const key of Object.keys(input)) {\n if (!(key in flatFields)) {\n throw new CadmusCmsError(\n `Unknown field \"${key}\" for collection \"${config.slug}\"`,\n );\n }\n }\n}\n\nfunction wrapWriteError(config: CollectionConfig, error: unknown): never {\n if (error instanceof CadmusCmsError) throw error;\n const message = error instanceof Error ? error.message : String(error);\n if (message.includes(\"UNIQUE constraint failed\")) {\n throw new CadmusCmsError(\n `Unique constraint violated for collection \"${config.slug}\"`,\n error,\n );\n }\n throw new CadmusCmsError(\n `Write failed for collection \"${config.slug}\"`,\n error,\n );\n}\n\nfunction notFound(config: CollectionConfig, id: number): never {\n throw new CadmusCmsError(`No \"${config.slug}\" document found with id ${id}`);\n}\n\n/**\n * Lets `createLocalApi` resolve `depth: 1` relationship fields without\n * importing every other collection's Local API (which would be a circular\n * dependency the moment two collections relate to each other). The\n * registry is just the raw ingredients — a table and a config per\n * collection slug — built once by the app (e.g. from `cadmeaConfig.collections`)\n * and passed to every `createLocalApi` call that has relationship fields.\n *\n * `apis` is a second, optional registry on the same object, for a\n * different problem: a *hook* (not `createLocalApi` itself) on one\n * collection that needs to write to *another* collection's Local API —\n * e.g. a CRM upsert hook on a lead-capture collection that creates/updates\n * `contacts`/`activities` rows. `tables`/`configs` can't serve this, since\n * a hook needs a real `LocalApi` (with its own access/hooks/search wiring\n * already applied), not raw ingredients to rebuild one from.\n *\n * The chicken-and-egg problem this solves: building collection A's\n * `LocalApi` might need to reference collection B's `LocalApi` (for a\n * hook), but collection B's `LocalApi` doesn't exist yet at the point A's\n * is constructed — and vice versa if B also has a hook referencing A.\n * The fix is **late binding**: build one `CmsRegistry` object, pass the\n * *same reference* into every `createLocalApi` call (so every collection's\n * hooks close over the same mutable object), construct every `LocalApi`,\n * then fill in `registry.apis` afterwards:\n *\n * ```ts\n * const registry: CmsRegistry = { tables, configs, apis: {} };\n * const contactsApi = createLocalApi(db, contactsTable, contactsCollection, registry);\n * const inquiriesApi = createLocalApi(db, inquiriesTable, inquiriesCollection, registry);\n * // populate *after* every createLocalApi call returns — any hook that\n * // reads registry.apis lazily (inside its returned function body, not\n * // at hook-factory-call time) sees the fully-populated map, since hooks\n * // only ever run once real requests start landing.\n * Object.assign(registry.apis!, { contacts: contactsApi, inquiries: inquiriesApi });\n * ```\n *\n * See `getRegisteredApi` for the accessor a hook factory should use to\n * read from this map, rather than indexing `registry.apis` directly.\n */\nexport interface CmsRegistry {\n tables: Record<string, AnyTable>;\n configs: Record<string, CollectionConfig>;\n // biome-ignore lint/suspicious/noExplicitAny: collections in the same registry can have different TContext shapes — same `any` escape hatch hono/cms.ts's CmsRoutesOptions already uses for the same reason\n apis?: Record<string, LocalApi<AnyTable, any>>;\n}\n\n/**\n * Reads collection `slug`'s `LocalApi` out of `registry.apis` — the\n * accessor hook factories should use (see `CmsRegistry`'s doc comment for\n * the late-binding pattern this assumes) instead of indexing\n * `registry.apis` directly, so every caller gets the same clear error if\n * the registry wasn't built/populated correctly. `TContext` is a type-only\n * parameter (the registry itself is stored with `never` to stay variance-\n * safe across collections with different context shapes) — callers assert\n * the context type they expect, the same way `resolveRelationships`'s own\n * registry lookups do.\n */\nexport function getRegisteredApi<TContext>(\n registry: CmsRegistry | undefined,\n slug: string,\n): LocalApi<AnyTable, TContext> {\n const api = registry?.apis?.[slug];\n if (!api) {\n throw new CadmusCmsError(\n `No LocalApi registered for collection \"${slug}\" — pass a CmsRegistry whose \"apis\" map has been populated with every collection a hook needs to reach (see CmsRegistry's doc comment for the late-binding build order)`,\n );\n }\n return api;\n}\n\n/**\n * Batch-resolves this collection's `hasMany: false` relationship fields\n * for an already-fetched page of `rows`, one query per relationship field\n * (not one query per row — the N+1 the `depth: 1` design note in types.ts\n * calls out avoiding). The related collection's `read` access fn is run\n * once per field against `context`, not once per row: there's a single\n * yes/no for \"can this context read collection X\", not a row-by-row\n * filter. When it rejects, the field is left as the bare id rather than\n * throwing — a denied relationship is an omission, not a failed request.\n * `hasMany: true` relationship fields are untouched (no column on this\n * table to resolve from — they live in a join table, out of scope here).\n */\nasync function resolveRelationships<TContext>(\n db: BaseSQLiteDatabase<\"async\", unknown>,\n config: CollectionConfig,\n rows: AnyRecord[],\n context: TContext,\n registry: CmsRegistry | undefined,\n): Promise<AnyRecord[]> {\n const relationshipFields = Object.entries(config.fields).filter(\n ([, field]) => field.type === \"relationship\" && !field.hasMany,\n );\n if (relationshipFields.length === 0) return rows;\n if (!registry) {\n throw new CadmusCmsError(\n `Collection \"${config.slug}\" requested depth: 1 but createLocalApi was not given a registry to resolve relationship fields against`,\n );\n }\n\n let result = rows;\n for (const [key, field] of relationshipFields) {\n const relationTo = (field as { relationTo: string }).relationTo;\n const relatedConfig = registry.configs[relationTo];\n const relatedTable = registry.tables[relationTo];\n if (!relatedConfig || !relatedTable) {\n throw new CadmusCmsError(\n `Collection \"${config.slug}\" field \"${key}\" relates to unknown collection \"${relationTo}\" — not present in the registry`,\n );\n }\n\n const readFn = relatedConfig.access?.read;\n const allowed = readFn ? await readFn(context) : true;\n if (!allowed) continue;\n\n const ids = [\n ...new Set(\n result\n .map((row) => row[key])\n .filter((id): id is number => typeof id === \"number\"),\n ),\n ];\n if (ids.length === 0) continue;\n\n const relatedRows = await db\n .select()\n .from(relatedTable)\n .where(inArray(relatedTable.id, ids));\n const byId = new Map(\n relatedRows.map((row) => [(row as AnyRecord).id, row as AnyRecord]),\n );\n\n result = result.map((row) => {\n const id = row[key];\n const related = typeof id === \"number\" ? byId.get(id) : undefined;\n return related ? { ...row, [key]: related } : row;\n });\n }\n return result;\n}\n\n// Hook runners. `config.hooks` (CollectionHooks) is folded into every\n// write/read below. Transforming hooks (beforeChange, beforeRead,\n// afterRead) run in array order, each fed the previous one's output; side-\n// effect hooks (afterChange, beforeDelete, afterDelete) run in order for\n// their effects only. All may be async. `config.access` is checked by\n// checkAccess() below, before any hook or DB work runs for that operation.\ntype AnyRecord = Record<string, unknown>;\n\n/**\n * Non-throwing counterpart to `checkAccess` below, for UI code that wants\n * to hide/disable an action a context can't perform rather than let it\n * fail server-side after a click (see Phase 6 / issue #26's\n * `getPageCapabilities`). `checkAccess` calls through this same function\n * rather than duplicating the \"no access fn = allowed\" logic, so `can()`'s\n * answer and the real operation's enforcement can never disagree.\n */\nexport async function can<TContext>(\n config: CollectionConfig,\n operation: keyof CollectionAccess,\n context: TContext,\n): Promise<boolean> {\n const fn = config.access?.[operation];\n if (!fn) return true;\n return await fn(context);\n}\n\n// Runs config.access[operation](context) if configured, throwing\n// CadmusAccessDeniedError when it resolves false. No access function for\n// an operation means that operation is unconditionally allowed — matches\n// the pre-Section-2 default of \"no enforcement at all\".\nasync function checkAccess<TContext>(\n config: CollectionConfig,\n operation: keyof CollectionAccess,\n context: TContext,\n): Promise<void> {\n if (await can(config, operation, context)) return;\n throw new CadmusAccessDeniedError(\n `Access denied for \"${operation}\" on collection \"${config.slug}\"`,\n );\n}\n\nasync function runBeforeChange(\n config: CollectionConfig,\n data: AnyRecord,\n): Promise<AnyRecord> {\n let result = data;\n for (const hook of config.hooks?.beforeChange ?? []) {\n result = (await hook({ data: result })) as AnyRecord;\n }\n return result;\n}\n\nasync function runAfterChange(\n config: CollectionConfig,\n doc: AnyRecord,\n operation: \"create\" | \"update\",\n): Promise<void> {\n for (const hook of config.hooks?.afterChange ?? []) {\n await hook({ doc, operation });\n }\n}\n\nasync function runReadHooks(\n config: CollectionConfig,\n doc: AnyRecord,\n): Promise<AnyRecord> {\n let result = doc;\n for (const hook of config.hooks?.beforeRead ?? []) {\n result = (await hook({ doc: result })) as AnyRecord;\n }\n for (const hook of config.hooks?.afterRead ?? []) {\n result = (await hook({ doc: result })) as AnyRecord;\n }\n return result;\n}\n\nfunction hasReadHooks(config: CollectionConfig): boolean {\n return Boolean(\n config.hooks?.beforeRead?.length || config.hooks?.afterRead?.length,\n );\n}\n\nasync function runBeforeDelete(\n config: CollectionConfig,\n id: number,\n): Promise<void> {\n for (const hook of config.hooks?.beforeDelete ?? []) {\n await hook({ id });\n }\n}\n\nasync function runAfterDelete(\n config: CollectionConfig,\n id: number,\n): Promise<void> {\n for (const hook of config.hooks?.afterDelete ?? []) {\n await hook({ id });\n }\n}\n\n// Keeps a collection's FTS5 companion table (see codegen.ts's\n// collectionSearchTableSQL) in sync on every create/update — issue #29's\n// \"populated via an afterChange hook\" wording, but wired in here rather\n// than exposed on `CollectionHooks.afterChange` since it's derived\n// entirely from `config.search` (no operator-authored hook function),\n// the same precedent as the `versions` companion table being built into\n// createVersionedLocalApi rather than a user-facing hook. FTS5 has no\n// native UPSERT; a plain DELETE-then-INSERT keyed by rowid (== the main\n// table's `id`) is the standard pattern for keeping an external,\n// non-content FTS5 table in sync with its source row.\nasync function syncSearchIndex(\n db: BaseSQLiteDatabase<\"async\", unknown>,\n config: CollectionConfig,\n doc: AnyRecord,\n): Promise<void> {\n const fields = config.search?.fields;\n if (!fields?.length) return;\n const id = doc.id;\n if (typeof id !== \"number\") return;\n const fts = sql.identifier(collectionSearchTableName(config));\n const columnList = sql.join(\n fields.map((key) => sql.identifier(key)),\n sql.raw(\", \"),\n );\n const values = extractSearchText(config, doc);\n const valueList = sql.join(\n values.map((value) => sql`${value}`),\n sql.raw(\", \"),\n );\n await db.run(sql`DELETE FROM ${fts} WHERE rowid = ${id}`);\n await db.run(\n sql`INSERT INTO ${fts} (rowid, ${columnList}) VALUES (${id}, ${valueList})`,\n );\n}\n\nasync function removeFromSearchIndex(\n db: BaseSQLiteDatabase<\"async\", unknown>,\n config: CollectionConfig,\n id: number,\n): Promise<void> {\n if (!config.search?.fields.length) return;\n const fts = sql.identifier(collectionSearchTableName(config));\n await db.run(sql`DELETE FROM ${fts} WHERE rowid = ${id}`);\n}\n\nexport function createLocalApi<TTable extends AnyTable, TContext = unknown>(\n db: BaseSQLiteDatabase<\"async\", unknown>,\n table: TTable,\n config: CollectionConfig,\n registry?: CmsRegistry,\n): LocalApi<TTable, TContext> {\n const idColumn = table.id;\n // Group fields are the only reason a document's shape (nested) ever\n // differs from its row's shape (flat columns) — skip the flatten/nest\n // round-trip entirely for the common case of a collection with none, so\n // every existing collection (none of which have group fields yet) pays\n // zero cost for this.\n const hasGroupFields = Object.values(config.fields).some(\n (field) => field.type === \"group\",\n );\n const toFlatDoc = (doc: Record<string, unknown>) =>\n hasGroupFields ? flattenDoc(config.fields, doc) : doc;\n const toNestedDoc = (row: Record<string, unknown>) =>\n hasGroupFields ? (nestDoc(config.fields, row) as AnyRecord) : row;\n\n return {\n async find(context, options) {\n await checkAccess(config, \"read\", context);\n if (\n options?.depth !== undefined &&\n options.depth !== 0 &&\n options.depth !== 1\n ) {\n throw new CadmusCmsError(\n `Relationship resolution depth ${options.depth} is not supported for collection \"${config.slug}\" (only 0 and 1 are)`,\n );\n }\n let query = db.select().from(table).where(options?.where).$dynamic();\n if (options?.orderBy !== undefined) {\n query = query.orderBy(\n ...(Array.isArray(options.orderBy)\n ? options.orderBy\n : [options.orderBy]),\n );\n }\n if (options?.limit !== undefined) query = query.limit(options.limit);\n if (options?.offset !== undefined) query = query.offset(options.offset);\n const rows = await query;\n const nestedRows = rows.map((row) =>\n toNestedDoc(row as Record<string, unknown>),\n );\n const afterHooks = hasReadHooks(config)\n ? await Promise.all(\n nestedRows.map((row) =>\n runReadHooks(config, row as Record<string, unknown>),\n ),\n )\n : nestedRows;\n const resolved =\n options?.depth === 1\n ? await resolveRelationships(\n db,\n config,\n afterHooks as AnyRecord[],\n context,\n registry,\n )\n : afterHooks;\n return resolved as InferSelectModel<TTable>[];\n },\n\n async count(context, options) {\n await checkAccess(config, \"read\", context);\n const [row] = await db\n .select({ value: countRows() })\n .from(table)\n .where(options?.where);\n return row?.value ?? 0;\n },\n\n async findByID(context, id, options) {\n await checkAccess(config, \"read\", context);\n if (\n options?.depth !== undefined &&\n options.depth !== 0 &&\n options.depth !== 1\n ) {\n throw new CadmusCmsError(\n `Relationship resolution depth ${options.depth} is not supported for collection \"${config.slug}\" (only 0 and 1 are)`,\n );\n }\n const [row] = await db.select().from(table).where(eq(idColumn, id));\n if (!row) notFound(config, id);\n const nestedRow = toNestedDoc(row as Record<string, unknown>);\n const afterHooks = hasReadHooks(config)\n ? await runReadHooks(config, nestedRow as Record<string, unknown>)\n : nestedRow;\n const resolved =\n options?.depth === 1\n ? (\n await resolveRelationships(\n db,\n config,\n [afterHooks as AnyRecord],\n context,\n registry,\n )\n )[0]\n : afterHooks;\n return resolved as InferSelectModel<TTable>;\n },\n\n async search(context, query, options) {\n await checkAccess(config, \"read\", context);\n if (!config.search?.fields.length) {\n throw new CadmusCmsError(\n `Collection \"${config.slug}\" has no \"search\" config — cannot run search()`,\n );\n }\n const fts = sql.identifier(collectionSearchTableName(config));\n const limit = options?.limit ?? 20;\n const rows = await db.all(sql`\n SELECT ${table}.* FROM ${fts}\n JOIN ${table} ON ${idColumn} = ${fts}.rowid\n WHERE ${fts} MATCH ${query}\n ORDER BY rank\n LIMIT ${limit}\n `);\n return rows.map((row) =>\n toNestedDoc(row as Record<string, unknown>),\n ) as InferSelectModel<TTable>[];\n },\n\n async create(context, input) {\n await checkAccess(config, \"create\", context);\n // beforeChange runs before validation so a hook may supply or default\n // a required field (e.g. the SEO plugin defaulting metaTitle). Hooks\n // always see/return the nested document shape — flattening for the\n // DB write happens after, never inside a hook.\n const data = await runBeforeChange(\n config,\n input as Record<string, unknown>,\n );\n const flatData = toFlatDoc(data);\n validateRequiredFields(config, flatData);\n rejectUnknownFields(config, flatData);\n // Chainable field rules (#16) — required-flag and unknown-field checks\n // above stay; this adds value-level rules (min/max/regex/unique/\n // reference/custom) and throws CadmusValidationError with per-field\n // violations. Runs after beforeChange so a hook-supplied value is\n // validated, and before the insert so unique/reference pre-check\n // rather than relying on a raw DB constraint error.\n await assertValid(config, data as Record<string, unknown>, {\n operation: \"create\",\n db,\n table,\n registry,\n });\n let row: InferSelectModel<TTable> | undefined;\n try {\n const [inserted] = await db\n .insert(table)\n // biome-ignore lint/suspicious/noExplicitAny: TTable is an abstract generic here, so drizzle's column-mapped insert types can't narrow against it — InferInsertModel<TTable> already gives callers the real, concrete typing.\n .values(flatData as any)\n .returning();\n row = inserted as InferSelectModel<TTable>;\n } catch (error) {\n wrapWriteError(config, error);\n }\n // wrapWriteError returns `never`, so reaching here means the insert\n // succeeded and `row` is set. afterChange runs outside the try so its\n // side-effect errors aren't mis-reported as write failures.\n const doc = toNestedDoc(row as AnyRecord);\n await syncSearchIndex(db, config, doc as AnyRecord);\n await runAfterChange(config, doc as Record<string, unknown>, \"create\");\n return doc as InferSelectModel<TTable>;\n },\n\n async update(context, id, input) {\n await checkAccess(config, \"update\", context);\n const data = await runBeforeChange(\n config,\n input as Record<string, unknown>,\n );\n const flatData = toFlatDoc(data);\n rejectUnknownFields(config, flatData);\n // Validate only the fields this partial update actually carries — a\n // partial update must not fail an absent field's rules (it isn't\n // changing it). `unique` excludes this row by id.\n await assertValid(config, data as Record<string, unknown>, {\n operation: \"update\",\n id,\n onlyFields: new Set(Object.keys(flatData)),\n db,\n table,\n registry,\n });\n let row: InferSelectModel<TTable> | undefined;\n try {\n const [updated] = await db\n .update(table)\n // biome-ignore lint/suspicious/noExplicitAny: see create() above\n .set(flatData as any)\n .where(eq(idColumn, id))\n .returning();\n if (!updated) notFound(config, id);\n row = updated as InferSelectModel<TTable>;\n } catch (error) {\n wrapWriteError(config, error);\n }\n const doc = toNestedDoc(row as AnyRecord);\n await syncSearchIndex(db, config, doc as AnyRecord);\n await runAfterChange(config, doc as Record<string, unknown>, \"update\");\n return doc as InferSelectModel<TTable>;\n },\n\n async deleteByID(context, id) {\n await checkAccess(config, \"delete\", context);\n await runBeforeDelete(config, id);\n const [rawRow] = await db\n .delete(table)\n .where(eq(idColumn, id))\n .returning();\n if (!rawRow) notFound(config, id);\n const row = toNestedDoc(rawRow as Record<string, unknown>);\n await removeFromSearchIndex(db, config, id);\n await runAfterDelete(config, id);\n return row as InferSelectModel<TTable>;\n },\n };\n}\n\nfunction notFoundVersion(config: CollectionConfig, id: number): never {\n throw new CadmusCmsError(`No \"${config.slug}\" version found with id ${id}`);\n}\n\n/**\n * Extends {@link LocalApi} with draft/publish operations for a collection\n * that opted in via `CollectionConfig.versions.drafts` (see codegen.ts's\n * `collectionVersionsTable`). A separate interface (not a wider\n * `LocalApi`) so non-versioned collections' types don't grow these methods\n * — TypeScript can't conditionally widen `createLocalApi`'s return type\n * off a runtime config value, so this is `createVersionedLocalApi`'s own\n * factory rather than a branch inside `createLocalApi`.\n *\n * Scope, deliberately: a document is always created via the inherited\n * `create()` first (existing behavior, unaffected by versioning) — these\n * methods operate against an *existing* row. `saveDraft` never validates\n * required fields (an incomplete draft is valid); `publish` runs the same\n * full validation `create`/`update` do, since publishing is what makes a\n * version the public-facing document. Plain `find`/`findByID` are\n * unchanged by any of this — they always return the main table's current\n * row regardless of `publishedVersionId`; filtering reads to\n * published-only content is not this phase's concern.\n */\nexport interface VersionedLocalApi<\n TTable extends AnyTable,\n TVersionsTable extends AnyTable,\n TContext = unknown,\n> extends LocalApi<TTable, TContext> {\n findVersions(\n context: TContext,\n parentId: number,\n ): Promise<InferSelectModel<TVersionsTable>[]>;\n /** Inserts a new version row holding `input` as a draft snapshot. */\n saveDraft(\n context: TContext,\n id: number,\n input: Partial<InferInsertModel<TTable>>,\n ): Promise<InferSelectModel<TVersionsTable>>;\n /** Copies a version's snapshot onto the main row and marks it published. */\n publish(\n context: TContext,\n versionId: number,\n ): Promise<InferSelectModel<TTable>>;\n /** Clears the main row's published pointer; the row's data is untouched. */\n unpublish(context: TContext, id: number): Promise<InferSelectModel<TTable>>;\n /**\n * Field-level diff (issue #14) between two version snapshots' `versionData`\n * — the per-field added/removed/changed list a version-history UI renders.\n * Both versions must belong to the same parent. Bookkeeping keys\n * (`id`/`createdAt`/`status`/`publishedVersionId`) are ignored.\n */\n diffVersions(\n context: TContext,\n fromVersionId: number,\n toVersionId: number,\n ): Promise<FieldChange[]>;\n}\n\nexport function createVersionedLocalApi<\n TTable extends AnyTable,\n TVersionsTable extends AnyTable,\n TContext = unknown,\n>(\n db: BaseSQLiteDatabase<\"async\", unknown>,\n table: TTable,\n versionsTable: TVersionsTable,\n config: CollectionConfig,\n registry?: CmsRegistry,\n): VersionedLocalApi<TTable, TVersionsTable, TContext> {\n const base = createLocalApi<TTable, TContext>(db, table, config, registry);\n const idColumn = table.id;\n const versionsIdColumn = versionsTable.id;\n const versionsParentIdColumn = versionsTable.parentId;\n\n return {\n ...base,\n\n async findVersions(context, parentId) {\n await checkAccess(config, \"read\", context);\n const rows = await db\n .select()\n .from(versionsTable)\n .where(eq(versionsParentIdColumn, parentId))\n .orderBy(desc(versionsIdColumn));\n return rows as InferSelectModel<TVersionsTable>[];\n },\n\n async saveDraft(context, id, input) {\n await checkAccess(config, \"update\", context);\n const [parent] = await db.select().from(table).where(eq(idColumn, id));\n if (!parent) notFound(config, id);\n const data = await runBeforeChange(\n config,\n input as Record<string, unknown>,\n );\n rejectUnknownFields(config, data);\n const insertValues = {\n parentId: id,\n versionData: data,\n status: \"draft\",\n // biome-ignore lint/suspicious/noExplicitAny: TVersionsTable is abstract here, same rationale as createLocalApi.create's .values() cast\n } as any;\n const [row] = await db\n .insert(versionsTable)\n .values(insertValues)\n .returning();\n return row as InferSelectModel<TVersionsTable>;\n },\n\n async publish(context, versionId) {\n await checkAccess(config, \"publish\", context);\n const [version] = await db\n .select()\n .from(versionsTable)\n .where(eq(versionsIdColumn, versionId));\n if (!version) notFoundVersion(config, versionId);\n const versionRecord = version as Record<string, unknown>;\n const data = await runBeforeChange(\n config,\n versionRecord.versionData as Record<string, unknown>,\n );\n validateRequiredFields(config, data);\n rejectUnknownFields(config, data);\n const parentId = versionRecord.parentId as number;\n // Publishing writes the whole version snapshot to the live row, so\n // validate every field (not a partial). `unique` excludes the parent\n // row by its own id.\n await assertValid(config, data, {\n operation: \"update\",\n id: parentId,\n db,\n table,\n registry,\n });\n let doc: InferSelectModel<TTable> | undefined;\n try {\n const [row] = await db\n .update(table)\n // biome-ignore lint/suspicious/noExplicitAny: see createLocalApi.update's .set() cast\n .set({ ...data, publishedVersionId: versionId } as any)\n .where(eq(idColumn, parentId))\n .returning();\n if (!row) notFound(config, parentId);\n doc = row as InferSelectModel<TTable>;\n } catch (error) {\n wrapWriteError(config, error);\n }\n await db\n .update(versionsTable)\n // biome-ignore lint/suspicious/noExplicitAny: status is a fixed two-value enum column\n .set({ status: \"published\" } as any)\n .where(eq(versionsIdColumn, versionId));\n await syncSearchIndex(db, config, doc as AnyRecord);\n // publish() writes to an already-existing row, never a new one —\n // counts as \"update\" the same way createLocalApi.update() does.\n await runAfterChange(config, doc as Record<string, unknown>, \"update\");\n return doc as InferSelectModel<TTable>;\n },\n\n async unpublish(context, id) {\n await checkAccess(config, \"publish\", context);\n const [row] = await db\n .update(table)\n // biome-ignore lint/suspicious/noExplicitAny: publishedVersionId is a bookkeeping column generated by codegen, not part of InferInsertModel<TTable>\n .set({ publishedVersionId: null } as any)\n .where(eq(idColumn, id))\n .returning();\n if (!row) notFound(config, id);\n return row as InferSelectModel<TTable>;\n },\n\n async diffVersions(context, fromVersionId, toVersionId) {\n await checkAccess(config, \"read\", context);\n const rows = await db\n .select()\n .from(versionsTable)\n .where(inArray(versionsIdColumn, [fromVersionId, toVersionId]));\n const byId = new Map(\n rows.map((r) => [\n (r as Record<string, unknown>).id as number,\n (r as Record<string, unknown>).versionData as Record<\n string,\n JsonValue\n >,\n ]),\n );\n const before = byId.get(fromVersionId);\n const after = byId.get(toVersionId);\n if (!before) notFoundVersion(config, fromVersionId);\n if (!after) notFoundVersion(config, toVersionId);\n // Ignore bookkeeping columns — only real content fields are of interest\n // in a version-history view.\n return diffDocuments(before, after, {\n ignore: [\"id\", \"createdAt\", \"status\", \"publishedVersionId\"],\n });\n },\n };\n}\n","// Copyright (c) 2026 BowenLabs. All rights reserved.\n// Cadmus is MIT licensed. See LICENSE in the repo root.\n\nimport type { CmsConfig, CollectionConfig } from \"./types.js\";\n\nexport interface CollectionMeta {\n slug: string;\n fields: CollectionConfig[\"fields\"];\n /** Whether `LocalApi.search()` is usable for this collection — see `CollectionConfig.search`. */\n searchable: boolean;\n}\n\n// Serializable introspection contract a CMS admin (or any other\n// consumer) uses to render generic UI without importing CollectionConfig\n// or CmsConfig directly. CollectionConfig is already plain, serializable\n// data — this is a stable, narrow public surface over it, not a\n// transformation.\nexport function getCollectionsMeta(config: CmsConfig): CollectionMeta[] {\n return config.collections.map((collection) => ({\n slug: collection.slug,\n fields: collection.fields,\n searchable: Boolean(collection.search?.fields.length),\n }));\n}\n","// Copyright (c) 2026 BowenLabs. All rights reserved.\n// Cadmus is MIT licensed. See LICENSE in the repo root.\n\nimport type { LocalApi } from \"./localApi.js\";\nimport { computePatch, type Patch } from \"./patch.js\";\nimport type { JsonValue } from \"./types.js\";\n\n/**\n * Content-migration runner (issue #18) — adopts Sanity's `sanity/migrate`\n * idea (pattern, not code): a versioned, repeatable transform over a\n * collection's stored documents, for reshaping content when a block/field\n * type changes (distinct from Drizzle *schema* migrations, which only touch\n * columns — this reshapes the JSON content inside them).\n *\n * A migration declares a per-document `document(doc)` transform; the runner\n * streams every document, computes the {@link Patch} from old→new (reusing\n * #14's patch model), and either reports it (`dryRun`) or applies it via the\n * collection's Local API. Idempotent by construction: a transform that's\n * already been applied produces an empty patch, so re-running changes\n * nothing.\n */\n\ntype Doc = Record<string, JsonValue>;\n\nexport interface Migration<TDoc extends Doc = Doc> {\n /** Stable identifier — name the checked-in migration file after this. */\n name: string;\n /**\n * Transform one document. Return the reshaped document, or `undefined`\n * (or the unchanged doc) to leave it as-is. Must be pure and idempotent —\n * applying it twice yields the same result as once.\n */\n document: (doc: TDoc) => TDoc | undefined | Promise<TDoc | undefined>;\n}\n\n/** Identity helper — gives a migration definition its type + a greppable call site. */\nexport function defineMigration<TDoc extends Doc = Doc>(\n migration: Migration<TDoc>,\n): Migration<TDoc> {\n return migration;\n}\n\nexport interface MigrationChange {\n id: number;\n patch: Patch;\n}\n\nexport interface MigrationResult {\n migration: string;\n dryRun: boolean;\n scanned: number;\n changed: number;\n /** Per-document patches (always populated — the dry-run report). */\n changes: MigrationChange[];\n errors: string[];\n}\n\nexport interface RunMigrationOptions<TContext> {\n // biome-ignore lint/suspicious/noExplicitAny: the runner is generic across any collection's table type\n api: LocalApi<any, TContext>;\n /** Context passed to the Local API's read/update (access + hooks). */\n context: TContext;\n /** When true, compute + report patches but write nothing. Default false. */\n dryRun?: boolean;\n}\n\n// A patch's net effect as a Local API update payload: `set` → the value,\n// `unset` → null (the DB-level \"cleared\" representation). Only changed\n// fields are sent, so hooks/validation see a minimal partial update.\nfunction patchToUpdate(patch: Patch): Record<string, JsonValue | null> {\n const values: Record<string, JsonValue | null> = {};\n for (const op of patch) {\n values[op.path] = op.op === \"set\" ? op.value : null;\n }\n return values;\n}\n\n/**\n * Run a migration over every document in a collection. Reads all documents\n * through `api.find`, applies `migration.document`, and (unless `dryRun`)\n * writes the resulting patch through `api.update`. Returns a report of what\n * changed — run it `dryRun` first, then apply.\n */\nexport async function runMigration<TContext>(\n migration: Migration,\n options: RunMigrationOptions<TContext>,\n): Promise<MigrationResult> {\n const { api, context, dryRun = false } = options;\n const rows = (await api.find(context)) as Array<Doc & { id: number }>;\n\n const changes: MigrationChange[] = [];\n const errors: string[] = [];\n let changed = 0;\n\n for (const before of rows) {\n try {\n const after = (await migration.document(before)) ?? before;\n const patch = computePatch(before, after);\n if (patch.length === 0) continue;\n changes.push({ id: before.id, patch });\n changed++;\n if (!dryRun) {\n await api.update(context, before.id, patchToUpdate(patch));\n }\n } catch (err) {\n errors.push(`document ${before.id}: ${String(err)}`);\n }\n }\n\n return {\n migration: migration.name,\n dryRun,\n scanned: rows.length,\n changed,\n changes,\n errors,\n };\n}\n","// Copyright (c) 2026 BowenLabs. All rights reserved.\n// Cadmus is MIT licensed. See LICENSE in the repo root.\n\nimport { CadmusCmsError } from \"../errors.js\";\nimport {\n type CmsConfig,\n type CollectionConfig,\n type FieldConfig,\n flattenFields,\n} from \"./types.js\";\n\nfunction toSnakeCase(value: string): string {\n return value.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`);\n}\n\nfunction quote(value: string): string {\n return JSON.stringify(value);\n}\n\n// Mirrors codegen.ts's fieldToColumn switch, emitting drizzle-orm\n// source text instead of building a runtime column. Kept as its own\n// switch (not shared code) since the two have different outputs\n// (Column vs. string) — codegen.test.ts's schema-parity assertions are\n// what keep this in sync with codegen.ts's actual runtime behavior.\nfunction fieldToColumnSource(\n key: string,\n field: FieldConfig,\n usedBuilders: Set<string>,\n): string {\n const columnName = field.name ?? toSnakeCase(key);\n\n switch (field.type) {\n case \"text\":\n case \"upload\": {\n usedBuilders.add(\"text\");\n let source = `text(${quote(columnName)})`;\n if (field.required) source += \".notNull()\";\n if (field.unique) source += \".unique()\";\n if (field.defaultValue !== undefined) {\n source += `.default(${quote(field.defaultValue)})`;\n }\n return source;\n }\n case \"richText\":\n case \"array\":\n case \"json\": {\n usedBuilders.add(\"text\");\n // `.$type<JsonValue>()` mirrors codegen.ts's fieldToColumn — see\n // types.ts's JsonValue doc comment for why drizzle's inferred\n // `unknown` needs overriding here. `group` fields never reach here —\n // see flattenFields below.\n let source = `text(${quote(columnName)}, { mode: \"json\" }).$type<JsonValue>()`;\n if (field.required) source += \".notNull()\";\n if (field.defaultValue !== undefined) {\n source += `.default(${JSON.stringify(field.defaultValue)})`;\n }\n return source;\n }\n case \"relationship\": {\n // hasMany relationship fields never reach here — collectionToTableSource\n // filters them out (see its own comment).\n usedBuilders.add(\"integer\");\n let source = `integer(${quote(columnName)})`;\n if (field.required) source += \".notNull()\";\n return source;\n }\n case \"select\": {\n usedBuilders.add(\"text\");\n const options = field.options.map(quote).join(\", \");\n let source = `text(${quote(columnName)}, { enum: [${options}] })`;\n if (field.required) source += \".notNull()\";\n if (field.defaultValue !== undefined) {\n source += `.default(${quote(field.defaultValue)})`;\n }\n return source;\n }\n case \"number\": {\n if (field.autoIncrement) {\n usedBuilders.add(\"integer\");\n return `integer(${quote(columnName)}).primaryKey({ autoIncrement: true })`;\n }\n usedBuilders.add(\"real\");\n let source = `real(${quote(columnName)})`;\n if (field.required) source += \".notNull()\";\n if (field.defaultValue !== undefined) {\n source += `.default(${field.defaultValue})`;\n }\n return source;\n }\n case \"date\": {\n usedBuilders.add(\"integer\");\n const mode = field.mode === \"timestamp_ms\" ? \"timestamp_ms\" : \"timestamp\";\n let source = `integer(${quote(columnName)}, { mode: ${quote(mode)} })`;\n if (field.required) source += \".notNull()\";\n if (field.defaultValue === \"now\") {\n source += \".$defaultFn(() => new Date())\";\n } else if (field.defaultValue instanceof Date) {\n source += `.$defaultFn(() => new Date(${field.defaultValue.getTime()}))`;\n }\n return source;\n }\n default:\n throw new CadmusCmsError(\n `Field type \"${(field as FieldConfig).type}\" is not yet supported by cadmus/cms schema-gen`,\n );\n }\n}\n\nfunction collectionToTableSource(\n config: CollectionConfig,\n usedBuilders: Set<string>,\n): string {\n const fieldLines = Object.entries(flattenFields(config.fields))\n // hasMany relationships have no column on this table — emitted as a\n // separate join table instead (see relationshipJoinTableSource).\n .filter(([, field]) => !(field.type === \"relationship\" && field.hasMany))\n .map(\n ([key, field]) =>\n ` ${key}: ${fieldToColumnSource(key, field, usedBuilders)},`,\n );\n // Mirrors codegen.ts's collectionToTable: a bookkeeping column, not a\n // content field, present only when this collection opts into versioning.\n if (config.versions?.drafts) {\n usedBuilders.add(\"integer\");\n fieldLines.push(' publishedVersionId: integer(\"published_version_id\"),');\n }\n return `export const ${config.slug} = sqliteTable(${quote(config.slug)}, {\\n${fieldLines.join(\"\\n\")}\\n});`;\n}\n\n// Mirrors codegen.ts's collectionVersionsTable.\nfunction versionsTableSource(\n config: CollectionConfig,\n usedBuilders: Set<string>,\n): string {\n usedBuilders.add(\"integer\");\n usedBuilders.add(\"text\");\n const tableName = `${config.slug}_versions`;\n return (\n `export const ${tableName} = sqliteTable(${quote(tableName)}, {\\n` +\n ' id: integer(\"id\").primaryKey({ autoIncrement: true }),\\n' +\n ' parentId: integer(\"parent_id\").notNull(),\\n' +\n ' versionData: text(\"version_data\", { mode: \"json\" }).$type<JsonValue>().notNull(),\\n' +\n ' status: text(\"status\", { enum: [\"draft\", \"published\"] }).notNull(),\\n' +\n ' createdAt: integer(\"created_at\", { mode: \"timestamp\" }).$defaultFn(() => new Date()),\\n' +\n \"});\"\n );\n}\n\n// Mirrors codegen.ts's relationshipJoinTables, emitting one\n// sqliteTable() source block per hasMany relationship field.\nfunction relationshipJoinTableSources(\n config: CollectionConfig,\n usedBuilders: Set<string>,\n): string[] {\n const blocks: string[] = [];\n for (const [key, field] of Object.entries(config.fields)) {\n if (field.type !== \"relationship\" || !field.hasMany) continue;\n usedBuilders.add(\"integer\");\n const tableName = `${config.slug}_${key}`;\n const ownColumn = `${config.slug}_id`;\n const relatedColumn = `${field.relationTo}_id`;\n blocks.push(\n `export const ${tableName} = sqliteTable(${quote(tableName)}, {\\n` +\n ` ${ownColumn}: integer(${quote(ownColumn)}).notNull(),\\n` +\n ` ${relatedColumn}: integer(${quote(relatedColumn)}).notNull(),\\n` +\n \"});\",\n );\n }\n return blocks;\n}\n\n// Generates the full TS source for a consuming app's generated Drizzle\n// schema file from a CmsConfig. Pure string generation — the caller (a\n// script run via tsx) is responsible for writing the result to disk\n// (and formatting it). Cadmus has no opinion on what the app names its\n// config file or where it lives — that's app-specific, never hardcoded\n// here (see CLAUDE.md \"Code in packages/cadmus/ must not contain\n// anything [app]-specific\").\nexport function generateSchemaSource(config: CmsConfig): string {\n const usedBuilders = new Set<string>([\"sqliteTable\"]);\n const blocks = config.collections.flatMap((collection) => [\n collectionToTableSource(collection, usedBuilders),\n ...relationshipJoinTableSources(collection, usedBuilders),\n ...(collection.versions?.drafts\n ? [versionsTableSource(collection, usedBuilders)]\n : []),\n ]);\n const importList = [...usedBuilders].sort().join(\", \");\n // Every JSON column emitted above carries a `.$type<JsonValue>()` call —\n // detected by string search rather than threading a second tracking set\n // through every *Source function, since this is the one place that\n // needs to know about it.\n const needsJsonValue = blocks.some((block) =>\n block.includes(\".$type<JsonValue>()\"),\n );\n return [\n \"// Generated by @thebes/cadmus/cms — do not hand-edit.\",\n \"// Source: this app's CmsConfig (see defineCmsConfig).\",\n // Biome's import-sort assist (run over consuming apps, not this\n // generated file directly) orders \"@thebes/cadmus/cms\" before\n // \"drizzle-orm/sqlite-core\" alphabetically — matching that order here\n // means a consumer running `biome check --write` over their repo\n // never sees this generated file flagged as needing a fix.\n ...(needsJsonValue\n ? ['import type { JsonValue } from \"@thebes/cadmus/cms\";']\n : []),\n `import { ${importList} } from \"drizzle-orm/sqlite-core\";`,\n \"\",\n blocks.join(\"\\n\\n\"),\n \"\",\n ].join(\"\\n\");\n}\n","// Copyright (c) 2026 BowenLabs. All rights reserved.\n// Cadmus is MIT licensed. See LICENSE in the repo root.\n\nimport type {\n CmsConfig,\n CollectionAdminConfig,\n CollectionConfig,\n} from \"./types.js\";\n\n/**\n * Cadmea's Structure Builder — the framework half of issue #12.\n *\n * Adopts Sanity's `sanity/structure` idea (pattern, not code): **decouple\n * the admin nav from the raw collection list.** Instead of mapping every\n * `config.collections` entry to an `/admin/<slug>` link — which surfaces\n * system/log tables as editable links and produces dead links — the sidebar\n * renders from an explicit, grouped structure derived here from each\n * collection's `admin` hints (see {@link CollectionAdminConfig}) plus\n * optional per-slug overrides supplied at the call site.\n *\n * Pure data in / pure data out: no SolidJS, no DOM, no server imports — so\n * it's safe to import from a client studio component (e.g. the site's\n * `PanelNav`) and trivially testable.\n */\n\n/** Default group heading for collections that don't declare `admin.group`. */\nexport const DEFAULT_STUDIO_GROUP = \"Content\";\n\n/** One navigable collection entry in the studio sidebar. */\nexport interface StudioStructureItem {\n /** The collection's slug. */\n slug: string;\n /** Human label — `admin.label`, else the capitalized slug. */\n label: string;\n /** Where the sidebar link points (`/admin/<slug>`, configurable prefix). */\n href: string;\n /** Read-only collections are viewable but not editable in the studio. */\n readOnly: boolean;\n /**\n * Singletons link straight to their editor rather than a list+create\n * flow. (The href is identical; the renderer uses this to skip the list.)\n */\n singleton: boolean;\n /** Opaque icon identifier from `admin.icon`, if any. */\n icon?: string;\n}\n\n/** A titled group of sidebar items, in render order. */\nexport interface StudioStructureGroup {\n title: string;\n items: StudioStructureItem[];\n}\n\nexport interface BuildStudioStructureOptions {\n /**\n * Per-slug presentation overrides, merged over each collection's own\n * `admin` block (override keys win). The escape hatch for plugin-injected\n * collections (`products`, `payments`, `webhook_events`, …) that can't\n * carry an `admin` block in hand-written config — the studio declares\n * their presentation here, exactly like Sanity defines structure at the\n * studio level rather than on the schema.\n */\n overrides?: Record<string, CollectionAdminConfig>;\n /**\n * Explicit group ordering by title. Groups listed here render first, in\n * this order; any remaining groups follow in first-appearance order. A\n * group title absent from `config`'s collections simply doesn't appear.\n */\n groupOrder?: readonly string[];\n /**\n * Link prefix for each item's `href`. Defaults to `/admin`, producing\n * `/admin/<slug>`. No trailing slash.\n */\n basePath?: string;\n}\n\nfunction capitalize(value: string): string {\n return value.length === 0 ? value : value[0].toUpperCase() + value.slice(1);\n}\n\nfunction resolveAdmin(\n collection: CollectionConfig,\n overrides: Record<string, CollectionAdminConfig> | undefined,\n): CollectionAdminConfig {\n return { ...collection.admin, ...overrides?.[collection.slug] };\n}\n\n/**\n * Build the studio sidebar structure from a resolved CMS config.\n *\n * - Hidden collections (`admin.hidden`) are dropped entirely.\n * - Each remaining collection is placed in its `admin.group` (or\n * {@link DEFAULT_STUDIO_GROUP}).\n * - Within a group, items sort by `admin.order` (ascending; unset sorts\n * after set), then by their original position in `config.collections` —\n * so config order is the stable tiebreaker.\n * - Groups render in `options.groupOrder` first, then first-appearance\n * order for the rest.\n *\n * The input is expected to be the *resolved* config (post-plugins), since\n * that's what carries plugin-injected collections like `products`.\n */\nexport function buildStudioStructure(\n config: CmsConfig,\n options: BuildStudioStructureOptions = {},\n): StudioStructureGroup[] {\n const basePath = options.basePath ?? \"/admin\";\n const groupOrder = options.groupOrder ?? [];\n\n // Preserve original index so config order can break ties deterministically.\n const ranked = config.collections.map((collection, index) => ({\n collection,\n index,\n admin: resolveAdmin(collection, options.overrides),\n }));\n\n const groups = new Map<string, StudioStructureItem[]>();\n // Track first-appearance order of group titles for the fallback ordering.\n const appearance: string[] = [];\n\n for (const { collection, admin } of ranked) {\n if (admin.hidden) continue;\n const title = admin.group ?? DEFAULT_STUDIO_GROUP;\n if (!groups.has(title)) {\n groups.set(title, []);\n appearance.push(title);\n }\n // biome-ignore lint/style/noNonNullAssertion: title was just ensured present above\n groups.get(title)!.push({\n slug: collection.slug,\n label: admin.label ?? capitalize(collection.slug),\n href: `${basePath}/${collection.slug}`,\n readOnly: admin.readOnly ?? false,\n singleton: admin.singleton ?? false,\n ...(admin.icon ? { icon: admin.icon } : {}),\n });\n }\n\n // Sort items within each group by (order ?? Infinity), then config index.\n // We rebuild a slug -> {order,index} lookup so the comparator stays O(1).\n const meta = new Map(\n ranked.map(({ collection, index, admin }) => [\n collection.slug,\n { order: admin.order ?? Number.POSITIVE_INFINITY, index },\n ]),\n );\n for (const items of groups.values()) {\n items.sort((a, b) => {\n // biome-ignore lint/style/noNonNullAssertion: every item came from `ranked`\n const ma = meta.get(a.slug)!;\n // biome-ignore lint/style/noNonNullAssertion: every item came from `ranked`\n const mb = meta.get(b.slug)!;\n return ma.order - mb.order || ma.index - mb.index;\n });\n }\n\n // Order groups: explicit groupOrder first (in that order, only if present),\n // then remaining groups by first appearance.\n const orderedTitles = [\n ...groupOrder.filter((title) => groups.has(title)),\n ...appearance.filter((title) => !groupOrder.includes(title)),\n ];\n\n return orderedTitles.map((title) => ({\n title,\n // biome-ignore lint/style/noNonNullAssertion: orderedTitles is derived from groups' keys\n items: groups.get(title)!,\n }));\n}\n","// Copyright (c) 2026 BowenLabs. All rights reserved.\n// Cadmus is MIT licensed. See LICENSE in the repo root.\n\n/**\n * Visual editing / click-to-edit (issue #15) — adopts Sanity's\n * Presentation/visual-editing idea (pattern, not code): the rendered page\n * (in a preview context) tags editable regions with the source field they\n * came from, and an overlay turns those regions into click targets that tell\n * the studio which field to focus.\n *\n * This module ships the two reusable, framework-agnostic primitives:\n * 1. **Encoding** — `editAttr({ collection, id, field })` produces a data\n * attribute the server renderer spreads onto an element; `decodeEditRef`\n * reads it back. Pure, testable.\n * 2. **Overlay** — `mountVisualEditing()` (browser-only; references `document`\n * lazily, so importing it server-side is harmless) highlights tagged\n * elements on hover and, on click, calls `onSelect` and `postMessage`s the\n * ref to the parent window (the studio shell hosting the preview iframe).\n *\n * The studio side listens for that message and navigates to\n * `/admin/<collection>/<id>` (and may focus `<field>`); that wiring is\n * consumer-side and not prescribed here.\n */\n\n/** A reference from a rendered region back to the field that produced it. */\nexport interface EditRef {\n collection: string;\n id: number;\n field: string;\n}\n\n/** The data attribute editable regions are tagged with. */\nexport const EDIT_ATTR = \"data-cadmus-edit\";\n\n/** `postMessage` payload type for a click-to-edit selection. */\nexport const VISUAL_EDIT_MESSAGE = \"cadmus:visual-edit\";\n\nexport function encodeEditRef(ref: EditRef): string {\n return `${ref.collection}:${ref.id}:${ref.field}`;\n}\n\n/** Parse an {@link EditRef} string, or null if malformed. */\nexport function decodeEditRef(value: string): EditRef | null {\n const parts = value.split(\":\");\n if (parts.length !== 3) return null;\n const [collection, idRaw, field] = parts;\n const id = Number.parseInt(idRaw, 10);\n if (!collection || !field || !Number.isFinite(id)) return null;\n return { collection, id, field };\n}\n\n/**\n * Attribute object to spread onto a rendered element so the overlay can map\n * it back to its source field, e.g. `<h1 {...editAttr({collection:'pages',\n * id, field:'title'})}>`.\n */\nexport function editAttr(ref: EditRef): Record<string, string> {\n return { [EDIT_ATTR]: encodeEditRef(ref) };\n}\n\nexport interface VisualEditingMessage {\n type: typeof VISUAL_EDIT_MESSAGE;\n ref: EditRef;\n}\n\nexport interface VisualEditingOptions {\n /** Called with the decoded ref when an editable region is clicked. */\n onSelect?: (ref: EditRef, element: Element) => void;\n /**\n * Origin to `postMessage` the selection to the parent window. Default\n * `\"*\"`. Set to the studio origin in production.\n */\n targetOrigin?: string;\n /** Outline color for the hover highlight. Default a teal accent. */\n highlightColor?: string;\n}\n\n/**\n * Mount the click-to-edit overlay. Browser-only — call from a preview page's\n * client script. Highlights `[data-cadmus-edit]` elements on hover and, on\n * click, calls `onSelect` and posts a {@link VisualEditingMessage} to the\n * parent window. Returns a cleanup function that removes the listeners.\n */\nexport function mountVisualEditing(\n options: VisualEditingOptions = {},\n): () => void {\n const { onSelect, targetOrigin = \"*\", highlightColor = \"#56c6be\" } = options;\n\n const closest = (target: EventTarget | null): HTMLElement | null => {\n if (!(target instanceof Element)) return null;\n const el = target.closest(`[${EDIT_ATTR}]`);\n return el instanceof HTMLElement ? el : null;\n };\n\n let previous: { el: HTMLElement; outline: string } | null = null;\n const clearHighlight = () => {\n if (previous) {\n previous.el.style.outline = previous.outline;\n previous = null;\n }\n };\n\n const onOver = (event: Event) => {\n const el = closest(event.target);\n if (!el || el === previous?.el) return;\n clearHighlight();\n previous = { el, outline: el.style.outline };\n el.style.outline = `2px solid ${highlightColor}`;\n el.style.outlineOffset = \"2px\";\n el.style.cursor = \"pointer\";\n };\n\n const onClick = (event: Event) => {\n const el = closest(event.target);\n if (!el) return;\n const ref = decodeEditRef(el.getAttribute(EDIT_ATTR) ?? \"\");\n if (!ref) return;\n event.preventDefault();\n event.stopPropagation();\n onSelect?.(ref, el);\n const message: VisualEditingMessage = { type: VISUAL_EDIT_MESSAGE, ref };\n window.parent?.postMessage(message, targetOrigin);\n };\n\n document.addEventListener(\"mouseover\", onOver, true);\n document.addEventListener(\"click\", onClick, true);\n\n return () => {\n clearHighlight();\n document.removeEventListener(\"mouseover\", onOver, true);\n document.removeEventListener(\"click\", onClick, true);\n };\n}\n","// Copyright (c) 2026 BowenLabs. All rights reserved.\n// Cadmus is MIT licensed. See LICENSE in the repo root.\n//\n// @thebes/cadmus/cms — afterChange-style outbound webhooks. The\n// `afterChange` hook itself only enqueues (via `@thebes/cadmus/queues`'\n// `enqueue`) — it never calls `fetch()` directly, so a slow or down\n// receiving endpoint can't add latency to a write request or get lost on\n// a single failed attempt. `deliverWebhookMessage` is the consumer-side\n// counterpart: a separate queue consumer calls it per message, and\n// `processBatch` (queues/index.ts) turns a thrown delivery failure into a\n// retry, eventually landing in that queue's configured DLQ.\n\nimport { CadmusQueueError } from \"../errors.js\";\nimport { enqueue } from \"../queues/index.js\";\nimport type { CollectionHooks } from \"./types.js\";\n\nexport interface WebhookConfig {\n /** Endpoint this webhook POSTs to on every matching event. */\n url: string;\n /** Restricts delivery to these operations. Default: both. */\n events?: Array<\"create\" | \"update\">;\n /**\n * When set, every delivery carries an `X-Cadmus-Signature` header —\n * HMAC-SHA256 (hex) over the raw JSON body — so the receiver can verify\n * the payload actually came from this Cadmus instance.\n */\n secret?: string;\n}\n\n/** The shape enqueued by `createWebhookHook`, consumed by `deliverWebhookMessage`. */\nexport interface WebhookMessage {\n url: string;\n secret?: string;\n event: \"create\" | \"update\";\n doc: Record<string, unknown>;\n /** ms since epoch, included in the signed/delivered payload. */\n timestamp: number;\n}\n\n/**\n * Builds an `afterChange` hook that enqueues a `WebhookMessage` for every\n * matching write — append the result to a collection's\n * `hooks.afterChange` array. `queue` is whatever `Queue<WebhookMessage>`\n * binding the caller's Worker has configured for webhook dispatch (see\n * wrangler.jsonc's webhook queue producer binding).\n */\nexport function createWebhookHook(\n queue: Queue<WebhookMessage>,\n config: WebhookConfig,\n): NonNullable<CollectionHooks[\"afterChange\"]>[number] {\n return async ({ doc, operation }) => {\n if (config.events && !config.events.includes(operation)) return;\n await enqueue(queue, {\n url: config.url,\n secret: config.secret,\n event: operation,\n doc,\n timestamp: Date.now(),\n });\n };\n}\n\n// Defense-in-depth, not the primary control: `global_fetch_strictly_public`\n// (set in both Workers' wrangler.jsonc) already blocks `fetch()` to\n// private/reserved IP literals at the platform level, and `WEBHOOK_URL` is\n// operator-supplied config, not attacker input. This catches the case that\n// guard doesn't: a hostname that *resolves* to a private address (or a\n// non-HTTP(S) scheme) rather than being one literally, plus a clear error\n// instead of a platform-level network failure when a deploy is\n// misconfigured.\nconst BLOCKED_HOSTNAME_PATTERNS = [\n /^localhost$/i,\n /^127\\./,\n /^0\\.0\\.0\\.0$/,\n /^169\\.254\\./, // link-local, including the cloud-metadata address\n /^10\\./,\n /^172\\.(1[6-9]|2\\d|3[01])\\./,\n /^192\\.168\\./,\n /^\\[?::1\\]?$/,\n /^\\[?fc/i,\n /^\\[?fd/i,\n /^\\[?fe80/i,\n];\n\nfunction isAllowedWebhookUrl(url: string): boolean {\n let parsed: URL;\n try {\n parsed = new URL(url);\n } catch {\n return false;\n }\n if (parsed.protocol !== \"https:\" && parsed.protocol !== \"http:\") {\n return false;\n }\n return !BLOCKED_HOSTNAME_PATTERNS.some((pattern) =>\n pattern.test(parsed.hostname),\n );\n}\n\nasync function hmacSha256Hex(payload: string, secret: string): Promise<string> {\n const key = await crypto.subtle.importKey(\n \"raw\",\n new TextEncoder().encode(secret),\n { name: \"HMAC\", hash: \"SHA-256\" },\n false,\n [\"sign\"],\n );\n const signature = await crypto.subtle.sign(\n \"HMAC\",\n key,\n new TextEncoder().encode(payload),\n );\n return Array.from(new Uint8Array(signature), (b) =>\n b.toString(16).padStart(2, \"0\"),\n ).join(\"\");\n}\n\n/**\n * Delivers a single `WebhookMessage` via `fetch()`. Throws\n * `CadmusQueueError` on any non-2xx response or network failure — meant\n * to be called from inside `processBatch`'s handler, where a thrown error\n * becomes a `message.retry()`.\n */\nexport async function deliverWebhookMessage(\n message: WebhookMessage,\n): Promise<void> {\n if (!isAllowedWebhookUrl(message.url)) {\n throw new CadmusQueueError(\n `Webhook URL \"${message.url}\" is not allowed (must be http(s) and not target a private/reserved/loopback address)`,\n );\n }\n\n const body = JSON.stringify({\n event: message.event,\n doc: message.doc,\n timestamp: message.timestamp,\n });\n const headers: Record<string, string> = {\n \"Content-Type\": \"application/json\",\n };\n if (message.secret) {\n headers[\"X-Cadmus-Signature\"] = await hmacSha256Hex(body, message.secret);\n }\n\n let response: Response;\n try {\n response = await fetch(message.url, { method: \"POST\", headers, body });\n } catch (cause) {\n throw new CadmusQueueError(\n `Webhook delivery to \"${message.url}\" failed`,\n cause,\n );\n }\n if (!response.ok) {\n throw new CadmusQueueError(\n `Webhook delivery to \"${message.url}\" returned status ${response.status}`,\n );\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":["toSnakeCase","countRows"],"sources":["../../src/cms/blocks.ts","../../src/cms/types.ts","../../src/cms/codegen.ts","../../src/cms/defineCollection.ts","../../src/cms/patch.ts","../../src/cms/validation.ts","../../src/cms/localApi.ts","../../src/cms/meta.ts","../../src/cms/migrate.ts","../../src/cms/schema-gen.ts","../../src/cms/structure.ts","../../src/cms/visual-editing.ts","../../src/cms/webhooks.ts"],"sourcesContent":["// Copyright (c) 2026 BowenLabs. All rights reserved.\n// Cadmus is MIT licensed. See LICENSE in the repo root.\n\n/**\n * Renderer registry for block/document content (issue #13) — adopts the\n * Portable Text + `@portabletext/react` pattern (idea, not code): a content\n * document is a serializable **array of typed blocks**, and rendering is a\n * lookup of `block.type → renderer`, not a hand-rolled `switch`. Adding a\n * new block type becomes \"register a renderer\" with no edit to a central\n * branch.\n *\n * Framework-agnostic on purpose: `TRenderer` is whatever a host wants a\n * block's renderer to be — a string-producing function (SSR/preview HTML),\n * an Astro component, a Solid component, etc. The registry only does lookup,\n * fallback, and introspection; it never assumes a rendering technology.\n */\n\n/**\n * The minimal shape every block must have: a string discriminant under\n * `type`. (Portable Text uses `_type`; Cadmea content is TipTap-JSON-shaped\n * and already keyed on `type`, so the registry keys on `type` to stay\n * drop-in with stored content — the editor can stay TipTap.)\n */\nexport interface PortableBlockLike {\n type: string;\n}\n\nexport interface BlockRegistry<TRenderer> {\n /** Register (or replace) the renderer for a block type. Chainable. */\n register(type: string, renderer: TRenderer): BlockRegistry<TRenderer>;\n /** Register several at once. Chainable. */\n registerMany(renderers: Record<string, TRenderer>): BlockRegistry<TRenderer>;\n /** The renderer registered for `type`, or `undefined`. */\n get(type: string): TRenderer | undefined;\n /** Whether a renderer is registered for `type`. */\n has(type: string): boolean;\n /** Every registered block type, in registration order. */\n types(): string[];\n /** Set the fallback used when a type has no registered renderer. Chainable. */\n setFallback(renderer: TRenderer): BlockRegistry<TRenderer>;\n /** The renderer for `type`, else the fallback, else `undefined`. */\n resolve(type: string): TRenderer | undefined;\n}\n\n/**\n * Create a block renderer registry. Seed it with an initial `type → renderer`\n * map and/or an `options.fallback` for unknown types.\n *\n * ```ts\n * const registry = createBlockRegistry<StringBlockRenderer>({\n * divider: () => \"<hr>\",\n * });\n * registry.register(\"hero\", (b) => `<h1>${b.heading}</h1>`);\n * renderBlocksToString(blocks, registry);\n * ```\n */\nexport function createBlockRegistry<TRenderer>(\n initial: Record<string, TRenderer> = {},\n options: { fallback?: TRenderer } = {},\n): BlockRegistry<TRenderer> {\n const renderers = new Map<string, TRenderer>(Object.entries(initial));\n let fallback = options.fallback;\n\n const registry: BlockRegistry<TRenderer> = {\n register(type, renderer) {\n renderers.set(type, renderer);\n return registry;\n },\n registerMany(map) {\n for (const [type, renderer] of Object.entries(map)) {\n renderers.set(type, renderer);\n }\n return registry;\n },\n get(type) {\n return renderers.get(type);\n },\n has(type) {\n return renderers.has(type);\n },\n types() {\n return [...renderers.keys()];\n },\n setFallback(renderer) {\n fallback = renderer;\n return registry;\n },\n resolve(type) {\n return renderers.get(type) ?? fallback;\n },\n };\n return registry;\n}\n\n/**\n * A renderer that turns one block into an HTML string — the registry value\n * type for SSR/preview paths that build markup as strings (e.g. a Hono\n * preview route) rather than mounting components.\n */\nexport type StringBlockRenderer<\n TBlock extends PortableBlockLike = PortableBlockLike,\n> = (block: TBlock) => string;\n\n/**\n * Render an array of blocks to a single HTML string via a registry of\n * {@link StringBlockRenderer}s. Blocks whose type resolves to no renderer\n * (and no fallback) contribute the empty string — the same forgiving\n * behavior the old hand-rolled `switch` had for unknown types.\n */\nexport function renderBlocksToString<TBlock extends PortableBlockLike>(\n blocks: readonly TBlock[],\n registry: BlockRegistry<StringBlockRenderer<TBlock>>,\n): string {\n return blocks\n .map((block) => {\n const renderer = registry.resolve(block.type);\n return renderer ? renderer(block) : \"\";\n })\n .join(\"\");\n}\n","// Copyright (c) 2026 BowenLabs. All rights reserved.\n// Cadmus is MIT licensed. See LICENSE in the repo root.\n\nimport type { ValidationBuilder } from \"./validation.js\";\n\n/**\n * Editor-only presentation hints for a single field (issue #16 follow-on) —\n * the field-level counterpart to {@link CollectionAdminConfig}. Purely about\n * how the studio renders the field; absent → sensible defaults (a humanized\n * key for the label, no help text, full width, always shown, editable). None\n * of this touches the DB schema or the Local API.\n */\nexport interface FieldAdminConfig {\n /** Human-friendly label; defaults to a humanized field key (`metaDescription` → \"Meta description\"). */\n label?: string;\n /** Help text rendered beneath the label. */\n description?: string;\n /** Placeholder for text-like inputs. */\n placeholder?: string;\n /** Groups the field into a titled fieldset in the editor, in first-seen order. */\n group?: string;\n /** Editor column width on >= md screens. Defaults to \"full\". */\n width?: \"full\" | \"half\";\n /**\n * Show the field only when this predicate — given the whole in-progress\n * form value — returns true (Payload's `admin.condition`). A function, so\n * it's evaluated by the studio directly from the imported config, not from\n * a serialized meta payload.\n */\n condition?: (values: Record<string, unknown>) => boolean;\n /** Render the field read-only in the editor. */\n readOnly?: boolean;\n}\n\nexport interface BaseFieldConfig {\n /** column name override; defaults to the config key */\n name?: string;\n required?: boolean;\n unique?: boolean;\n defaultValue?: unknown;\n /** Editor-only presentation hints — see {@link FieldAdminConfig}. */\n admin?: FieldAdminConfig;\n /**\n * Chainable validation rules (issue #16), Sanity's `defineField`\n * `validation` analogue: `validation: (rule) => rule.required().min(2)`.\n * Evaluated server-side by createLocalApi on every create/update (and by\n * the studio for client-side feedback). Independent of the `required`/\n * `unique` flags above — those still drive the DB schema; these drive\n * value-level checks with clear, per-field error messages. See\n * {@link ValidationBuilder} and `validation.ts`.\n */\n validation?: ValidationBuilder;\n}\n\nexport interface TextFieldConfig extends BaseFieldConfig {\n type: \"text\";\n defaultValue?: string;\n}\n\nexport interface SelectFieldConfig<TOption extends string = string>\n extends BaseFieldConfig {\n type: \"select\";\n options: readonly TOption[];\n defaultValue?: TOption;\n}\n\nexport interface NumberFieldConfig extends BaseFieldConfig {\n type: \"number\";\n /** marks this field as the table's auto-incrementing primary key */\n autoIncrement?: boolean;\n defaultValue?: number;\n}\n\nexport interface DateFieldConfig extends BaseFieldConfig {\n type: \"date\";\n /** mirrors drizzle's integer(..., { mode: \"timestamp\" | \"timestamp_ms\" }) */\n mode?: \"timestamp\" | \"timestamp_ms\";\n defaultValue?: \"now\" | Date;\n}\n\n// Full field-type matrix from issue #16. All six are implemented in\n// codegen.ts: richText/array → JSON column, upload → text column,\n// relationship → integer column (hasMany:false) or join table\n// (hasMany:true), checkbox → integer column with drizzle's boolean mode.\nexport interface RichTextFieldConfig extends BaseFieldConfig {\n type: \"richText\";\n}\n\n/**\n * The TS type every JSON-mode column (`richText`/`array` fields,\n * `versionData`) is given via drizzle's `.$type<JsonValue>()` — see\n * codegen.ts's and schema-gen.ts's richText/array cases. Without it,\n * drizzle infers a JSON column as `unknown`, which TanStack Start's\n * server-function return-type validator rejects outright (`unknown`\n * doesn't structurally match its `Serializable` check the way a plain\n * object/array/primitive union does). Recursive on purpose — that's what\n * lets the validator recurse through it instead of bottoming out at\n * `unknown`.\n */\nexport type JsonValue =\n | string\n | number\n | boolean\n | null\n | JsonValue[]\n | { [key: string]: JsonValue };\n\nexport interface CheckboxFieldConfig extends BaseFieldConfig {\n type: \"checkbox\";\n defaultValue?: boolean;\n}\n\nexport interface RelationshipFieldConfig extends BaseFieldConfig {\n type: \"relationship\";\n relationTo: string;\n /**\n * `false` (default): a plain integer column on this collection's own\n * table storing the related row's id.\n * `true`: no column on this table — represented by a generated join\n * table instead (see codegen.ts's relationshipJoinTables).\n */\n hasMany?: boolean;\n}\n\n/**\n * `0` (default): a relationship field's column comes back as the bare\n * related-row id. `1`: `createLocalApi`'s `registry` param is used to\n * batch-resolve `hasMany: false` relationship fields into the related\n * row's full document — see localApi.ts's `resolveRelationships`. Depths\n * beyond 1 (resolving a relationship's own relationships) aren't\n * implemented; there's no nested-relationship fixture yet to validate\n * that design against.\n */\nexport type RelationshipDepth = 0 | 1;\n\nexport interface ArrayFieldConfig extends BaseFieldConfig {\n type: \"array\";\n /**\n * Fields shown for every item, regardless of variant — must include\n * `discriminator.key`'s own field (typically a `select`) if set.\n */\n fields: Record<string, FieldConfig>;\n /**\n * Lets one array field model a union of item shapes (e.g. page-builder\n * blocks: image vs hero vs richText vs...) instead of one fixed field\n * set for every item. `key` names a field already present in `fields`\n * (rendered as the item's type switcher); `variants` maps each of that\n * field's possible values to *additional* fields layered on top, shown\n * only for items whose `key` field currently holds that value. Fields\n * not listed under any variant (i.e. everything in `fields`) render\n * unconditionally — that's the place for fields shared across every\n * variant (e.g. a `caption` every block type has).\n *\n * Storage is unaffected either way — `array` is always one JSON column\n * (see codegen.ts); this only changes what `CollectionEdit` renders.\n */\n discriminator?: {\n key: string;\n variants: Record<string, Record<string, FieldConfig>>;\n /**\n * Optional per-variant presentation for the studio's \"Add block\" picker\n * (the visual block builder). `label` defaults to a humanized variant\n * name; `icon` is an opaque CSS class the studio applies to an `<i>`\n * (e.g. a Phosphor `\"ph ph-image\"`), keeping cadmea icon-library-agnostic.\n */\n variantsAdmin?: Record<string, { label?: string; icon?: string }>;\n };\n}\n\nexport interface UploadFieldConfig extends BaseFieldConfig {\n type: \"upload\";\n defaultValue?: string;\n}\n\n/**\n * A freeform JSON-blob column — the `json` field type from Section 3 (issue\n * #20-adjacent field-type gap, see DECISIONS.md). Storage-identical to\n * `richText`/`array` (one `.$type<JsonValue>()` text column, see\n * codegen.ts's `fieldToColumn`) but with no TipTap/array-item connotation —\n * use this for genuinely unstructured data (webhook audit payloads, CRM\n * activity metadata), not page-builder content.\n */\nexport interface JsonFieldConfig extends BaseFieldConfig {\n type: \"json\";\n defaultValue?: JsonValue;\n}\n\n/**\n * A fixed-shape, queryable sub-object — the `group` field type from Section\n * 3. Unlike `array` (JSON-blob storage, variable length), `group` flattens\n * to real prefixed columns at the Drizzle level (`<key>_<subKey>`, see\n * codegen.ts's `collectionToTable` and `flattenFields` below) so SQL-level\n * querying/sorting on a subfield (e.g. `shippingAddress.city`) still works.\n * `required`/`unique`/`defaultValue` on the group itself are meaningless —\n * set them on the individual nested `fields` instead; codegen ignores them\n * at the group level.\n */\nexport interface GroupFieldConfig extends BaseFieldConfig {\n type: \"group\";\n fields: Record<string, FieldConfig>;\n}\n\nexport type FieldConfig =\n | TextFieldConfig\n | SelectFieldConfig\n | NumberFieldConfig\n | DateFieldConfig\n | RichTextFieldConfig\n | CheckboxFieldConfig\n | RelationshipFieldConfig\n | ArrayFieldConfig\n | UploadFieldConfig\n | JsonFieldConfig\n | GroupFieldConfig;\n\n/**\n * Expands every `group` field in `fields` into its flattened equivalents\n * (`<key>_<subKey>`, recursively — a group nested inside a group flattens\n * all the way down), and passes every other field through unchanged. This\n * is the single canonicalization step codegen, schema-gen, and the Local\n * API's field-shape validation (`validateRequiredFields`/\n * `rejectUnknownFields`) all run before touching `group` fields, so none of\n * them need their own group-aware branch — see localApi.ts's `flattenDoc`/\n * `nestDoc` for the matching document-level transform.\n *\n * Known limitation: a flattened key can collide if two different group\n * nestings produce the same combined name (e.g. a group `a_b` containing\n * field `c` collides with group `a` containing field `b_c`) — not guarded\n * against, since no current collection nests groups deeply enough to hit\n * it.\n */\nexport function flattenFields(\n fields: Record<string, FieldConfig>,\n): Record<string, FieldConfig> {\n const result: Record<string, FieldConfig> = {};\n for (const [key, field] of Object.entries(fields)) {\n if (field.type === \"group\") {\n for (const [subKey, subField] of Object.entries(\n flattenFields(field.fields),\n )) {\n result[`${key}_${subKey}`] = subField;\n }\n } else {\n result[key] = field;\n }\n }\n return result;\n}\n\n/**\n * The document-level counterpart to `flattenFields` — turns a `group`\n * field's nested object value (`{ shippingAddress: { city: \"...\" } }`) into\n * its flattened equivalent (`{ shippingAddress_city: \"...\" }`) for writing\n * to the DB, recursively. Fields not present in `doc` are simply omitted\n * from the result (lets `update()`'s partial inputs flatten correctly —\n * an absent group means every one of its flattened keys is absent too, not\n * `undefined`-valued). See `nestDoc` for the inverse, used on read.\n */\nexport function flattenDoc(\n fields: Record<string, FieldConfig>,\n doc: Record<string, unknown>,\n): Record<string, unknown> {\n const result: Record<string, unknown> = {};\n for (const [key, field] of Object.entries(fields)) {\n if (field.type === \"group\") {\n if (!(key in doc)) continue;\n const nested = (doc[key] ?? {}) as Record<string, unknown>;\n for (const [subKey, subValue] of Object.entries(\n flattenDoc(field.fields, nested),\n )) {\n result[`${key}_${subKey}`] = subValue;\n }\n } else if (key in doc) {\n result[key] = doc[key];\n }\n }\n return result;\n}\n\n/**\n * The inverse of `flattenDoc` — re-nests a flat DB row's `<key>_<subKey>`\n * columns back into `{ key: { subKey: ... } }` for everything the Local\n * API returns to a caller, so a `group` field's document shape always\n * matches its config shape regardless of how it's actually stored.\n */\nexport function nestDoc(\n fields: Record<string, FieldConfig>,\n flatRow: Record<string, unknown>,\n): Record<string, unknown> {\n const result: Record<string, unknown> = {};\n for (const [key, field] of Object.entries(fields)) {\n if (field.type === \"group\") {\n const prefix = `${key}_`;\n const nestedFlat: Record<string, unknown> = {};\n for (const [flatKey, value] of Object.entries(flatRow)) {\n if (flatKey.startsWith(prefix)) {\n nestedFlat[flatKey.slice(prefix.length)] = value;\n }\n }\n result[key] = nestDoc(field.fields, nestedFlat);\n } else if (key in flatRow) {\n result[key] = flatRow[key];\n }\n }\n return result;\n}\n\n/**\n * Per-operation access check, modeled on Payload's own `access` shape.\n * @returns whether the operation is allowed. Implementations decide their\n * own context shape (auth/session info isn't standardized by Cadmus) — see\n * {@link LocalApi}'s `TContext` generic, which every operation now requires\n * a value for.\n *\n * Enforced by `createLocalApi` since Section 2 — see\n * {@link CollectionConfig.access}.\n */\n// biome-ignore lint/suspicious/noExplicitAny: the context shape is intentionally caller-defined; Cadmus doesn't standardize auth/session info\nexport type AccessFn<TContext = any> = (\n context: TContext,\n) => boolean | Promise<boolean>;\n\nexport interface CollectionAccess {\n create?: AccessFn;\n read?: AccessFn;\n update?: AccessFn;\n delete?: AccessFn;\n /**\n * Gates `VersionedLocalApi.publish`/`unpublish` (see createVersionedLocalApi\n * in localApi.ts). Separate from `update` — publishing is a distinct\n * privilege from editing a draft, matching Payload's own model.\n */\n publish?: AccessFn;\n}\n\n/**\n * Lifecycle hooks, modeled on Payload's own hook points. Each is an\n * ordered array, run in sequence. Enforced by `createLocalApi` — see\n * {@link CollectionConfig.hooks}.\n */\nexport interface CollectionHooks<TDoc = Record<string, unknown>> {\n beforeChange?: Array<\n (args: { data: Partial<TDoc> }) => Partial<TDoc> | Promise<Partial<TDoc>>\n >;\n /**\n * `operation` distinguishes a freshly-inserted doc from an edited one —\n * `publish()` (versioned collections) counts as `\"update\"`, since it\n * writes to an already-existing row rather than creating one. Lets\n * webhook config (see `cms/webhooks.ts`) filter which events it fires\n * on without the hook itself tracking state.\n */\n afterChange?: Array<\n (args: {\n doc: TDoc;\n operation: \"create\" | \"update\";\n }) => void | Promise<void>\n >;\n beforeRead?: Array<(args: { doc: TDoc }) => TDoc | Promise<TDoc>>;\n afterRead?: Array<(args: { doc: TDoc }) => TDoc | Promise<TDoc>>;\n beforeDelete?: Array<(args: { id: number }) => void | Promise<void>>;\n afterDelete?: Array<(args: { id: number }) => void | Promise<void>>;\n}\n\n/**\n * Studio-presentation hints for a collection, modeled on Sanity's Structure\n * Builder (`sanity/structure`). Purely about how the admin sidebar/editor\n * *presents* a collection — never affects the DB schema, the Local API, or\n * access control. Consumed by {@link buildStudioStructure} (see\n * `structure.ts`); a collection with no `admin` block falls back to sensible\n * defaults (visible, editable, listed, grouped under the default group,\n * label = capitalized slug). Plugin-injected collections can't carry an\n * `admin` block in hand-written config, so `buildStudioStructure` also\n * accepts per-slug overrides at the call site — see its `overrides` option.\n */\nexport interface CollectionAdminConfig {\n /**\n * Sidebar group heading this collection appears under (e.g. \"Content\",\n * \"Store\"). Collections without a group fall into the builder's default\n * group. Decoupling nav grouping from the raw collection list is the whole\n * point of the Structure Builder.\n */\n group?: string;\n /**\n * Sort order within a group — lower sorts first. Ties (and the absence of\n * an explicit order) break by the collection's position in the config\n * array, so config order is the stable default.\n */\n order?: number;\n /**\n * Drop this collection from the sidebar entirely. For pure system/log\n * tables a human never browses (e.g. `webhook_events`).\n */\n hidden?: boolean;\n /**\n * Mark as read-only in the studio — still navigable/viewable, but the UI\n * suppresses create/edit/delete affordances. For machine-written tables a\n * human should inspect but never edit (e.g. `payments`).\n */\n readOnly?: boolean;\n /**\n * Singleton: exactly one document. The sidebar links straight to its\n * editor (`/admin/<slug>`) instead of a list-then-create flow — Sanity's\n * singleton-document structure pattern. (Storage is unchanged; this only\n * changes navigation.)\n */\n singleton?: boolean;\n /** Display label override; defaults to a capitalized slug. */\n label?: string;\n /**\n * Optional icon identifier passed through to the sidebar renderer (e.g. a\n * Phosphor icon name). The builder treats it as an opaque string.\n */\n icon?: string;\n}\n\nexport interface CollectionConfig {\n /** table name in D1; also the Local API's collection slug (later step) */\n slug: string;\n fields: Record<string, FieldConfig>;\n /**\n * Per-operation access control, enforced by `createLocalApi` (Section 2).\n * Reserved per issue #16 step 7 (\"reserve typed config keys now,\n * implementation deferred to Section 2+\") — that deferral is over: every\n * `LocalApi` method now requires a `context` argument and runs the\n * matching access function (`read` for `find`/`findByID`, `create` for\n * `create`, etc.) before touching the database. No access function\n * configured for an operation means that operation is unconditionally\n * allowed, matching the pre-enforcement default.\n */\n access?: CollectionAccess;\n /** Lifecycle hooks, enforced by `createLocalApi`. See {@link CollectionHooks}. */\n hooks?: CollectionHooks;\n /**\n * Opts this collection into draft/version history. When `drafts` is\n * true, codegen (see codegen.ts/schema-gen.ts) generates a companion\n * `${slug}_versions` table and a nullable `published_version_id` pointer\n * column on the main table, and `createVersionedLocalApi` (localApi.ts)\n * becomes usable against it. Collections without this stay exactly as\n * before — no versions table, no extra column, only `createLocalApi`.\n */\n versions?: {\n drafts?: boolean;\n /** Reserved for future pruning of old versions; not enforced yet. */\n maxPerDoc?: number;\n };\n /**\n * Opts this collection into full-text search (issue #29). `fields` names\n * which of this collection's own `text`/`richText`/`upload` fields are\n * indexed — `defineCmsConfig`/`defineCollection` reject any other field\n * type or an unknown key. When set, codegen (see codegen.ts's\n * `collectionSearchTableSQL`) describes a companion `${slug}_fts` SQLite\n * FTS5 virtual table, and `createLocalApi` both becomes able to run\n * `.search()` and keeps that table in sync on every create/update/delete\n * — see localApi.ts's `syncSearchIndex`. `richText` fields are flattened\n * to plain text (TipTap JSON's `text` leaves, concatenated) before being\n * indexed; nested `array`/block content is out of scope for this phase.\n */\n search?: {\n fields: readonly string[];\n };\n /**\n * Studio-presentation hints — grouping, ordering, hidden/read-only,\n * singleton, label, icon. Consumed only by {@link buildStudioStructure}\n * (the Structure Builder); never affects schema, Local API, or access.\n * See {@link CollectionAdminConfig}.\n */\n admin?: CollectionAdminConfig;\n}\n\n/**\n * A Cadmea plugin — a synchronous transform over the whole CMS config,\n * modeled on Payload's `plugins: [(config) => config]` shape. A plugin may\n * add or modify collections, inject fields, or register lifecycle hooks.\n * `defineCmsConfig` runs plugins in array order, each receiving the output\n * of the previous one, *before* validation — so a plugin's output is held\n * to the same rules as a hand-written config.\n *\n * Synchronous in Section 2 by design: the resolved config is consumed by\n * schema codegen and runtime config loading, both of which are sync. An\n * async variant is a deliberate later extension, not an oversight.\n */\nexport type CadmeaPlugin = (config: CmsConfig) => CmsConfig;\n\nexport interface CmsConfig {\n collections: CollectionConfig[];\n /**\n * Config transforms run in order by `defineCmsConfig` before validation.\n * See {@link CadmeaPlugin}. Omit for a plain, plugin-free config.\n */\n plugins?: CadmeaPlugin[];\n}\n","// Copyright (c) 2026 BowenLabs. All rights reserved.\n// Cadmus is MIT licensed. See LICENSE in the repo root.\n\nimport {\n integer,\n real,\n type SQLiteColumnBuilderBase,\n sqliteTable,\n text,\n} from \"drizzle-orm/sqlite-core\";\nimport { CadmusCmsError } from \"../errors.js\";\nimport {\n type CmsConfig,\n type CollectionConfig,\n type FieldConfig,\n flattenFields,\n type JsonValue,\n} from \"./types.js\";\n\nfunction toSnakeCase(value: string): string {\n return value.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`);\n}\n\n// relationship fields with hasMany never reach fieldToColumn — they're\n// filtered out in collectionToTable and represented by a join table\n// instead (see relationshipJoinTables below). hasMany:false relationship\n// fields below store the related row's id as a plain integer, with no\n// SQL .references() FK constraint — building real cross-collection FK\n// wiring would require a whole-schema pass, not a per-field switch; with\n// no real relationship field to validate that design against yet, it's\n// deferred rather than half-built.\nfunction fieldToColumn(\n key: string,\n field: FieldConfig,\n): SQLiteColumnBuilderBase {\n const columnName = field.name ?? toSnakeCase(key);\n\n switch (field.type) {\n case \"text\":\n case \"upload\": {\n let column = text(columnName);\n if (field.required) column = column.notNull();\n if (field.unique) column = column.unique();\n if (field.defaultValue !== undefined) {\n column = column.default(field.defaultValue);\n }\n return column;\n }\n case \"richText\":\n case \"array\":\n case \"json\": {\n // TipTap JSON / array-group content / freeform json blobs — all a\n // single JSON column. The nested `fields` on an array config\n // describe the JSON shape for introspection only; not enforced at\n // write time in this step. `group` fields never reach here — they're\n // expanded into their flattened equivalents by `flattenFields`\n // before `collectionToTable` builds columns.\n // `.$type<JsonValue>()` overrides drizzle's inferred `unknown` —\n // see types.ts's JsonValue doc comment for why that matters.\n let column = text(columnName, { mode: \"json\" }).$type<JsonValue>();\n if (field.required) column = column.notNull();\n if (field.defaultValue !== undefined) {\n // BaseFieldConfig types `defaultValue` as `unknown` (it's shared\n // across every field type); a richText/array default is always\n // JSON-shaped data by construction, so this is a narrowing cast,\n // not an escape hatch.\n column = column.default(field.defaultValue as JsonValue);\n }\n return column;\n }\n case \"relationship\": {\n let column = integer(columnName);\n if (field.required) column = column.notNull();\n return column;\n }\n case \"select\": {\n let column = text(columnName, {\n enum: field.options as [string, ...string[]],\n });\n if (field.required) column = column.notNull();\n if (field.defaultValue !== undefined) {\n column = column.default(field.defaultValue);\n }\n return column;\n }\n case \"number\": {\n // autoIncrement marks the table's PK — SQLite's \"INTEGER PRIMARY\n // KEY AUTOINCREMENT\" rowid-alias behavior requires the literal\n // column type INTEGER, so this case never uses `real`, regardless\n // of the general number-field mapping below.\n if (field.autoIncrement) {\n return integer(columnName).primaryKey({ autoIncrement: true });\n }\n let column = real(columnName);\n if (field.required) column = column.notNull();\n if (field.defaultValue !== undefined) {\n column = column.default(field.defaultValue);\n }\n return column;\n }\n case \"date\": {\n let column =\n field.mode === \"timestamp_ms\"\n ? integer(columnName, { mode: \"timestamp_ms\" })\n : integer(columnName, { mode: \"timestamp\" });\n if (field.required) column = column.notNull();\n if (field.defaultValue === \"now\") {\n column = column.$defaultFn(() => new Date());\n } else if (field.defaultValue instanceof Date) {\n const defaultDate = field.defaultValue;\n column = column.$defaultFn(() => defaultDate);\n }\n return column;\n }\n case \"checkbox\": {\n // Same SQLite-integer-as-boolean mapping as the hand-written\n // boolean columns in app/core/db/schema.ts (darkMode, etc.) — kept\n // consistent rather than inventing a second boolean convention.\n let column = integer(columnName, { mode: \"boolean\" });\n if (field.required) column = column.notNull();\n if (field.defaultValue !== undefined) {\n column = column.default(field.defaultValue);\n }\n return column;\n }\n default:\n throw new CadmusCmsError(\n `Field type \"${(field as FieldConfig).type}\" is not yet supported by cadmus/cms codegen`,\n );\n }\n}\n\nexport function collectionToTable(config: CollectionConfig) {\n const columns: Record<string, SQLiteColumnBuilderBase> = {};\n // `group` fields are expanded into `<key>_<subKey>` flattened columns\n // here, before fieldToColumn ever sees them — see types.ts's\n // `flattenFields` doc comment for why this is the one canonicalization\n // step every consumer (codegen, schema-gen, Local API validation) shares.\n for (const [key, field] of Object.entries(flattenFields(config.fields))) {\n // hasMany relationships have no column on this table — they're\n // represented by a join table (see relationshipJoinTables).\n if (field.type === \"relationship\" && field.hasMany) continue;\n columns[key] = fieldToColumn(key, field);\n }\n // Bookkeeping column, not a content field — absent from config.fields so\n // admin-UI introspection (meta.ts) never sees it. Null until the first\n // publish; createVersionedLocalApi.publish() sets it, .unpublish() clears\n // it. See collectionVersionsTable below for the table it points into.\n if (config.versions?.drafts) {\n columns.publishedVersionId = integer(\"published_version_id\");\n }\n return sqliteTable(config.slug, columns);\n}\n\n// One row per saved version (draft or published) of a document, keyed by\n// `parentId` (the main table's row id — no SQL FK constraint, same\n// deferred-FK precedent as relationship fields above). `versionData` is\n// the full document snapshot as JSON, independent of the main table's\n// columns — so a draft can hold an incomplete/invalid-for-publish shape\n// without touching the main row at all.\nexport function collectionVersionsTable(config: CollectionConfig) {\n return sqliteTable(`${config.slug}_versions`, {\n id: integer(\"id\").primaryKey({ autoIncrement: true }),\n parentId: integer(\"parent_id\").notNull(),\n versionData: text(\"version_data\", { mode: \"json\" })\n .$type<JsonValue>()\n .notNull(),\n status: text(\"status\", { enum: [\"draft\", \"published\"] }).notNull(),\n createdAt: integer(\"created_at\", { mode: \"timestamp\" }).$defaultFn(\n () => new Date(),\n ),\n });\n}\n\n// For each hasMany relationship field in a collection, builds a join\n// table named `${collectionSlug}_${fieldKey}` with two plain integer\n// columns. No composite primary key in this step — see codegen.ts's\n// module comment on deferred FK enforcement. Known limitation: a\n// self-referential relationship (relationTo === the collection's own\n// slug) would collide both column names into one — not handled, since\n// no collection needs a self-relation yet.\nexport function relationshipJoinTables(\n config: CollectionConfig,\n): Record<string, ReturnType<typeof sqliteTable>> {\n const joinTables: Record<string, ReturnType<typeof sqliteTable>> = {};\n for (const [key, field] of Object.entries(config.fields)) {\n if (field.type !== \"relationship\" || !field.hasMany) continue;\n const tableName = `${config.slug}_${key}`;\n const ownColumn = `${config.slug}_id`;\n const relatedColumn = `${field.relationTo}_id`;\n const columns: Record<string, SQLiteColumnBuilderBase> = {};\n columns[ownColumn] = integer(ownColumn).notNull();\n columns[relatedColumn] = integer(relatedColumn).notNull();\n joinTables[tableName] = sqliteTable(tableName, columns);\n }\n return joinTables;\n}\n\n// FTS5 virtual tables aren't representable as a drizzle-orm sqliteTable —\n// drizzle has no virtual-table column builder, so unlike collectionToTable/\n// collectionVersionsTable above this emits raw SQL text rather than a\n// runtime table object. The migration itself is hand-authored (drizzle-kit\n// can't diff a TS schema it was never given), this function just keeps that\n// migration's SQL in one place, generated from the same config that drives\n// the rest of codegen — see app/core/db/migrations/0006_pages_search_fts.sql.\nexport function collectionSearchTableName(config: CollectionConfig): string {\n return `${config.slug}_fts`;\n}\n\nexport function collectionSearchTableSQL(config: CollectionConfig): string {\n const fields = config.search?.fields ?? [];\n if (fields.length === 0) return \"\";\n const columns = fields.map((key) => `\"${key}\"`).join(\", \");\n return `CREATE VIRTUAL TABLE IF NOT EXISTS \"${collectionSearchTableName(config)}\" USING fts5(${columns});`;\n}\n\n// Flattens a richText field's TipTap JSON into plain text for FTS5\n// indexing — walks every node's `text` leaves (TipTap's own shape for a\n// run of plain text) and joins them with spaces, ignoring marks/attrs\n// entirely since FTS5 only ever sees plain text. Anything that isn't\n// TipTap JSON (a bare string, a non-object) is coerced to its own string\n// form rather than throwing — search indexing is best-effort, not a\n// validation pass.\nfunction flattenRichText(value: unknown): string {\n if (value === null || value === undefined) return \"\";\n if (typeof value === \"string\") return value;\n if (typeof value !== \"object\") return String(value);\n if (Array.isArray(value)) {\n return value.map(flattenRichText).filter(Boolean).join(\" \");\n }\n const node = value as Record<string, unknown>;\n const parts: string[] = [];\n if (typeof node.text === \"string\") parts.push(node.text);\n if (Array.isArray(node.content)) parts.push(flattenRichText(node.content));\n return parts.filter(Boolean).join(\" \");\n}\n\n// Builds the row inserted into a collection's FTS5 table from a freshly\n// written document — one column per `search.fields` entry, in that order,\n// matching collectionSearchTableSQL's column list. `text`/`upload` fields\n// are indexed as-is; `richText` fields go through flattenRichText first.\nexport function extractSearchText(\n config: CollectionConfig,\n doc: Record<string, unknown>,\n): string[] {\n const fields = config.search?.fields ?? [];\n return fields.map((key) => {\n const field = config.fields[key];\n const raw = doc[key];\n if (field?.type === \"richText\") return flattenRichText(raw);\n return typeof raw === \"string\" ? raw : \"\";\n });\n}\n\nexport function cmsConfigToSchema(\n config: CmsConfig,\n): Record<\n string,\n | ReturnType<typeof collectionToTable>\n | ReturnType<typeof collectionVersionsTable>\n> {\n const schema: Record<\n string,\n | ReturnType<typeof collectionToTable>\n | ReturnType<typeof collectionVersionsTable>\n > = {};\n for (const collection of config.collections) {\n schema[collection.slug] = collectionToTable(collection);\n Object.assign(schema, relationshipJoinTables(collection));\n if (collection.versions?.drafts) {\n schema[`${collection.slug}_versions`] =\n collectionVersionsTable(collection);\n }\n }\n return schema;\n}\n","// Copyright (c) 2026 BowenLabs. All rights reserved.\n// Cadmus is MIT licensed. See LICENSE in the repo root.\n\nimport { CadmusCmsError } from \"../errors.js\";\nimport type { CmsConfig, CollectionConfig, FieldConfig } from \"./types.js\";\n\nconst KNOWN_FIELD_TYPES: ReadonlySet<FieldConfig[\"type\"]> = new Set([\n \"text\",\n \"select\",\n \"number\",\n \"date\",\n \"richText\",\n \"checkbox\",\n \"relationship\",\n \"array\",\n \"upload\",\n \"json\",\n \"group\",\n]);\n\n// Validates one field's own shape (type known, relationship/array/group\n// invariants) without descending into a collection's broader rules\n// (search.fields etc, which only make sense at the top level). Recurses\n// into `group`'s nested fields so a group can't smuggle in an unrecognized\n// or malformed nested field — same checks, one level down.\nfunction validateField(slug: string, key: string, field: FieldConfig): void {\n if (!KNOWN_FIELD_TYPES.has(field.type)) {\n throw new CadmusCmsError(\n `Collection \"${slug}\" field \"${key}\" has unrecognized type \"${field.type}\"`,\n );\n }\n\n if (field.type === \"relationship\" && !field.relationTo) {\n throw new CadmusCmsError(\n `Collection \"${slug}\" field \"${key}\" is a relationship field and requires \"relationTo\"`,\n );\n }\n\n if (field.type === \"array\" && Object.keys(field.fields ?? {}).length === 0) {\n throw new CadmusCmsError(\n `Collection \"${slug}\" field \"${key}\" is an array field and must define at least one nested field`,\n );\n }\n\n if (field.type === \"group\") {\n const nestedEntries = Object.entries(field.fields ?? {});\n if (nestedEntries.length === 0) {\n throw new CadmusCmsError(\n `Collection \"${slug}\" field \"${key}\" is a group field and must define at least one nested field`,\n );\n }\n for (const [nestedKey, nestedField] of nestedEntries) {\n validateField(slug, `${key}.${nestedKey}`, nestedField);\n }\n }\n}\n\nfunction validateCollectionConfig(config: CollectionConfig): void {\n if (!config.slug || config.slug.trim().length === 0) {\n throw new CadmusCmsError(\"Collection config requires a non-empty slug\");\n }\n\n const fieldEntries = Object.entries(config.fields ?? {});\n if (fieldEntries.length === 0) {\n throw new CadmusCmsError(\n `Collection \"${config.slug}\" must define at least one field`,\n );\n }\n\n for (const [key, field] of fieldEntries) {\n validateField(config.slug, key, field);\n }\n\n const SEARCHABLE_FIELD_TYPES: ReadonlySet<FieldConfig[\"type\"]> = new Set([\n \"text\",\n \"richText\",\n \"upload\",\n ]);\n for (const key of config.search?.fields ?? []) {\n const field = config.fields[key];\n if (!field) {\n throw new CadmusCmsError(\n `Collection \"${config.slug}\" search.fields references unknown field \"${key}\"`,\n );\n }\n if (!SEARCHABLE_FIELD_TYPES.has(field.type)) {\n throw new CadmusCmsError(\n `Collection \"${config.slug}\" search.fields field \"${key}\" has type \"${field.type}\" — only \"text\", \"richText\", and \"upload\" fields can be indexed`,\n );\n }\n }\n}\n\nfunction validateUniqueSlugs(collections: readonly CollectionConfig[]): void {\n const seen = new Set<string>();\n for (const collection of collections) {\n if (seen.has(collection.slug)) {\n throw new CadmusCmsError(\n `Duplicate collection slug \"${collection.slug}\" — collection slugs must be unique`,\n );\n }\n seen.add(collection.slug);\n }\n}\n\nexport function defineCollection(config: CollectionConfig): CollectionConfig {\n validateCollectionConfig(config);\n return config;\n}\n\nexport function defineCmsConfig(config: CmsConfig): CmsConfig {\n // Run plugins in array order, each fed the previous one's output, before\n // any validation — a plugin's emitted config is held to exactly the same\n // rules as a hand-written one. The resolved config (not the raw input) is\n // what every downstream consumer reads: schema codegen, admin meta, and\n // the Local API. Plugins must not mutate their input; treat `config` as\n // immutable and return a new object (the SEO plugin does this).\n let resolved: CmsConfig = config;\n for (const plugin of config.plugins ?? []) {\n resolved = plugin(resolved);\n }\n\n for (const collection of resolved.collections) {\n validateCollectionConfig(collection);\n }\n validateUniqueSlugs(resolved.collections);\n return resolved;\n}\n","// Copyright (c) 2026 BowenLabs. All rights reserved.\n// Cadmus is MIT licensed. See LICENSE in the repo root.\n\nimport type { JsonValue } from \"./types.js\";\n\n/**\n * Patch model + field-level diff (issue #14) — adopts Sanity's mutation/patch\n * idea (pattern, not code): represent a content change as a small set of\n * field operations, and compute a field-level diff between two document\n * snapshots. Underpins version-history display (what changed between two\n * versions) and the content-migration runner (#18), which expresses a\n * transform's effect as a {@link Patch} and applies it.\n *\n * Scope: operations are keyed by **top-level field** (a document's own\n * fields), matching \"field-level diff\" — a changed `blocks` array reads as\n * one changed field, not a deep per-node diff. Deep/array-aware diffing is a\n * deliberate later extension, not built here.\n */\n\n/** A single field operation. `set` writes a value; `unset` removes the field. */\nexport type PatchOp =\n | { op: \"set\"; path: string; value: JsonValue }\n | { op: \"unset\"; path: string };\n\n/** An ordered set of field operations transforming one document into another. */\nexport type Patch = PatchOp[];\n\nexport type FieldChangeKind = \"added\" | \"removed\" | \"changed\";\n\n/** One field's difference between two document snapshots. */\nexport interface FieldChange {\n /** Top-level field key. */\n path: string;\n kind: FieldChangeKind;\n /** Value in the \"before\" snapshot (absent for `added`). */\n before?: JsonValue;\n /** Value in the \"after\" snapshot (absent for `removed`). */\n after?: JsonValue;\n}\n\ntype Doc = Record<string, JsonValue>;\n\n// Structural deep-equality over JSON values — order-sensitive for arrays\n// (a reordered blocks array is a real change), key-order-insensitive for\n// objects.\nfunction deepEqual(a: JsonValue, b: JsonValue): boolean {\n if (a === b) return true;\n if (a === null || b === null) return false;\n if (typeof a !== typeof b) return false;\n if (Array.isArray(a) || Array.isArray(b)) {\n if (!Array.isArray(a) || !Array.isArray(b)) return false;\n if (a.length !== b.length) return false;\n return a.every((item, i) => deepEqual(item, b[i] as JsonValue));\n }\n if (typeof a === \"object\" && typeof b === \"object\") {\n const ak = Object.keys(a);\n const bk = Object.keys(b);\n if (ak.length !== bk.length) return false;\n return ak.every(\n (key) =>\n Object.hasOwn(b, key) && deepEqual((a as Doc)[key], (b as Doc)[key]),\n );\n }\n return false;\n}\n\nexport interface DiffOptions {\n /**\n * Restrict the diff to these field keys. Omit to diff the union of both\n * documents' own keys. Useful for ignoring bookkeeping columns\n * (`id`/`createdAt`/`publishedVersionId`) in a version-history view.\n */\n fields?: readonly string[];\n /** Field keys to skip (e.g. `[\"id\", \"createdAt\"]`). */\n ignore?: readonly string[];\n}\n\n/**\n * Field-level diff between two document snapshots — the per-field\n * added/removed/changed list a version-history UI renders. Values are\n * compared structurally (deep-equal), so a field only shows as `changed`\n * when its content actually differs.\n */\nexport function diffDocuments(\n before: Doc,\n after: Doc,\n options: DiffOptions = {},\n): FieldChange[] {\n const ignore = new Set(options.ignore ?? []);\n const keys = options.fields\n ? options.fields\n : [...new Set([...Object.keys(before), ...Object.keys(after)])];\n\n const changes: FieldChange[] = [];\n for (const path of keys) {\n if (ignore.has(path)) continue;\n const inBefore = Object.hasOwn(before, path);\n const inAfter = Object.hasOwn(after, path);\n if (inBefore && !inAfter) {\n changes.push({ path, kind: \"removed\", before: before[path] });\n } else if (!inBefore && inAfter) {\n changes.push({ path, kind: \"added\", after: after[path] });\n } else if (inBefore && inAfter && !deepEqual(before[path], after[path])) {\n changes.push({\n path,\n kind: \"changed\",\n before: before[path],\n after: after[path],\n });\n }\n }\n return changes;\n}\n\n/**\n * The {@link Patch} that transforms `before` into `after`: `set` for each\n * added/changed field, `unset` for each removed field. `applyPatch(before,\n * computePatch(before, after))` deep-equals `after`.\n */\nexport function computePatch(before: Doc, after: Doc): Patch {\n return diffDocuments(before, after).map((change) =>\n change.kind === \"removed\"\n ? { op: \"unset\", path: change.path }\n : { op: \"set\", path: change.path, value: change.after as JsonValue },\n );\n}\n\n/**\n * Apply a {@link Patch} to a document, returning a new document (the input is\n * never mutated). Unknown ops are ignored defensively.\n */\nexport function applyPatch(doc: Doc, patch: Patch): Doc {\n const next: Doc = { ...doc };\n for (const op of patch) {\n if (op.op === \"set\") {\n next[op.path] = op.value;\n } else if (op.op === \"unset\") {\n delete next[op.path];\n }\n }\n return next;\n}\n","// Copyright (c) 2026 BowenLabs. All rights reserved.\n// Cadmus is MIT licensed. See LICENSE in the repo root.\n\nimport { and, eq, ne } from \"drizzle-orm\";\nimport type {\n BaseSQLiteDatabase,\n SQLiteTableWithColumns,\n} from \"drizzle-orm/sqlite-core\";\nimport { CadmusValidationError, type ValidationViolation } from \"../errors.js\";\nimport type { CmsRegistry } from \"./localApi.js\";\nimport type { CollectionConfig, FieldConfig } from \"./types.js\";\nimport { flattenDoc, flattenFields } from \"./types.js\";\n\n// Mirrors localApi.ts's own local alias — drizzle's default table generic.\n// biome-ignore lint/suspicious/noExplicitAny: matches drizzle-orm's own SQLiteTableWithColumns default generic usage\ntype AnyTable = SQLiteTableWithColumns<any>;\n\n/**\n * Chainable field validation for Cadmea (issue #16) — adopts Sanity's\n * `defineField`/`Rule` validation API (pattern, not code). A field declares\n * `validation: (rule) => rule.required().min(2).custom(...)`; this module\n * turns that chain into a list of declarative checks and evaluates them at\n * write time (server-side, in createLocalApi) as well as anywhere the studio\n * wants synchronous feedback.\n *\n * Design notes:\n * - The builder is **immutable** — every method returns a new {@link Rule}\n * with one more check appended, so a shared base rule can't be mutated by\n * a consumer's chain (mirrors Sanity).\n * - Most checks are synchronous and pure (min/max/regex/custom over the\n * value alone). Two — `unique` and `reference` — need the database and so\n * only run where {@link validateDocument} is given a `db` (i.e. the Local\n * API); they're skipped (not failed) in a pure client-side pass.\n */\n\nexport type ValidationSeverity = \"error\" | \"warning\";\n\n/**\n * What a {@link CustomValidator} may return:\n * - `true` / `undefined` → valid\n * - `false` → invalid, generic message\n * - `string` → invalid, that message\n * - `{ message, severity? }` → invalid, that message at the given severity\n */\nexport type CustomValidatorResult =\n | boolean\n | undefined\n | string\n | { message: string; severity?: ValidationSeverity };\n\nexport interface ValidationFieldContext {\n /** The whole document being validated (nested shape, post-hooks). */\n document: Record<string, unknown>;\n /** This field's flattened key (e.g. `slug`, `shippingAddress_city`). */\n path: string;\n /** Whether this is a create or an update. */\n operation: \"create\" | \"update\";\n /** The document's id on update — lets `unique` exclude the row itself. */\n id?: number;\n}\n\nexport type CustomValidator = (\n value: unknown,\n context: ValidationFieldContext,\n) => CustomValidatorResult | Promise<CustomValidatorResult>;\n\n// Internal check descriptors. `message`/`severity` are per-check overrides\n// applied by `.error()`/`.warning()` to the most recently added check.\ntype Check = (\n | { kind: \"required\" }\n | { kind: \"min\"; n: number }\n | { kind: \"max\"; n: number }\n | { kind: \"length\"; n: number }\n | { kind: \"regex\"; re: RegExp; label: string }\n | { kind: \"integer\" }\n | { kind: \"positive\" }\n | { kind: \"unique\" }\n | { kind: \"reference\" }\n | { kind: \"custom\"; fn: CustomValidator }\n) & { message?: string; severity?: ValidationSeverity };\n\n// Pre-baked formats so consumers don't hand-roll the same regexes.\nconst EMAIL_RE = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/;\n// Lowercase kebab slug: letters/digits separated by single hyphens.\nconst SLUG_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;\n\n/**\n * Immutable, chainable rule builder — the value a field's `validation`\n * function receives and returns. Build a `Rule` with the module-level\n * {@link rule} factory, or accept the one passed to your `validation`\n * callback.\n */\nexport class Rule {\n // Frozen on construction; every builder method returns a fresh Rule.\n private readonly checks: readonly Check[];\n\n constructor(checks: readonly Check[] = []) {\n this.checks = checks;\n }\n\n private add(check: Check): Rule {\n return new Rule([...this.checks, check]);\n }\n\n /** Override the message of the most recently added check. */\n error(message: string): Rule {\n return this.withLast({ message, severity: \"error\" });\n }\n\n /**\n * Demote the most recently added check to a warning (non-blocking),\n * optionally with a message. Sanity's `Rule.warning()` analogue.\n */\n warning(message?: string): Rule {\n return this.withLast({\n severity: \"warning\",\n ...(message ? { message } : {}),\n });\n }\n\n private withLast(patch: Partial<Check>): Rule {\n if (this.checks.length === 0) return this;\n const next = this.checks.slice();\n next[next.length - 1] = { ...next[next.length - 1], ...patch } as Check;\n return new Rule(next);\n }\n\n required(): Rule {\n return this.add({ kind: \"required\" });\n }\n\n /** Minimum string length / array length / numeric value. */\n min(n: number): Rule {\n return this.add({ kind: \"min\", n });\n }\n\n /** Maximum string length / array length / numeric value. */\n max(n: number): Rule {\n return this.add({ kind: \"max\", n });\n }\n\n /** Exact string/array length. */\n length(n: number): Rule {\n return this.add({ kind: \"length\", n });\n }\n\n regex(re: RegExp, label = \"match the required format\"): Rule {\n return this.add({ kind: \"regex\", re, label });\n }\n\n email(): Rule {\n return this.add({ kind: \"regex\", re: EMAIL_RE, label: \"be a valid email\" });\n }\n\n /** Lowercase kebab-case slug format. Pair with `.unique()` for slugs. */\n slug(): Rule {\n return this.add({\n kind: \"regex\",\n re: SLUG_RE,\n label: \"be a lowercase, hyphen-separated slug\",\n });\n }\n\n integer(): Rule {\n return this.add({ kind: \"integer\" });\n }\n\n positive(): Rule {\n return this.add({ kind: \"positive\" });\n }\n\n /**\n * Value must be unique across the collection (DB-backed; skipped in a\n * pure client-side pass). A first-class rule rather than the hand-rolled\n * column `unique` flag, so the failure is a clear field message instead of\n * a raw UNIQUE-constraint write error.\n */\n unique(): Rule {\n return this.add({ kind: \"unique\" });\n }\n\n /**\n * For a `relationship` field: the referenced id must exist in the related\n * collection (DB-backed; skipped client-side).\n */\n reference(): Rule {\n return this.add({ kind: \"reference\" });\n }\n\n custom(fn: CustomValidator): Rule {\n return this.add({ kind: \"custom\", fn });\n }\n\n /** Internal: the accumulated checks, read by {@link validateDocument}. */\n toChecks(): readonly Check[] {\n return this.checks;\n }\n}\n\n/** Fresh, empty rule — the root of a chain. */\nexport function rule(): Rule {\n return new Rule();\n}\n\n/**\n * A field's `validation` value: a function from a fresh Rule to the\n * configured chain (Sanity's signature). Returning an array lets a field\n * carry several independent rule chains.\n */\nexport type ValidationBuilder = (r: Rule) => Rule | Rule[];\n\n/**\n * Identity helper mirroring Sanity's `defineField` — returns the field\n * config unchanged but gives editors autocomplete and a single, greppable\n * call site for field definitions. Optional: a plain object literal is still\n * a valid field.\n */\nexport function defineField<T extends FieldConfig>(field: T): T {\n return field;\n}\n\nfunction resolveChecks(field: FieldConfig): readonly Check[] {\n if (!field.validation) return [];\n const built = field.validation(new Rule());\n const rules = Array.isArray(built) ? built : [built];\n return rules.flatMap((r) => r.toChecks());\n}\n\nfunction isEmpty(value: unknown): boolean {\n return (\n value === undefined ||\n value === null ||\n (typeof value === \"string\" && value.length === 0)\n );\n}\n\nfunction sizeOf(value: unknown): { size: number; unit: string } | null {\n if (typeof value === \"string\")\n return { size: value.length, unit: \"character\" };\n if (Array.isArray(value)) return { size: value.length, unit: \"item\" };\n if (typeof value === \"number\") return { size: value, unit: \"\" };\n return null;\n}\n\nexport interface ValidateDocumentOptions {\n operation: \"create\" | \"update\";\n /** Document id (update only) — passed to `unique`/custom validators. */\n id?: number;\n /**\n * Restrict validation to these flattened field keys. Used by update(),\n * which only receives a partial document — validating absent fields would\n * spuriously fail their rules. Omit to validate every field (create).\n */\n onlyFields?: ReadonlySet<string>;\n /**\n * Database handle for DB-backed rules (`unique`, `reference`). When\n * omitted, those rules are skipped — so the same function powers a pure\n * client-side validation pass.\n */\n db?: BaseSQLiteDatabase<\"async\", unknown>;\n /** This collection's own table (for `unique`). */\n table?: AnyTable;\n /** Registry of tables by slug (for `reference` target lookups). */\n registry?: CmsRegistry;\n}\n\n/**\n * Evaluate every field's validation rules against `doc`, returning all\n * violations (both errors and warnings). `doc` is the nested document; field\n * values are read from its flattened form so group subfields validate too.\n */\nexport async function validateDocument(\n config: CollectionConfig,\n doc: Record<string, unknown>,\n options: ValidateDocumentOptions,\n): Promise<ValidationViolation[]> {\n const flatFields = flattenFields(config.fields);\n const flatDoc = flattenDocShallow(config, doc);\n const violations: ValidationViolation[] = [];\n\n for (const [path, field] of Object.entries(flatFields)) {\n if (options.onlyFields && !options.onlyFields.has(path)) continue;\n const checks = resolveChecks(field);\n if (checks.length === 0) continue;\n\n const value = flatDoc[path];\n const ctx: ValidationFieldContext = {\n document: doc,\n path,\n operation: options.operation,\n ...(options.id !== undefined ? { id: options.id } : {}),\n };\n\n for (const check of checks) {\n const violation = await evaluateCheck(check, value, field, ctx, options);\n if (violation) violations.push(violation);\n }\n }\n\n return violations;\n}\n\n// Reuse the Local API's own flattening so a group subfield's value is read\n// from the same `<key>_<subKey>` shape it's stored under. Skip the round\n// trip when the collection has no group fields (the common case).\nfunction flattenDocShallow(\n config: CollectionConfig,\n doc: Record<string, unknown>,\n): Record<string, unknown> {\n const hasGroup = Object.values(config.fields).some((f) => f.type === \"group\");\n return hasGroup ? flattenDoc(config.fields, doc) : doc;\n}\n\nasync function evaluateCheck(\n check: Check,\n value: unknown,\n field: FieldConfig,\n ctx: ValidationFieldContext,\n options: ValidateDocumentOptions,\n): Promise<ValidationViolation | null> {\n const fail = (defaultMessage: string): ValidationViolation => ({\n path: ctx.path,\n message: check.message ?? `${ctx.path} must ${defaultMessage}`,\n severity: check.severity ?? \"error\",\n });\n\n switch (check.kind) {\n case \"required\":\n return isEmpty(value) ? fail(\"not be empty\") : null;\n\n case \"min\": {\n if (isEmpty(value)) return null;\n const s = sizeOf(value);\n if (s && s.size < check.n) {\n return fail(\n s.unit\n ? `have at least ${check.n} ${s.unit}${check.n === 1 ? \"\" : \"s\"}`\n : `be at least ${check.n}`,\n );\n }\n return null;\n }\n\n case \"max\": {\n if (isEmpty(value)) return null;\n const s = sizeOf(value);\n if (s && s.size > check.n) {\n return fail(\n s.unit\n ? `have at most ${check.n} ${s.unit}${check.n === 1 ? \"\" : \"s\"}`\n : `be at most ${check.n}`,\n );\n }\n return null;\n }\n\n case \"length\": {\n if (isEmpty(value)) return null;\n const s = sizeOf(value);\n if (s?.unit && s.size !== check.n) {\n return fail(\n `be exactly ${check.n} ${s.unit}${check.n === 1 ? \"\" : \"s\"}`,\n );\n }\n return null;\n }\n\n case \"regex\": {\n if (isEmpty(value)) return null;\n if (typeof value !== \"string\" || !check.re.test(value)) {\n return fail(check.label);\n }\n return null;\n }\n\n case \"integer\":\n if (isEmpty(value)) return null;\n return typeof value === \"number\" && Number.isInteger(value)\n ? null\n : fail(\"be an integer\");\n\n case \"positive\":\n if (isEmpty(value)) return null;\n return typeof value === \"number\" && value > 0\n ? null\n : fail(\"be a positive number\");\n\n case \"unique\":\n return evaluateUnique(value, ctx, options, check);\n\n case \"reference\":\n return evaluateReference(value, field, ctx, options, check);\n\n case \"custom\": {\n const result = await check.fn(value, ctx);\n if (result === true || result === undefined) return null;\n if (result === false) return fail(\"be valid\");\n if (typeof result === \"string\") {\n return {\n path: ctx.path,\n message: result,\n severity: check.severity ?? \"error\",\n };\n }\n return {\n path: ctx.path,\n message: result.message,\n severity: result.severity ?? check.severity ?? \"error\",\n };\n }\n }\n}\n\nasync function evaluateUnique(\n value: unknown,\n ctx: ValidationFieldContext,\n options: ValidateDocumentOptions,\n check: Check,\n): Promise<ValidationViolation | null> {\n if (isEmpty(value) || !options.db || !options.table) return null;\n const column = (options.table as Record<string, unknown>)[ctx.path];\n if (!column) return null;\n // On update, exclude the row itself so re-saving an unchanged value passes.\n const where =\n ctx.id !== undefined\n ? and(eq(column as never, value as never), ne(options.table.id, ctx.id))\n : eq(column as never, value as never);\n const existing = await options.db\n .select({ id: options.table.id })\n .from(options.table as never)\n .where(where)\n .limit(1);\n if (existing.length > 0) {\n return {\n path: ctx.path,\n message:\n check.message ?? `${ctx.path} \"${String(value)}\" is already taken`,\n severity: check.severity ?? \"error\",\n };\n }\n return null;\n}\n\nasync function evaluateReference(\n value: unknown,\n field: FieldConfig,\n ctx: ValidationFieldContext,\n options: ValidateDocumentOptions,\n check: Check,\n): Promise<ValidationViolation | null> {\n if (isEmpty(value) || !options.db || !options.registry) return null;\n if (field.type !== \"relationship\") return null;\n const target = options.registry.tables[field.relationTo];\n if (!target) return null;\n const found = await options.db\n .select({ id: target.id })\n .from(target as never)\n .where(eq(target.id, value as never))\n .limit(1);\n if (found.length === 0) {\n return {\n path: ctx.path,\n message:\n check.message ??\n `${ctx.path} references a \"${field.relationTo}\" that does not exist`,\n severity: check.severity ?? \"error\",\n };\n }\n return null;\n}\n\n/**\n * Run {@link validateDocument} and throw {@link CadmusValidationError} if any\n * `\"error\"`-severity violations are found. Warnings are returned (never\n * thrown) so a caller can still surface them. The thrown error's message is\n * a readable, joined summary of every blocking violation.\n */\nexport async function assertValid(\n config: CollectionConfig,\n doc: Record<string, unknown>,\n options: ValidateDocumentOptions,\n): Promise<ValidationViolation[]> {\n const violations = await validateDocument(config, doc, options);\n const errors = violations.filter((v) => v.severity === \"error\");\n if (errors.length > 0) {\n const summary = errors.map((v) => v.message).join(\"; \");\n throw new CadmusValidationError(\n `Validation failed for collection \"${config.slug}\": ${summary}`,\n violations,\n );\n }\n return violations;\n}\n","// Copyright (c) 2026 BowenLabs. All rights reserved.\n// Cadmus is MIT licensed. See LICENSE in the repo root.\n\nimport {\n count as countRows,\n desc,\n eq,\n type InferInsertModel,\n type InferSelectModel,\n inArray,\n type SQL,\n sql,\n} from \"drizzle-orm\";\nimport type {\n BaseSQLiteDatabase,\n SQLiteTableWithColumns,\n} from \"drizzle-orm/sqlite-core\";\nimport { CadmusAccessDeniedError, CadmusCmsError } from \"../errors.js\";\nimport { collectionSearchTableName, extractSearchText } from \"./codegen.js\";\nimport { diffDocuments, type FieldChange } from \"./patch.js\";\nimport {\n type CollectionAccess,\n type CollectionConfig,\n flattenDoc,\n flattenFields,\n type JsonValue,\n nestDoc,\n type RelationshipDepth,\n} from \"./types.js\";\nimport { assertValid } from \"./validation.js\";\n\n// biome-ignore lint/suspicious/noExplicitAny: matches drizzle-orm's own SQLiteTableWithColumns default generic usage\ntype AnyTable = SQLiteTableWithColumns<any>;\n\n/**\n * `TContext` is the per-request value passed to every method and forwarded\n * unchanged to the collection's `access` functions (see {@link CollectionAccess}).\n * Cadmus doesn't standardize its shape — Cadmea types it as `{ session }`,\n * other consumers may type it differently. `context` is a required first\n * argument on every method (not optional) so a call site can't forget it.\n */\nexport interface LocalApi<TTable extends AnyTable, TContext = unknown> {\n /**\n * `depth: 0` (default) returns relationship fields as bare ids; `depth: 1`\n * batch-resolves `hasMany: false` relationship fields into the related\n * row, gated by that collection's own `read` access fn — see\n * `resolveRelationships` below. Requires `createLocalApi`'s `registry`\n * param; throws CadmusCmsError if `depth: 1` is requested without one.\n */\n find(\n context: TContext,\n options?: {\n where?: SQL;\n depth?: RelationshipDepth;\n /** Row cap, applied after `where` — for paginated list views. */\n limit?: number;\n /** Rows to skip, applied after `where` — pairs with `limit`. */\n offset?: number;\n /** One or more `asc(table.col)`/`desc(table.col)` expressions. */\n orderBy?: SQL | SQL[];\n },\n ): Promise<InferSelectModel<TTable>[]>;\n findByID(\n context: TContext,\n id: number,\n options?: { depth?: RelationshipDepth },\n ): Promise<InferSelectModel<TTable>>;\n /**\n * Total row count for `where` (ignoring `limit`/`offset`) — pairs with\n * `find` to compute page counts/next-page availability without fetching\n * every row. Gated by the same `read` access check as `find`.\n */\n count(context: TContext, options?: { where?: SQL }): Promise<number>;\n /**\n * Full-text search over this collection's `search.fields`-configured\n * companion FTS5 table — see types.ts's `CollectionConfig.search` and\n * codegen.ts's `collectionSearchTableSQL`. Gated by `read` access, same\n * as `find`/`findByID`. Throws `CadmusCmsError` if the collection has no\n * `search` config.\n */\n search(\n context: TContext,\n query: string,\n options?: { limit?: number },\n ): Promise<InferSelectModel<TTable>[]>;\n create(\n context: TContext,\n input: InferInsertModel<TTable>,\n ): Promise<InferSelectModel<TTable>>;\n update(\n context: TContext,\n id: number,\n input: Partial<InferInsertModel<TTable>>,\n ): Promise<InferSelectModel<TTable>>;\n deleteByID(context: TContext, id: number): Promise<InferSelectModel<TTable>>;\n}\n\n// `input` here is always already-flattened (group fields expanded to\n// `<key>_<subKey>`) — both callers in create()/update() flatten via\n// flattenDoc before reaching these, so flattening config.fields too means\n// every key in input lines up with a key in this flattened field map.\nfunction validateRequiredFields(\n config: CollectionConfig,\n input: Record<string, unknown>,\n): void {\n for (const [key, field] of Object.entries(flattenFields(config.fields))) {\n const hasDefault = field.defaultValue !== undefined;\n if (field.required && !hasDefault && input[key] === undefined) {\n throw new CadmusCmsError(\n `Missing required field \"${key}\" for collection \"${config.slug}\"`,\n );\n }\n }\n}\n\nfunction rejectUnknownFields(\n config: CollectionConfig,\n input: Record<string, unknown>,\n): void {\n const flatFields = flattenFields(config.fields);\n for (const key of Object.keys(input)) {\n if (!(key in flatFields)) {\n throw new CadmusCmsError(\n `Unknown field \"${key}\" for collection \"${config.slug}\"`,\n );\n }\n }\n}\n\nfunction wrapWriteError(config: CollectionConfig, error: unknown): never {\n if (error instanceof CadmusCmsError) throw error;\n const message = error instanceof Error ? error.message : String(error);\n if (message.includes(\"UNIQUE constraint failed\")) {\n throw new CadmusCmsError(\n `Unique constraint violated for collection \"${config.slug}\"`,\n error,\n );\n }\n throw new CadmusCmsError(\n `Write failed for collection \"${config.slug}\"`,\n error,\n );\n}\n\nfunction notFound(config: CollectionConfig, id: number): never {\n throw new CadmusCmsError(`No \"${config.slug}\" document found with id ${id}`);\n}\n\n/**\n * Lets `createLocalApi` resolve `depth: 1` relationship fields without\n * importing every other collection's Local API (which would be a circular\n * dependency the moment two collections relate to each other). The\n * registry is just the raw ingredients — a table and a config per\n * collection slug — built once by the app (e.g. from `cadmeaConfig.collections`)\n * and passed to every `createLocalApi` call that has relationship fields.\n *\n * `apis` is a second, optional registry on the same object, for a\n * different problem: a *hook* (not `createLocalApi` itself) on one\n * collection that needs to write to *another* collection's Local API —\n * e.g. a CRM upsert hook on a lead-capture collection that creates/updates\n * `contacts`/`activities` rows. `tables`/`configs` can't serve this, since\n * a hook needs a real `LocalApi` (with its own access/hooks/search wiring\n * already applied), not raw ingredients to rebuild one from.\n *\n * The chicken-and-egg problem this solves: building collection A's\n * `LocalApi` might need to reference collection B's `LocalApi` (for a\n * hook), but collection B's `LocalApi` doesn't exist yet at the point A's\n * is constructed — and vice versa if B also has a hook referencing A.\n * The fix is **late binding**: build one `CmsRegistry` object, pass the\n * *same reference* into every `createLocalApi` call (so every collection's\n * hooks close over the same mutable object), construct every `LocalApi`,\n * then fill in `registry.apis` afterwards:\n *\n * ```ts\n * const registry: CmsRegistry = { tables, configs, apis: {} };\n * const contactsApi = createLocalApi(db, contactsTable, contactsCollection, registry);\n * const inquiriesApi = createLocalApi(db, inquiriesTable, inquiriesCollection, registry);\n * // populate *after* every createLocalApi call returns — any hook that\n * // reads registry.apis lazily (inside its returned function body, not\n * // at hook-factory-call time) sees the fully-populated map, since hooks\n * // only ever run once real requests start landing.\n * Object.assign(registry.apis!, { contacts: contactsApi, inquiries: inquiriesApi });\n * ```\n *\n * See `getRegisteredApi` for the accessor a hook factory should use to\n * read from this map, rather than indexing `registry.apis` directly.\n */\nexport interface CmsRegistry {\n tables: Record<string, AnyTable>;\n configs: Record<string, CollectionConfig>;\n // biome-ignore lint/suspicious/noExplicitAny: collections in the same registry can have different TContext shapes — same `any` escape hatch hono/cms.ts's CmsRoutesOptions already uses for the same reason\n apis?: Record<string, LocalApi<AnyTable, any>>;\n}\n\n/**\n * Reads collection `slug`'s `LocalApi` out of `registry.apis` — the\n * accessor hook factories should use (see `CmsRegistry`'s doc comment for\n * the late-binding pattern this assumes) instead of indexing\n * `registry.apis` directly, so every caller gets the same clear error if\n * the registry wasn't built/populated correctly. `TContext` is a type-only\n * parameter (the registry itself is stored with `never` to stay variance-\n * safe across collections with different context shapes) — callers assert\n * the context type they expect, the same way `resolveRelationships`'s own\n * registry lookups do.\n */\nexport function getRegisteredApi<TContext>(\n registry: CmsRegistry | undefined,\n slug: string,\n): LocalApi<AnyTable, TContext> {\n const api = registry?.apis?.[slug];\n if (!api) {\n throw new CadmusCmsError(\n `No LocalApi registered for collection \"${slug}\" — pass a CmsRegistry whose \"apis\" map has been populated with every collection a hook needs to reach (see CmsRegistry's doc comment for the late-binding build order)`,\n );\n }\n return api;\n}\n\n/**\n * Batch-resolves this collection's `hasMany: false` relationship fields\n * for an already-fetched page of `rows`, one query per relationship field\n * (not one query per row — the N+1 the `depth: 1` design note in types.ts\n * calls out avoiding). The related collection's `read` access fn is run\n * once per field against `context`, not once per row: there's a single\n * yes/no for \"can this context read collection X\", not a row-by-row\n * filter. When it rejects, the field is left as the bare id rather than\n * throwing — a denied relationship is an omission, not a failed request.\n * `hasMany: true` relationship fields are untouched (no column on this\n * table to resolve from — they live in a join table, out of scope here).\n */\nasync function resolveRelationships<TContext>(\n db: BaseSQLiteDatabase<\"async\", unknown>,\n config: CollectionConfig,\n rows: AnyRecord[],\n context: TContext,\n registry: CmsRegistry | undefined,\n): Promise<AnyRecord[]> {\n const relationshipFields = Object.entries(config.fields).filter(\n ([, field]) => field.type === \"relationship\" && !field.hasMany,\n );\n if (relationshipFields.length === 0) return rows;\n if (!registry) {\n throw new CadmusCmsError(\n `Collection \"${config.slug}\" requested depth: 1 but createLocalApi was not given a registry to resolve relationship fields against`,\n );\n }\n\n let result = rows;\n for (const [key, field] of relationshipFields) {\n const relationTo = (field as { relationTo: string }).relationTo;\n const relatedConfig = registry.configs[relationTo];\n const relatedTable = registry.tables[relationTo];\n if (!relatedConfig || !relatedTable) {\n throw new CadmusCmsError(\n `Collection \"${config.slug}\" field \"${key}\" relates to unknown collection \"${relationTo}\" — not present in the registry`,\n );\n }\n\n const readFn = relatedConfig.access?.read;\n const allowed = readFn ? await readFn(context) : true;\n if (!allowed) continue;\n\n const ids = [\n ...new Set(\n result\n .map((row) => row[key])\n .filter((id): id is number => typeof id === \"number\"),\n ),\n ];\n if (ids.length === 0) continue;\n\n const relatedRows = await db\n .select()\n .from(relatedTable)\n .where(inArray(relatedTable.id, ids));\n const byId = new Map(\n relatedRows.map((row) => [(row as AnyRecord).id, row as AnyRecord]),\n );\n\n result = result.map((row) => {\n const id = row[key];\n const related = typeof id === \"number\" ? byId.get(id) : undefined;\n return related ? { ...row, [key]: related } : row;\n });\n }\n return result;\n}\n\n// Hook runners. `config.hooks` (CollectionHooks) is folded into every\n// write/read below. Transforming hooks (beforeChange, beforeRead,\n// afterRead) run in array order, each fed the previous one's output; side-\n// effect hooks (afterChange, beforeDelete, afterDelete) run in order for\n// their effects only. All may be async. `config.access` is checked by\n// checkAccess() below, before any hook or DB work runs for that operation.\ntype AnyRecord = Record<string, unknown>;\n\n/**\n * Non-throwing counterpart to `checkAccess` below, for UI code that wants\n * to hide/disable an action a context can't perform rather than let it\n * fail server-side after a click (see Phase 6 / issue #26's\n * `getPageCapabilities`). `checkAccess` calls through this same function\n * rather than duplicating the \"no access fn = allowed\" logic, so `can()`'s\n * answer and the real operation's enforcement can never disagree.\n */\nexport async function can<TContext>(\n config: CollectionConfig,\n operation: keyof CollectionAccess,\n context: TContext,\n): Promise<boolean> {\n const fn = config.access?.[operation];\n if (!fn) return true;\n return await fn(context);\n}\n\n// Runs config.access[operation](context) if configured, throwing\n// CadmusAccessDeniedError when it resolves false. No access function for\n// an operation means that operation is unconditionally allowed — matches\n// the pre-Section-2 default of \"no enforcement at all\".\nasync function checkAccess<TContext>(\n config: CollectionConfig,\n operation: keyof CollectionAccess,\n context: TContext,\n): Promise<void> {\n if (await can(config, operation, context)) return;\n throw new CadmusAccessDeniedError(\n `Access denied for \"${operation}\" on collection \"${config.slug}\"`,\n );\n}\n\nasync function runBeforeChange(\n config: CollectionConfig,\n data: AnyRecord,\n): Promise<AnyRecord> {\n let result = data;\n for (const hook of config.hooks?.beforeChange ?? []) {\n result = (await hook({ data: result })) as AnyRecord;\n }\n return result;\n}\n\nasync function runAfterChange(\n config: CollectionConfig,\n doc: AnyRecord,\n operation: \"create\" | \"update\",\n): Promise<void> {\n for (const hook of config.hooks?.afterChange ?? []) {\n await hook({ doc, operation });\n }\n}\n\nasync function runReadHooks(\n config: CollectionConfig,\n doc: AnyRecord,\n): Promise<AnyRecord> {\n let result = doc;\n for (const hook of config.hooks?.beforeRead ?? []) {\n result = (await hook({ doc: result })) as AnyRecord;\n }\n for (const hook of config.hooks?.afterRead ?? []) {\n result = (await hook({ doc: result })) as AnyRecord;\n }\n return result;\n}\n\nfunction hasReadHooks(config: CollectionConfig): boolean {\n return Boolean(\n config.hooks?.beforeRead?.length || config.hooks?.afterRead?.length,\n );\n}\n\nasync function runBeforeDelete(\n config: CollectionConfig,\n id: number,\n): Promise<void> {\n for (const hook of config.hooks?.beforeDelete ?? []) {\n await hook({ id });\n }\n}\n\nasync function runAfterDelete(\n config: CollectionConfig,\n id: number,\n): Promise<void> {\n for (const hook of config.hooks?.afterDelete ?? []) {\n await hook({ id });\n }\n}\n\n// Keeps a collection's FTS5 companion table (see codegen.ts's\n// collectionSearchTableSQL) in sync on every create/update — issue #29's\n// \"populated via an afterChange hook\" wording, but wired in here rather\n// than exposed on `CollectionHooks.afterChange` since it's derived\n// entirely from `config.search` (no operator-authored hook function),\n// the same precedent as the `versions` companion table being built into\n// createVersionedLocalApi rather than a user-facing hook. FTS5 has no\n// native UPSERT; a plain DELETE-then-INSERT keyed by rowid (== the main\n// table's `id`) is the standard pattern for keeping an external,\n// non-content FTS5 table in sync with its source row.\nasync function syncSearchIndex(\n db: BaseSQLiteDatabase<\"async\", unknown>,\n config: CollectionConfig,\n doc: AnyRecord,\n): Promise<void> {\n const fields = config.search?.fields;\n if (!fields?.length) return;\n const id = doc.id;\n if (typeof id !== \"number\") return;\n const fts = sql.identifier(collectionSearchTableName(config));\n const columnList = sql.join(\n fields.map((key) => sql.identifier(key)),\n sql.raw(\", \"),\n );\n const values = extractSearchText(config, doc);\n const valueList = sql.join(\n values.map((value) => sql`${value}`),\n sql.raw(\", \"),\n );\n await db.run(sql`DELETE FROM ${fts} WHERE rowid = ${id}`);\n await db.run(\n sql`INSERT INTO ${fts} (rowid, ${columnList}) VALUES (${id}, ${valueList})`,\n );\n}\n\nasync function removeFromSearchIndex(\n db: BaseSQLiteDatabase<\"async\", unknown>,\n config: CollectionConfig,\n id: number,\n): Promise<void> {\n if (!config.search?.fields.length) return;\n const fts = sql.identifier(collectionSearchTableName(config));\n await db.run(sql`DELETE FROM ${fts} WHERE rowid = ${id}`);\n}\n\nexport function createLocalApi<TTable extends AnyTable, TContext = unknown>(\n db: BaseSQLiteDatabase<\"async\", unknown>,\n table: TTable,\n config: CollectionConfig,\n registry?: CmsRegistry,\n): LocalApi<TTable, TContext> {\n const idColumn = table.id;\n // Group fields are the only reason a document's shape (nested) ever\n // differs from its row's shape (flat columns) — skip the flatten/nest\n // round-trip entirely for the common case of a collection with none, so\n // every existing collection (none of which have group fields yet) pays\n // zero cost for this.\n const hasGroupFields = Object.values(config.fields).some(\n (field) => field.type === \"group\",\n );\n const toFlatDoc = (doc: Record<string, unknown>) =>\n hasGroupFields ? flattenDoc(config.fields, doc) : doc;\n const toNestedDoc = (row: Record<string, unknown>) =>\n hasGroupFields ? (nestDoc(config.fields, row) as AnyRecord) : row;\n\n return {\n async find(context, options) {\n await checkAccess(config, \"read\", context);\n if (\n options?.depth !== undefined &&\n options.depth !== 0 &&\n options.depth !== 1\n ) {\n throw new CadmusCmsError(\n `Relationship resolution depth ${options.depth} is not supported for collection \"${config.slug}\" (only 0 and 1 are)`,\n );\n }\n let query = db.select().from(table).where(options?.where).$dynamic();\n if (options?.orderBy !== undefined) {\n query = query.orderBy(\n ...(Array.isArray(options.orderBy)\n ? options.orderBy\n : [options.orderBy]),\n );\n }\n if (options?.limit !== undefined) query = query.limit(options.limit);\n if (options?.offset !== undefined) query = query.offset(options.offset);\n const rows = await query;\n const nestedRows = rows.map((row) =>\n toNestedDoc(row as Record<string, unknown>),\n );\n const afterHooks = hasReadHooks(config)\n ? await Promise.all(\n nestedRows.map((row) =>\n runReadHooks(config, row as Record<string, unknown>),\n ),\n )\n : nestedRows;\n const resolved =\n options?.depth === 1\n ? await resolveRelationships(\n db,\n config,\n afterHooks as AnyRecord[],\n context,\n registry,\n )\n : afterHooks;\n return resolved as InferSelectModel<TTable>[];\n },\n\n async count(context, options) {\n await checkAccess(config, \"read\", context);\n const [row] = await db\n .select({ value: countRows() })\n .from(table)\n .where(options?.where);\n return row?.value ?? 0;\n },\n\n async findByID(context, id, options) {\n await checkAccess(config, \"read\", context);\n if (\n options?.depth !== undefined &&\n options.depth !== 0 &&\n options.depth !== 1\n ) {\n throw new CadmusCmsError(\n `Relationship resolution depth ${options.depth} is not supported for collection \"${config.slug}\" (only 0 and 1 are)`,\n );\n }\n const [row] = await db.select().from(table).where(eq(idColumn, id));\n if (!row) notFound(config, id);\n const nestedRow = toNestedDoc(row as Record<string, unknown>);\n const afterHooks = hasReadHooks(config)\n ? await runReadHooks(config, nestedRow as Record<string, unknown>)\n : nestedRow;\n const resolved =\n options?.depth === 1\n ? (\n await resolveRelationships(\n db,\n config,\n [afterHooks as AnyRecord],\n context,\n registry,\n )\n )[0]\n : afterHooks;\n return resolved as InferSelectModel<TTable>;\n },\n\n async search(context, query, options) {\n await checkAccess(config, \"read\", context);\n if (!config.search?.fields.length) {\n throw new CadmusCmsError(\n `Collection \"${config.slug}\" has no \"search\" config — cannot run search()`,\n );\n }\n const fts = sql.identifier(collectionSearchTableName(config));\n const limit = options?.limit ?? 20;\n const rows = await db.all(sql`\n SELECT ${table}.* FROM ${fts}\n JOIN ${table} ON ${idColumn} = ${fts}.rowid\n WHERE ${fts} MATCH ${query}\n ORDER BY rank\n LIMIT ${limit}\n `);\n return rows.map((row) =>\n toNestedDoc(row as Record<string, unknown>),\n ) as InferSelectModel<TTable>[];\n },\n\n async create(context, input) {\n await checkAccess(config, \"create\", context);\n // beforeChange runs before validation so a hook may supply or default\n // a required field (e.g. the SEO plugin defaulting metaTitle). Hooks\n // always see/return the nested document shape — flattening for the\n // DB write happens after, never inside a hook.\n const data = await runBeforeChange(\n config,\n input as Record<string, unknown>,\n );\n const flatData = toFlatDoc(data);\n validateRequiredFields(config, flatData);\n rejectUnknownFields(config, flatData);\n // Chainable field rules (#16) — required-flag and unknown-field checks\n // above stay; this adds value-level rules (min/max/regex/unique/\n // reference/custom) and throws CadmusValidationError with per-field\n // violations. Runs after beforeChange so a hook-supplied value is\n // validated, and before the insert so unique/reference pre-check\n // rather than relying on a raw DB constraint error.\n await assertValid(config, data as Record<string, unknown>, {\n operation: \"create\",\n db,\n table,\n registry,\n });\n let row: InferSelectModel<TTable> | undefined;\n try {\n const [inserted] = await db\n .insert(table)\n // biome-ignore lint/suspicious/noExplicitAny: TTable is an abstract generic here, so drizzle's column-mapped insert types can't narrow against it — InferInsertModel<TTable> already gives callers the real, concrete typing.\n .values(flatData as any)\n .returning();\n row = inserted as InferSelectModel<TTable>;\n } catch (error) {\n wrapWriteError(config, error);\n }\n // wrapWriteError returns `never`, so reaching here means the insert\n // succeeded and `row` is set. afterChange runs outside the try so its\n // side-effect errors aren't mis-reported as write failures.\n const doc = toNestedDoc(row as AnyRecord);\n await syncSearchIndex(db, config, doc as AnyRecord);\n await runAfterChange(config, doc as Record<string, unknown>, \"create\");\n return doc as InferSelectModel<TTable>;\n },\n\n async update(context, id, input) {\n await checkAccess(config, \"update\", context);\n const data = await runBeforeChange(\n config,\n input as Record<string, unknown>,\n );\n const flatData = toFlatDoc(data);\n rejectUnknownFields(config, flatData);\n // Validate only the fields this partial update actually carries — a\n // partial update must not fail an absent field's rules (it isn't\n // changing it). `unique` excludes this row by id.\n await assertValid(config, data as Record<string, unknown>, {\n operation: \"update\",\n id,\n onlyFields: new Set(Object.keys(flatData)),\n db,\n table,\n registry,\n });\n let row: InferSelectModel<TTable> | undefined;\n try {\n const [updated] = await db\n .update(table)\n // biome-ignore lint/suspicious/noExplicitAny: see create() above\n .set(flatData as any)\n .where(eq(idColumn, id))\n .returning();\n if (!updated) notFound(config, id);\n row = updated as InferSelectModel<TTable>;\n } catch (error) {\n wrapWriteError(config, error);\n }\n const doc = toNestedDoc(row as AnyRecord);\n await syncSearchIndex(db, config, doc as AnyRecord);\n await runAfterChange(config, doc as Record<string, unknown>, \"update\");\n return doc as InferSelectModel<TTable>;\n },\n\n async deleteByID(context, id) {\n await checkAccess(config, \"delete\", context);\n await runBeforeDelete(config, id);\n const [rawRow] = await db\n .delete(table)\n .where(eq(idColumn, id))\n .returning();\n if (!rawRow) notFound(config, id);\n const row = toNestedDoc(rawRow as Record<string, unknown>);\n await removeFromSearchIndex(db, config, id);\n await runAfterDelete(config, id);\n return row as InferSelectModel<TTable>;\n },\n };\n}\n\nfunction notFoundVersion(config: CollectionConfig, id: number): never {\n throw new CadmusCmsError(`No \"${config.slug}\" version found with id ${id}`);\n}\n\n/**\n * Extends {@link LocalApi} with draft/publish operations for a collection\n * that opted in via `CollectionConfig.versions.drafts` (see codegen.ts's\n * `collectionVersionsTable`). A separate interface (not a wider\n * `LocalApi`) so non-versioned collections' types don't grow these methods\n * — TypeScript can't conditionally widen `createLocalApi`'s return type\n * off a runtime config value, so this is `createVersionedLocalApi`'s own\n * factory rather than a branch inside `createLocalApi`.\n *\n * Scope, deliberately: a document is always created via the inherited\n * `create()` first (existing behavior, unaffected by versioning) — these\n * methods operate against an *existing* row. `saveDraft` never validates\n * required fields (an incomplete draft is valid); `publish` runs the same\n * full validation `create`/`update` do, since publishing is what makes a\n * version the public-facing document. Plain `find`/`findByID` are\n * unchanged by any of this — they always return the main table's current\n * row regardless of `publishedVersionId`; filtering reads to\n * published-only content is not this phase's concern.\n */\nexport interface VersionedLocalApi<\n TTable extends AnyTable,\n TVersionsTable extends AnyTable,\n TContext = unknown,\n> extends LocalApi<TTable, TContext> {\n findVersions(\n context: TContext,\n parentId: number,\n ): Promise<InferSelectModel<TVersionsTable>[]>;\n /** Inserts a new version row holding `input` as a draft snapshot. */\n saveDraft(\n context: TContext,\n id: number,\n input: Partial<InferInsertModel<TTable>>,\n ): Promise<InferSelectModel<TVersionsTable>>;\n /** Copies a version's snapshot onto the main row and marks it published. */\n publish(\n context: TContext,\n versionId: number,\n ): Promise<InferSelectModel<TTable>>;\n /** Clears the main row's published pointer; the row's data is untouched. */\n unpublish(context: TContext, id: number): Promise<InferSelectModel<TTable>>;\n /**\n * Field-level diff (issue #14) between two version snapshots' `versionData`\n * — the per-field added/removed/changed list a version-history UI renders.\n * Both versions must belong to the same parent. Bookkeeping keys\n * (`id`/`createdAt`/`status`/`publishedVersionId`) are ignored.\n */\n diffVersions(\n context: TContext,\n fromVersionId: number,\n toVersionId: number,\n ): Promise<FieldChange[]>;\n}\n\nexport function createVersionedLocalApi<\n TTable extends AnyTable,\n TVersionsTable extends AnyTable,\n TContext = unknown,\n>(\n db: BaseSQLiteDatabase<\"async\", unknown>,\n table: TTable,\n versionsTable: TVersionsTable,\n config: CollectionConfig,\n registry?: CmsRegistry,\n): VersionedLocalApi<TTable, TVersionsTable, TContext> {\n const base = createLocalApi<TTable, TContext>(db, table, config, registry);\n const idColumn = table.id;\n const versionsIdColumn = versionsTable.id;\n const versionsParentIdColumn = versionsTable.parentId;\n\n return {\n ...base,\n\n async findVersions(context, parentId) {\n await checkAccess(config, \"read\", context);\n const rows = await db\n .select()\n .from(versionsTable)\n .where(eq(versionsParentIdColumn, parentId))\n .orderBy(desc(versionsIdColumn));\n return rows as InferSelectModel<TVersionsTable>[];\n },\n\n async saveDraft(context, id, input) {\n await checkAccess(config, \"update\", context);\n const [parent] = await db.select().from(table).where(eq(idColumn, id));\n if (!parent) notFound(config, id);\n const data = await runBeforeChange(\n config,\n input as Record<string, unknown>,\n );\n rejectUnknownFields(config, data);\n const insertValues = {\n parentId: id,\n versionData: data,\n status: \"draft\",\n // biome-ignore lint/suspicious/noExplicitAny: TVersionsTable is abstract here, same rationale as createLocalApi.create's .values() cast\n } as any;\n const [row] = await db\n .insert(versionsTable)\n .values(insertValues)\n .returning();\n return row as InferSelectModel<TVersionsTable>;\n },\n\n async publish(context, versionId) {\n await checkAccess(config, \"publish\", context);\n const [version] = await db\n .select()\n .from(versionsTable)\n .where(eq(versionsIdColumn, versionId));\n if (!version) notFoundVersion(config, versionId);\n const versionRecord = version as Record<string, unknown>;\n const data = await runBeforeChange(\n config,\n versionRecord.versionData as Record<string, unknown>,\n );\n validateRequiredFields(config, data);\n rejectUnknownFields(config, data);\n const parentId = versionRecord.parentId as number;\n // Publishing writes the whole version snapshot to the live row, so\n // validate every field (not a partial). `unique` excludes the parent\n // row by its own id.\n await assertValid(config, data, {\n operation: \"update\",\n id: parentId,\n db,\n table,\n registry,\n });\n let doc: InferSelectModel<TTable> | undefined;\n try {\n const [row] = await db\n .update(table)\n // biome-ignore lint/suspicious/noExplicitAny: see createLocalApi.update's .set() cast\n .set({ ...data, publishedVersionId: versionId } as any)\n .where(eq(idColumn, parentId))\n .returning();\n if (!row) notFound(config, parentId);\n doc = row as InferSelectModel<TTable>;\n } catch (error) {\n wrapWriteError(config, error);\n }\n await db\n .update(versionsTable)\n // biome-ignore lint/suspicious/noExplicitAny: status is a fixed two-value enum column\n .set({ status: \"published\" } as any)\n .where(eq(versionsIdColumn, versionId));\n await syncSearchIndex(db, config, doc as AnyRecord);\n // publish() writes to an already-existing row, never a new one —\n // counts as \"update\" the same way createLocalApi.update() does.\n await runAfterChange(config, doc as Record<string, unknown>, \"update\");\n return doc as InferSelectModel<TTable>;\n },\n\n async unpublish(context, id) {\n await checkAccess(config, \"publish\", context);\n const [row] = await db\n .update(table)\n // biome-ignore lint/suspicious/noExplicitAny: publishedVersionId is a bookkeeping column generated by codegen, not part of InferInsertModel<TTable>\n .set({ publishedVersionId: null } as any)\n .where(eq(idColumn, id))\n .returning();\n if (!row) notFound(config, id);\n return row as InferSelectModel<TTable>;\n },\n\n async diffVersions(context, fromVersionId, toVersionId) {\n await checkAccess(config, \"read\", context);\n const rows = await db\n .select()\n .from(versionsTable)\n .where(inArray(versionsIdColumn, [fromVersionId, toVersionId]));\n const byId = new Map(\n rows.map((r) => [\n (r as Record<string, unknown>).id as number,\n (r as Record<string, unknown>).versionData as Record<\n string,\n JsonValue\n >,\n ]),\n );\n const before = byId.get(fromVersionId);\n const after = byId.get(toVersionId);\n if (!before) notFoundVersion(config, fromVersionId);\n if (!after) notFoundVersion(config, toVersionId);\n // Ignore bookkeeping columns — only real content fields are of interest\n // in a version-history view.\n return diffDocuments(before, after, {\n ignore: [\"id\", \"createdAt\", \"status\", \"publishedVersionId\"],\n });\n },\n };\n}\n","// Copyright (c) 2026 BowenLabs. All rights reserved.\n// Cadmus is MIT licensed. See LICENSE in the repo root.\n\nimport type { CmsConfig, CollectionConfig } from \"./types.js\";\n\nexport interface CollectionMeta {\n slug: string;\n fields: CollectionConfig[\"fields\"];\n /** Whether `LocalApi.search()` is usable for this collection — see `CollectionConfig.search`. */\n searchable: boolean;\n}\n\n// Serializable introspection contract a CMS admin (or any other\n// consumer) uses to render generic UI without importing CollectionConfig\n// or CmsConfig directly. CollectionConfig is already plain, serializable\n// data — this is a stable, narrow public surface over it, not a\n// transformation.\nexport function getCollectionsMeta(config: CmsConfig): CollectionMeta[] {\n return config.collections.map((collection) => ({\n slug: collection.slug,\n fields: collection.fields,\n searchable: Boolean(collection.search?.fields.length),\n }));\n}\n","// Copyright (c) 2026 BowenLabs. All rights reserved.\n// Cadmus is MIT licensed. See LICENSE in the repo root.\n\nimport type { LocalApi } from \"./localApi.js\";\nimport { computePatch, type Patch } from \"./patch.js\";\nimport type { JsonValue } from \"./types.js\";\n\n/**\n * Content-migration runner (issue #18) — adopts Sanity's `sanity/migrate`\n * idea (pattern, not code): a versioned, repeatable transform over a\n * collection's stored documents, for reshaping content when a block/field\n * type changes (distinct from Drizzle *schema* migrations, which only touch\n * columns — this reshapes the JSON content inside them).\n *\n * A migration declares a per-document `document(doc)` transform; the runner\n * streams every document, computes the {@link Patch} from old→new (reusing\n * #14's patch model), and either reports it (`dryRun`) or applies it via the\n * collection's Local API. Idempotent by construction: a transform that's\n * already been applied produces an empty patch, so re-running changes\n * nothing.\n */\n\ntype Doc = Record<string, JsonValue>;\n\nexport interface Migration<TDoc extends Doc = Doc> {\n /** Stable identifier — name the checked-in migration file after this. */\n name: string;\n /**\n * Transform one document. Return the reshaped document, or `undefined`\n * (or the unchanged doc) to leave it as-is. Must be pure and idempotent —\n * applying it twice yields the same result as once.\n */\n document: (doc: TDoc) => TDoc | undefined | Promise<TDoc | undefined>;\n}\n\n/** Identity helper — gives a migration definition its type + a greppable call site. */\nexport function defineMigration<TDoc extends Doc = Doc>(\n migration: Migration<TDoc>,\n): Migration<TDoc> {\n return migration;\n}\n\nexport interface MigrationChange {\n id: number;\n patch: Patch;\n}\n\nexport interface MigrationResult {\n migration: string;\n dryRun: boolean;\n scanned: number;\n changed: number;\n /** Per-document patches (always populated — the dry-run report). */\n changes: MigrationChange[];\n errors: string[];\n}\n\nexport interface RunMigrationOptions<TContext> {\n // biome-ignore lint/suspicious/noExplicitAny: the runner is generic across any collection's table type\n api: LocalApi<any, TContext>;\n /** Context passed to the Local API's read/update (access + hooks). */\n context: TContext;\n /** When true, compute + report patches but write nothing. Default false. */\n dryRun?: boolean;\n}\n\n// A patch's net effect as a Local API update payload: `set` → the value,\n// `unset` → null (the DB-level \"cleared\" representation). Only changed\n// fields are sent, so hooks/validation see a minimal partial update.\nfunction patchToUpdate(patch: Patch): Record<string, JsonValue | null> {\n const values: Record<string, JsonValue | null> = {};\n for (const op of patch) {\n values[op.path] = op.op === \"set\" ? op.value : null;\n }\n return values;\n}\n\n/**\n * Run a migration over every document in a collection. Reads all documents\n * through `api.find`, applies `migration.document`, and (unless `dryRun`)\n * writes the resulting patch through `api.update`. Returns a report of what\n * changed — run it `dryRun` first, then apply.\n */\nexport async function runMigration<TContext>(\n migration: Migration,\n options: RunMigrationOptions<TContext>,\n): Promise<MigrationResult> {\n const { api, context, dryRun = false } = options;\n const rows = (await api.find(context)) as Array<Doc & { id: number }>;\n\n const changes: MigrationChange[] = [];\n const errors: string[] = [];\n let changed = 0;\n\n for (const before of rows) {\n try {\n const after = (await migration.document(before)) ?? before;\n const patch = computePatch(before, after);\n if (patch.length === 0) continue;\n changes.push({ id: before.id, patch });\n changed++;\n if (!dryRun) {\n await api.update(context, before.id, patchToUpdate(patch));\n }\n } catch (err) {\n errors.push(`document ${before.id}: ${String(err)}`);\n }\n }\n\n return {\n migration: migration.name,\n dryRun,\n scanned: rows.length,\n changed,\n changes,\n errors,\n };\n}\n","// Copyright (c) 2026 BowenLabs. All rights reserved.\n// Cadmus is MIT licensed. See LICENSE in the repo root.\n\nimport { CadmusCmsError } from \"../errors.js\";\nimport {\n type CmsConfig,\n type CollectionConfig,\n type FieldConfig,\n flattenFields,\n} from \"./types.js\";\n\nfunction toSnakeCase(value: string): string {\n return value.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`);\n}\n\nfunction quote(value: string): string {\n return JSON.stringify(value);\n}\n\n// Mirrors codegen.ts's fieldToColumn switch, emitting drizzle-orm\n// source text instead of building a runtime column. Kept as its own\n// switch (not shared code) since the two have different outputs\n// (Column vs. string) — codegen.test.ts's schema-parity assertions are\n// what keep this in sync with codegen.ts's actual runtime behavior.\nfunction fieldToColumnSource(\n key: string,\n field: FieldConfig,\n usedBuilders: Set<string>,\n): string {\n const columnName = field.name ?? toSnakeCase(key);\n\n switch (field.type) {\n case \"text\":\n case \"upload\": {\n usedBuilders.add(\"text\");\n let source = `text(${quote(columnName)})`;\n if (field.required) source += \".notNull()\";\n if (field.unique) source += \".unique()\";\n if (field.defaultValue !== undefined) {\n source += `.default(${quote(field.defaultValue)})`;\n }\n return source;\n }\n case \"richText\":\n case \"array\":\n case \"json\": {\n usedBuilders.add(\"text\");\n // `.$type<JsonValue>()` mirrors codegen.ts's fieldToColumn — see\n // types.ts's JsonValue doc comment for why drizzle's inferred\n // `unknown` needs overriding here. `group` fields never reach here —\n // see flattenFields below.\n let source = `text(${quote(columnName)}, { mode: \"json\" }).$type<JsonValue>()`;\n if (field.required) source += \".notNull()\";\n if (field.defaultValue !== undefined) {\n source += `.default(${JSON.stringify(field.defaultValue)})`;\n }\n return source;\n }\n case \"relationship\": {\n // hasMany relationship fields never reach here — collectionToTableSource\n // filters them out (see its own comment).\n usedBuilders.add(\"integer\");\n let source = `integer(${quote(columnName)})`;\n if (field.required) source += \".notNull()\";\n return source;\n }\n case \"select\": {\n usedBuilders.add(\"text\");\n const options = field.options.map(quote).join(\", \");\n let source = `text(${quote(columnName)}, { enum: [${options}] })`;\n if (field.required) source += \".notNull()\";\n if (field.defaultValue !== undefined) {\n source += `.default(${quote(field.defaultValue)})`;\n }\n return source;\n }\n case \"number\": {\n if (field.autoIncrement) {\n usedBuilders.add(\"integer\");\n return `integer(${quote(columnName)}).primaryKey({ autoIncrement: true })`;\n }\n usedBuilders.add(\"real\");\n let source = `real(${quote(columnName)})`;\n if (field.required) source += \".notNull()\";\n if (field.defaultValue !== undefined) {\n source += `.default(${field.defaultValue})`;\n }\n return source;\n }\n case \"date\": {\n usedBuilders.add(\"integer\");\n const mode = field.mode === \"timestamp_ms\" ? \"timestamp_ms\" : \"timestamp\";\n let source = `integer(${quote(columnName)}, { mode: ${quote(mode)} })`;\n if (field.required) source += \".notNull()\";\n if (field.defaultValue === \"now\") {\n source += \".$defaultFn(() => new Date())\";\n } else if (field.defaultValue instanceof Date) {\n source += `.$defaultFn(() => new Date(${field.defaultValue.getTime()}))`;\n }\n return source;\n }\n default:\n throw new CadmusCmsError(\n `Field type \"${(field as FieldConfig).type}\" is not yet supported by cadmus/cms schema-gen`,\n );\n }\n}\n\nfunction collectionToTableSource(\n config: CollectionConfig,\n usedBuilders: Set<string>,\n): string {\n const fieldLines = Object.entries(flattenFields(config.fields))\n // hasMany relationships have no column on this table — emitted as a\n // separate join table instead (see relationshipJoinTableSource).\n .filter(([, field]) => !(field.type === \"relationship\" && field.hasMany))\n .map(\n ([key, field]) =>\n ` ${key}: ${fieldToColumnSource(key, field, usedBuilders)},`,\n );\n // Mirrors codegen.ts's collectionToTable: a bookkeeping column, not a\n // content field, present only when this collection opts into versioning.\n if (config.versions?.drafts) {\n usedBuilders.add(\"integer\");\n fieldLines.push(' publishedVersionId: integer(\"published_version_id\"),');\n }\n return `export const ${config.slug} = sqliteTable(${quote(config.slug)}, {\\n${fieldLines.join(\"\\n\")}\\n});`;\n}\n\n// Mirrors codegen.ts's collectionVersionsTable.\nfunction versionsTableSource(\n config: CollectionConfig,\n usedBuilders: Set<string>,\n): string {\n usedBuilders.add(\"integer\");\n usedBuilders.add(\"text\");\n const tableName = `${config.slug}_versions`;\n return (\n `export const ${tableName} = sqliteTable(${quote(tableName)}, {\\n` +\n ' id: integer(\"id\").primaryKey({ autoIncrement: true }),\\n' +\n ' parentId: integer(\"parent_id\").notNull(),\\n' +\n ' versionData: text(\"version_data\", { mode: \"json\" }).$type<JsonValue>().notNull(),\\n' +\n ' status: text(\"status\", { enum: [\"draft\", \"published\"] }).notNull(),\\n' +\n ' createdAt: integer(\"created_at\", { mode: \"timestamp\" }).$defaultFn(() => new Date()),\\n' +\n \"});\"\n );\n}\n\n// Mirrors codegen.ts's relationshipJoinTables, emitting one\n// sqliteTable() source block per hasMany relationship field.\nfunction relationshipJoinTableSources(\n config: CollectionConfig,\n usedBuilders: Set<string>,\n): string[] {\n const blocks: string[] = [];\n for (const [key, field] of Object.entries(config.fields)) {\n if (field.type !== \"relationship\" || !field.hasMany) continue;\n usedBuilders.add(\"integer\");\n const tableName = `${config.slug}_${key}`;\n const ownColumn = `${config.slug}_id`;\n const relatedColumn = `${field.relationTo}_id`;\n blocks.push(\n `export const ${tableName} = sqliteTable(${quote(tableName)}, {\\n` +\n ` ${ownColumn}: integer(${quote(ownColumn)}).notNull(),\\n` +\n ` ${relatedColumn}: integer(${quote(relatedColumn)}).notNull(),\\n` +\n \"});\",\n );\n }\n return blocks;\n}\n\n// Generates the full TS source for a consuming app's generated Drizzle\n// schema file from a CmsConfig. Pure string generation — the caller (a\n// script run via tsx) is responsible for writing the result to disk\n// (and formatting it). Cadmus has no opinion on what the app names its\n// config file or where it lives — that's app-specific, never hardcoded\n// here (see CLAUDE.md \"Code in packages/cadmus/ must not contain\n// anything [app]-specific\").\nexport function generateSchemaSource(config: CmsConfig): string {\n const usedBuilders = new Set<string>([\"sqliteTable\"]);\n const blocks = config.collections.flatMap((collection) => [\n collectionToTableSource(collection, usedBuilders),\n ...relationshipJoinTableSources(collection, usedBuilders),\n ...(collection.versions?.drafts\n ? [versionsTableSource(collection, usedBuilders)]\n : []),\n ]);\n const importList = [...usedBuilders].sort().join(\", \");\n // Every JSON column emitted above carries a `.$type<JsonValue>()` call —\n // detected by string search rather than threading a second tracking set\n // through every *Source function, since this is the one place that\n // needs to know about it.\n const needsJsonValue = blocks.some((block) =>\n block.includes(\".$type<JsonValue>()\"),\n );\n return [\n \"// Generated by @thebes/cadmus/cms — do not hand-edit.\",\n \"// Source: this app's CmsConfig (see defineCmsConfig).\",\n // Biome's import-sort assist (run over consuming apps, not this\n // generated file directly) orders \"@thebes/cadmus/cms\" before\n // \"drizzle-orm/sqlite-core\" alphabetically — matching that order here\n // means a consumer running `biome check --write` over their repo\n // never sees this generated file flagged as needing a fix.\n ...(needsJsonValue\n ? ['import type { JsonValue } from \"@thebes/cadmus/cms\";']\n : []),\n `import { ${importList} } from \"drizzle-orm/sqlite-core\";`,\n \"\",\n blocks.join(\"\\n\\n\"),\n \"\",\n ].join(\"\\n\");\n}\n","// Copyright (c) 2026 BowenLabs. All rights reserved.\n// Cadmus is MIT licensed. See LICENSE in the repo root.\n\nimport type {\n CmsConfig,\n CollectionAdminConfig,\n CollectionConfig,\n} from \"./types.js\";\n\n/**\n * Cadmea's Structure Builder — the framework half of issue #12.\n *\n * Adopts Sanity's `sanity/structure` idea (pattern, not code): **decouple\n * the admin nav from the raw collection list.** Instead of mapping every\n * `config.collections` entry to an `/admin/<slug>` link — which surfaces\n * system/log tables as editable links and produces dead links — the sidebar\n * renders from an explicit, grouped structure derived here from each\n * collection's `admin` hints (see {@link CollectionAdminConfig}) plus\n * optional per-slug overrides supplied at the call site.\n *\n * Pure data in / pure data out: no SolidJS, no DOM, no server imports — so\n * it's safe to import from a client studio component (e.g. the site's\n * `PanelNav`) and trivially testable.\n */\n\n/** Default group heading for collections that don't declare `admin.group`. */\nexport const DEFAULT_STUDIO_GROUP = \"Content\";\n\n/** One navigable collection entry in the studio sidebar. */\nexport interface StudioStructureItem {\n /** The collection's slug. */\n slug: string;\n /** Human label — `admin.label`, else the capitalized slug. */\n label: string;\n /** Where the sidebar link points (`/admin/<slug>`, configurable prefix). */\n href: string;\n /** Read-only collections are viewable but not editable in the studio. */\n readOnly: boolean;\n /**\n * Singletons link straight to their editor rather than a list+create\n * flow. (The href is identical; the renderer uses this to skip the list.)\n */\n singleton: boolean;\n /** Opaque icon identifier from `admin.icon`, if any. */\n icon?: string;\n}\n\n/** A titled group of sidebar items, in render order. */\nexport interface StudioStructureGroup {\n title: string;\n items: StudioStructureItem[];\n}\n\nexport interface BuildStudioStructureOptions {\n /**\n * Per-slug presentation overrides, merged over each collection's own\n * `admin` block (override keys win). The escape hatch for plugin-injected\n * collections (`products`, `payments`, `webhook_events`, …) that can't\n * carry an `admin` block in hand-written config — the studio declares\n * their presentation here, exactly like Sanity defines structure at the\n * studio level rather than on the schema.\n */\n overrides?: Record<string, CollectionAdminConfig>;\n /**\n * Explicit group ordering by title. Groups listed here render first, in\n * this order; any remaining groups follow in first-appearance order. A\n * group title absent from `config`'s collections simply doesn't appear.\n */\n groupOrder?: readonly string[];\n /**\n * Link prefix for each item's `href`. Defaults to `/admin`, producing\n * `/admin/<slug>`. No trailing slash.\n */\n basePath?: string;\n}\n\nfunction capitalize(value: string): string {\n return value.length === 0 ? value : value[0].toUpperCase() + value.slice(1);\n}\n\nfunction resolveAdmin(\n collection: CollectionConfig,\n overrides: Record<string, CollectionAdminConfig> | undefined,\n): CollectionAdminConfig {\n return { ...collection.admin, ...overrides?.[collection.slug] };\n}\n\n/**\n * Build the studio sidebar structure from a resolved CMS config.\n *\n * - Hidden collections (`admin.hidden`) are dropped entirely.\n * - Each remaining collection is placed in its `admin.group` (or\n * {@link DEFAULT_STUDIO_GROUP}).\n * - Within a group, items sort by `admin.order` (ascending; unset sorts\n * after set), then by their original position in `config.collections` —\n * so config order is the stable tiebreaker.\n * - Groups render in `options.groupOrder` first, then first-appearance\n * order for the rest.\n *\n * The input is expected to be the *resolved* config (post-plugins), since\n * that's what carries plugin-injected collections like `products`.\n */\nexport function buildStudioStructure(\n config: CmsConfig,\n options: BuildStudioStructureOptions = {},\n): StudioStructureGroup[] {\n const basePath = options.basePath ?? \"/admin\";\n const groupOrder = options.groupOrder ?? [];\n\n // Preserve original index so config order can break ties deterministically.\n const ranked = config.collections.map((collection, index) => ({\n collection,\n index,\n admin: resolveAdmin(collection, options.overrides),\n }));\n\n const groups = new Map<string, StudioStructureItem[]>();\n // Track first-appearance order of group titles for the fallback ordering.\n const appearance: string[] = [];\n\n for (const { collection, admin } of ranked) {\n if (admin.hidden) continue;\n const title = admin.group ?? DEFAULT_STUDIO_GROUP;\n if (!groups.has(title)) {\n groups.set(title, []);\n appearance.push(title);\n }\n // biome-ignore lint/style/noNonNullAssertion: title was just ensured present above\n groups.get(title)!.push({\n slug: collection.slug,\n label: admin.label ?? capitalize(collection.slug),\n href: `${basePath}/${collection.slug}`,\n readOnly: admin.readOnly ?? false,\n singleton: admin.singleton ?? false,\n ...(admin.icon ? { icon: admin.icon } : {}),\n });\n }\n\n // Sort items within each group by (order ?? Infinity), then config index.\n // We rebuild a slug -> {order,index} lookup so the comparator stays O(1).\n const meta = new Map(\n ranked.map(({ collection, index, admin }) => [\n collection.slug,\n { order: admin.order ?? Number.POSITIVE_INFINITY, index },\n ]),\n );\n for (const items of groups.values()) {\n items.sort((a, b) => {\n // biome-ignore lint/style/noNonNullAssertion: every item came from `ranked`\n const ma = meta.get(a.slug)!;\n // biome-ignore lint/style/noNonNullAssertion: every item came from `ranked`\n const mb = meta.get(b.slug)!;\n return ma.order - mb.order || ma.index - mb.index;\n });\n }\n\n // Order groups: explicit groupOrder first (in that order, only if present),\n // then remaining groups by first appearance.\n const orderedTitles = [\n ...groupOrder.filter((title) => groups.has(title)),\n ...appearance.filter((title) => !groupOrder.includes(title)),\n ];\n\n return orderedTitles.map((title) => ({\n title,\n // biome-ignore lint/style/noNonNullAssertion: orderedTitles is derived from groups' keys\n items: groups.get(title)!,\n }));\n}\n","// Copyright (c) 2026 BowenLabs. All rights reserved.\n// Cadmus is MIT licensed. See LICENSE in the repo root.\n\n/**\n * Visual editing / click-to-edit (issue #15) — adopts Sanity's\n * Presentation/visual-editing idea (pattern, not code): the rendered page\n * (in a preview context) tags editable regions with the source field they\n * came from, and an overlay turns those regions into click targets that tell\n * the studio which field to focus.\n *\n * This module ships the two reusable, framework-agnostic primitives:\n * 1. **Encoding** — `editAttr({ collection, id, field })` produces a data\n * attribute the server renderer spreads onto an element; `decodeEditRef`\n * reads it back. Pure, testable.\n * 2. **Overlay** — `mountVisualEditing()` (browser-only; references `document`\n * lazily, so importing it server-side is harmless) highlights tagged\n * elements on hover and, on click, calls `onSelect` and `postMessage`s the\n * ref to the parent window (the studio shell hosting the preview iframe).\n *\n * The studio side listens for that message and navigates to\n * `/admin/<collection>/<id>` (and may focus `<field>`); that wiring is\n * consumer-side and not prescribed here.\n */\n\n/** A reference from a rendered region back to the field that produced it. */\nexport interface EditRef {\n collection: string;\n id: number;\n field: string;\n}\n\n/** The data attribute editable regions are tagged with. */\nexport const EDIT_ATTR = \"data-cadmus-edit\";\n\n/** `postMessage` payload type for a click-to-edit selection. */\nexport const VISUAL_EDIT_MESSAGE = \"cadmus:visual-edit\";\n\nexport function encodeEditRef(ref: EditRef): string {\n return `${ref.collection}:${ref.id}:${ref.field}`;\n}\n\n/** Parse an {@link EditRef} string, or null if malformed. */\nexport function decodeEditRef(value: string): EditRef | null {\n const parts = value.split(\":\");\n if (parts.length !== 3) return null;\n const [collection, idRaw, field] = parts;\n const id = Number.parseInt(idRaw, 10);\n if (!collection || !field || !Number.isFinite(id)) return null;\n return { collection, id, field };\n}\n\n/**\n * Attribute object to spread onto a rendered element so the overlay can map\n * it back to its source field, e.g. `<h1 {...editAttr({collection:'pages',\n * id, field:'title'})}>`.\n */\nexport function editAttr(ref: EditRef): Record<string, string> {\n return { [EDIT_ATTR]: encodeEditRef(ref) };\n}\n\nexport interface VisualEditingMessage {\n type: typeof VISUAL_EDIT_MESSAGE;\n ref: EditRef;\n}\n\n// ---------------------------------------------------------------------------\n// Live preview (studio → preview): the reverse channel of click-to-edit. The\n// studio posts the in-progress form values into the preview iframe so tagged\n// text regions update as the client types. Structural edits (adding blocks)\n// aren't reflected — those need a full re-render — but text edits feel live.\n// ---------------------------------------------------------------------------\n\n/** `postMessage` type carrying in-progress field values into the preview. */\nexport const PREVIEW_VALUES_MESSAGE = \"cadmus:preview-values\";\n\nexport interface PreviewValuesMessage {\n type: typeof PREVIEW_VALUES_MESSAGE;\n /** Which document the values belong to — must match the preview's. */\n collection: string;\n id: number;\n /** Field key → current value (only string values patch text regions). */\n values: Record<string, unknown>;\n}\n\n/**\n * Patch tagged regions' text from in-progress field values. For each string\n * value, updates every `[data-cadmus-edit=\"collection:id:field\"]` element's\n * `textContent`. Pure (takes the root to search), so it's unit-testable\n * without a live preview window.\n */\nexport function applyPreviewValues(\n root: ParentNode,\n target: { collection: string; id: number },\n values: Record<string, unknown>,\n): void {\n for (const [field, value] of Object.entries(values)) {\n if (typeof value !== \"string\") continue;\n const attr = encodeEditRef({\n collection: target.collection,\n id: target.id,\n field,\n });\n for (const el of root.querySelectorAll(`[${EDIT_ATTR}=\"${attr}\"]`)) {\n el.textContent = value;\n }\n }\n}\n\nexport interface PreviewSyncOptions {\n /** The document this preview renders — messages for others are ignored. */\n collection: string;\n id: number;\n /** Where to search for tagged regions. Default `document`. */\n root?: ParentNode;\n /** Only accept messages from this origin (the studio). Default: any. */\n allowedOrigin?: string;\n}\n\n/**\n * Mount the live-preview receiver on a preview page (browser-only). Listens\n * for {@link PreviewValuesMessage} from the studio window and patches tagged\n * text regions via {@link applyPreviewValues}. Returns a cleanup function.\n */\nexport function mountPreviewSync(options: PreviewSyncOptions): () => void {\n const root = options.root ?? document;\n const handler = (event: MessageEvent) => {\n if (options.allowedOrigin && event.origin !== options.allowedOrigin) return;\n const data = event.data as Partial<PreviewValuesMessage> | null;\n if (data?.type !== PREVIEW_VALUES_MESSAGE) return;\n if (data.collection !== options.collection || data.id !== options.id) {\n return;\n }\n if (data.values) {\n applyPreviewValues(\n root,\n { collection: options.collection, id: options.id },\n data.values,\n );\n }\n };\n window.addEventListener(\"message\", handler);\n return () => window.removeEventListener(\"message\", handler);\n}\n\nexport interface VisualEditingOptions {\n /** Called with the decoded ref when an editable region is clicked. */\n onSelect?: (ref: EditRef, element: Element) => void;\n /**\n * Origin to `postMessage` the selection to the parent window. Default\n * `\"*\"`. Set to the studio origin in production.\n */\n targetOrigin?: string;\n /** Outline color for the hover highlight. Default a teal accent. */\n highlightColor?: string;\n}\n\n/**\n * Mount the click-to-edit overlay. Browser-only — call from a preview page's\n * client script. Highlights `[data-cadmus-edit]` elements on hover and, on\n * click, calls `onSelect` and posts a {@link VisualEditingMessage} to the\n * parent window. Returns a cleanup function that removes the listeners.\n */\nexport function mountVisualEditing(\n options: VisualEditingOptions = {},\n): () => void {\n const { onSelect, targetOrigin = \"*\", highlightColor = \"#56c6be\" } = options;\n\n const closest = (target: EventTarget | null): HTMLElement | null => {\n if (!(target instanceof Element)) return null;\n const el = target.closest(`[${EDIT_ATTR}]`);\n return el instanceof HTMLElement ? el : null;\n };\n\n let previous: { el: HTMLElement; outline: string } | null = null;\n const clearHighlight = () => {\n if (previous) {\n previous.el.style.outline = previous.outline;\n previous = null;\n }\n };\n\n const onOver = (event: Event) => {\n const el = closest(event.target);\n if (!el || el === previous?.el) return;\n clearHighlight();\n previous = { el, outline: el.style.outline };\n el.style.outline = `2px solid ${highlightColor}`;\n el.style.outlineOffset = \"2px\";\n el.style.cursor = \"pointer\";\n };\n\n const onClick = (event: Event) => {\n const el = closest(event.target);\n if (!el) return;\n const ref = decodeEditRef(el.getAttribute(EDIT_ATTR) ?? \"\");\n if (!ref) return;\n event.preventDefault();\n event.stopPropagation();\n onSelect?.(ref, el);\n const message: VisualEditingMessage = { type: VISUAL_EDIT_MESSAGE, ref };\n window.parent?.postMessage(message, targetOrigin);\n };\n\n document.addEventListener(\"mouseover\", onOver, true);\n document.addEventListener(\"click\", onClick, true);\n\n return () => {\n clearHighlight();\n document.removeEventListener(\"mouseover\", onOver, true);\n document.removeEventListener(\"click\", onClick, true);\n };\n}\n","// Copyright (c) 2026 BowenLabs. All rights reserved.\n// Cadmus is MIT licensed. See LICENSE in the repo root.\n//\n// @thebes/cadmus/cms — afterChange-style outbound webhooks. The\n// `afterChange` hook itself only enqueues (via `@thebes/cadmus/queues`'\n// `enqueue`) — it never calls `fetch()` directly, so a slow or down\n// receiving endpoint can't add latency to a write request or get lost on\n// a single failed attempt. `deliverWebhookMessage` is the consumer-side\n// counterpart: a separate queue consumer calls it per message, and\n// `processBatch` (queues/index.ts) turns a thrown delivery failure into a\n// retry, eventually landing in that queue's configured DLQ.\n\nimport { CadmusQueueError } from \"../errors.js\";\nimport { enqueue } from \"../queues/index.js\";\nimport type { CollectionHooks } from \"./types.js\";\n\nexport interface WebhookConfig {\n /** Endpoint this webhook POSTs to on every matching event. */\n url: string;\n /** Restricts delivery to these operations. Default: both. */\n events?: Array<\"create\" | \"update\">;\n /**\n * When set, every delivery carries an `X-Cadmus-Signature` header —\n * HMAC-SHA256 (hex) over the raw JSON body — so the receiver can verify\n * the payload actually came from this Cadmus instance.\n */\n secret?: string;\n}\n\n/** The shape enqueued by `createWebhookHook`, consumed by `deliverWebhookMessage`. */\nexport interface WebhookMessage {\n url: string;\n secret?: string;\n event: \"create\" | \"update\";\n doc: Record<string, unknown>;\n /** ms since epoch, included in the signed/delivered payload. */\n timestamp: number;\n}\n\n/**\n * Builds an `afterChange` hook that enqueues a `WebhookMessage` for every\n * matching write — append the result to a collection's\n * `hooks.afterChange` array. `queue` is whatever `Queue<WebhookMessage>`\n * binding the caller's Worker has configured for webhook dispatch (see\n * wrangler.jsonc's webhook queue producer binding).\n */\nexport function createWebhookHook(\n queue: Queue<WebhookMessage>,\n config: WebhookConfig,\n): NonNullable<CollectionHooks[\"afterChange\"]>[number] {\n return async ({ doc, operation }) => {\n if (config.events && !config.events.includes(operation)) return;\n await enqueue(queue, {\n url: config.url,\n secret: config.secret,\n event: operation,\n doc,\n timestamp: Date.now(),\n });\n };\n}\n\n// Defense-in-depth, not the primary control: `global_fetch_strictly_public`\n// (set in both Workers' wrangler.jsonc) already blocks `fetch()` to\n// private/reserved IP literals at the platform level, and `WEBHOOK_URL` is\n// operator-supplied config, not attacker input. This catches the case that\n// guard doesn't: a hostname that *resolves* to a private address (or a\n// non-HTTP(S) scheme) rather than being one literally, plus a clear error\n// instead of a platform-level network failure when a deploy is\n// misconfigured.\nconst BLOCKED_HOSTNAME_PATTERNS = [\n /^localhost$/i,\n /^127\\./,\n /^0\\.0\\.0\\.0$/,\n /^169\\.254\\./, // link-local, including the cloud-metadata address\n /^10\\./,\n /^172\\.(1[6-9]|2\\d|3[01])\\./,\n /^192\\.168\\./,\n /^\\[?::1\\]?$/,\n /^\\[?fc/i,\n /^\\[?fd/i,\n /^\\[?fe80/i,\n];\n\nfunction isAllowedWebhookUrl(url: string): boolean {\n let parsed: URL;\n try {\n parsed = new URL(url);\n } catch {\n return false;\n }\n if (parsed.protocol !== \"https:\" && parsed.protocol !== \"http:\") {\n return false;\n }\n return !BLOCKED_HOSTNAME_PATTERNS.some((pattern) =>\n pattern.test(parsed.hostname),\n );\n}\n\nasync function hmacSha256Hex(payload: string, secret: string): Promise<string> {\n const key = await crypto.subtle.importKey(\n \"raw\",\n new TextEncoder().encode(secret),\n { name: \"HMAC\", hash: \"SHA-256\" },\n false,\n [\"sign\"],\n );\n const signature = await crypto.subtle.sign(\n \"HMAC\",\n key,\n new TextEncoder().encode(payload),\n );\n return Array.from(new Uint8Array(signature), (b) =>\n b.toString(16).padStart(2, \"0\"),\n ).join(\"\");\n}\n\n/**\n * Delivers a single `WebhookMessage` via `fetch()`. Throws\n * `CadmusQueueError` on any non-2xx response or network failure — meant\n * to be called from inside `processBatch`'s handler, where a thrown error\n * becomes a `message.retry()`.\n */\nexport async function deliverWebhookMessage(\n message: WebhookMessage,\n): Promise<void> {\n if (!isAllowedWebhookUrl(message.url)) {\n throw new CadmusQueueError(\n `Webhook URL \"${message.url}\" is not allowed (must be http(s) and not target a private/reserved/loopback address)`,\n );\n }\n\n const body = JSON.stringify({\n event: message.event,\n doc: message.doc,\n timestamp: message.timestamp,\n });\n const headers: Record<string, string> = {\n \"Content-Type\": \"application/json\",\n };\n if (message.secret) {\n headers[\"X-Cadmus-Signature\"] = await hmacSha256Hex(body, message.secret);\n }\n\n let response: Response;\n try {\n response = await fetch(message.url, { method: \"POST\", headers, body });\n } catch (cause) {\n throw new CadmusQueueError(\n `Webhook delivery to \"${message.url}\" failed`,\n cause,\n );\n }\n if (!response.ok) {\n throw new CadmusQueueError(\n `Webhook delivery to \"${message.url}\" returned status ${response.status}`,\n );\n }\n}\n"],"mappings":"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"}
|