import { DataStore } from '@voltro/database'; import { Effect } from 'effect'; import { ReactNode } from 'react'; import { Row } from '@voltro/database'; import { Schema as Schema_2 } from 'effect'; import { TableLike } from '@voltro/database'; /** * Compute every `derivedFrom` field from its source value. Returns a NEW * row — the input is not mutated. A caller-provided value for a derived * field is OVERWRITTEN (derived values are the content type's opinion, * mirroring the column `.computed()` semantics). Derivations run in * declaration order against the progressively-updated row, so a derived * field may source another, earlier-declared derived field. */ export declare const applyDerivations: (type: Pick, row: Readonly>) => Record; /** * Archive a content row: set `status: 'archived'` on the draft AND the * published copy (whichever exist). Archived rows stay in their tables * but disappear from the read surface (`ctx.cms` excludes `archived`). * Atomic per row. Throws `ContentNotFound` when the id exists in neither * table within the caller's scope. */ export declare const archive: (store: DataStore, type: ContentType, id: string) => Promise; /** Effect-native `archive` (see {@link saveDraftEffect}). */ export declare const archiveEffect: (store: DataStore, type: ContentType, id: string) => Effect.Effect; /** The CMS read surface attached to `ctx.cms` (and the standalone client). */ export declare interface CmsApi { /** Open a query over a content type's PUBLISHED rows (archived excluded). */ contentType(name: string): ContentQuery; /** * Resolve a single row honouring a preview token. With a valid token * for `{type, id}`, reads the DRAFT row; otherwise reads the published * row by id. Returns null when nothing matches / the token is invalid. */ preview(type: string, id: string, token: string | undefined): Promise; } /** * Build the `ctx.cms` surface over a request-scoped store. Attach the * result to your `AppContext` slot (or read it via `useCms(ctx)`). */ export declare const cmsContext: (store: DataStore, types: ReadonlyArray, options?: { secret?: string; }) => CmsApi; export declare interface CmsRestOptions { readonly cms: CmsApi; readonly types: ReadonlyArray; /** * API keys allowed to read. A key may be scoped to specific content * types; `'*'` (or omitting `types`) grants all. Reads * `Authorization: Bearer ` or `x-api-key: `. */ readonly apiKeys: ReadonlyArray<{ key: string; types?: ReadonlyArray; }>; /** Default page size when `?limit` is absent. */ readonly defaultLimit?: number; /** Maximum page size — `?limit` is clamped to this. */ readonly maxLimit?: number; } /** A minimal HTTP request the REST handler reads. */ export declare interface CmsRestRequest { readonly method: string; /** Path AFTER the mount prefix, e.g. `/v1/cms/blogPost` or `/v1/cms/blogPost/my-slug`. */ readonly path: string; readonly headers: Readonly>; readonly query?: Readonly>; } /** The JSON response the handler returns. */ export declare interface CmsRestResponse { readonly status: number; readonly body: unknown; } /** Everything the write pipeline can fail with on the typed channel. */ export declare type CmsWriteError = ContentValidationFailed | ContentNotFound | ContentIdConflict; /** The lifecycle states a content row moves through. */ export declare const CONTENT_STATUSES: readonly ["draft", "published", "archived"]; /** The pair of tables a content type compiles to. */ export declare interface ContentEntities { readonly draft: TableLike; readonly published: TableLike; } /** * Auto-generated content form. Renders one labelled widget per field, * driven entirely by each field's `editorHint`. Stateless — the caller * owns `value` + `onChange`. */ export declare const ContentForm: (props: ContentFormProps) => ReactNode; export declare interface ContentFormProps { readonly contentType: ContentFormType; readonly value: ContentValue; readonly onChange: (next: ContentValue) => void; /** Override the widget for one or more editor hints (e.g. a TipTap richText). */ readonly widgets?: Partial>; } /** The content-type shape ContentForm reads (structural — avoids a value import). */ export declare interface ContentFormType { readonly fields: Readonly>; } /** * Raised by `saveDraft` when the caller supplies an explicit `id` that * already exists OUTSIDE the caller's scope (another tenant's row). * Refusing here keeps a caller-pinned id from ever updating — or, on a * store without DB-enforced PKs, overwriting — a foreign row. */ export declare class ContentIdConflict extends ContentIdConflict_base { } declare const ContentIdConflict_base: Schema_2.TaggedErrorClass; } & { contentType: typeof Schema_2.String; id: typeof Schema_2.String; }>; /** * Raised by `publish` / `unpublish` / `archive` when the row id doesn't * exist in the caller's scope. On a tenant-scoped request store this is * ALSO the cross-tenant answer: another tenant's row is indistinguishable * from a missing one (the scoped read never sees it). */ export declare class ContentNotFound extends ContentNotFound_base { } declare const ContentNotFound_base: Schema_2.TaggedErrorClass; } & { contentType: typeof Schema_2.String; id: typeof Schema_2.String; /** Which lifecycle table the id was looked up in. */ stage: Schema_2.Literal<["draft", "published"]>; }>; /** A chainable read over one content type's published rows. */ export declare interface ContentQuery { where(field: string, op: WhereOp, value: unknown): ContentQuery; orderBy(field: string, dir?: 'asc' | 'desc'): ContentQuery; limit(n: number): ContentQuery; offset(n: number): ContentQuery; /** Resolve all matching rows. */ all(): Promise>; /** Resolve the first matching row, or null. */ one(): Promise; } export declare type ContentStatus = (typeof CONTENT_STATUSES)[number]; /** A registered content type. */ export declare interface ContentType { readonly name: string; readonly displayName: string; readonly pluralName: string; readonly fields: Readonly>; readonly list?: ListConfig; readonly idPrefix?: string; /** Declared ISR revalidations fired on publish/unpublish — see * `ContentTypeSpec.revalidate`. */ readonly revalidate?: ContentTypeSpec['revalidate']; /** Field names whose value is computed-on-save (derivedFrom). */ readonly derivedFields: ReadonlyArray; /** Field names marked unique-per-tenant (composite `(tenantId, field)` UNIQUE). */ readonly uniqueFields: ReadonlyArray; } /** The user-authored spec passed to `defineContentType`. */ export declare interface ContentTypeSpec { readonly name: string; readonly displayName: string; readonly pluralName: string; readonly fields: Readonly>; readonly list?: ListConfig; /** Override the TypeID prefix for awkward plurals / long names. */ readonly idPrefix?: string; /** * ISR routes to drop when content of this type is published or * unpublished — `publish()`/`unpublish()` call `revalidatePath` / * `revalidateTag` (from `@voltro/runtime`) for each entry after the * write commits, reaching every `voltro start` replica. * * You DON'T need this on postgres for the plain publish case: a route * declaring `cacheInvalidatesOn: ['_published']` is already dropped * by CDC when the published table changes. Declare routes here for the * cases CDC can't cover — non-postgres dialects, pattern purges of routes * whose loader reads this content indirectly, or tag fanout. */ readonly revalidate?: { /** Route patterns (`/blog/[slug]`) or concrete paths (`/pricing`). */ readonly paths?: ReadonlyArray; /** Tags matched against routes' `cacheInvalidatesOn` entries. */ readonly tags?: ReadonlyArray; }; } /** * Compile a content type to its `_drafts` + `_published` * tables. Drafts hold in-progress edits; publishing copies a row to the * published table (the one the rest of the app reads). Both carry the * lifecycle `status` column + audit timestamps and are reactive so * a publish wakes live consumer subscriptions. The draft table also * carries a nullable `publishAt` (scheduled publishing). A field marked * `unique()` adds a composite `(tenantId, field)` DB UNIQUE to BOTH * tables — per-tenant uniqueness enforced by the database. */ export declare const contentTypeToEntities: (ct: ContentType) => ContentEntities; /** * Raised by `validateContent` / `saveDraft` when a row violates the * content type's field rules (pattern / maxLength / typed kinds / * literal sets / rich-text content policy). Carries EVERY violation, * per field, so an editor can annotate the whole form in one pass. */ export declare class ContentValidationFailed extends ContentValidationFailed_base { } declare const ContentValidationFailed_base: Schema_2.TaggedErrorClass; } & { contentType: typeof Schema_2.String; violations: Schema_2.Array$>; }>; export declare type ContentValue = Record; /** * Collect every save-time rule violation for `row` against `type` — * required presence, per-kind type checks, `maxLength`, `pattern`, * `Literal` value sets, and the rich-text content policy * (`allowImages` / `allowEmbeds: false` reject image/embed nodes or the * `` / `