import type { Kysely } from 'kysely'; import type { Database, SnippetKind } from '../db/schema.js'; /** * Reusable text snippets — a value defined once and used in prose everywhere. * * Modelled on `reusableBlocks.ts`, which solves the same problem one size up: a reusable block owns * a region of a page, a snippet owns a value inside a sentence. Everything here that looks like a * copy of that file is a deliberate one, because the two features have to behave the same way where * they overlap — most importantly around deletion and the cache stamp. */ export interface Snippet { id: string; api_id: string; name: string; description: string | null; kind: SnippetKind; value: string; display: string | null; created_at: string; updated_at: string; } /** What a page's payload carries for each snippet it uses. */ export interface ResolvedSnippet { kind: SnippetKind; /** Canonical: the string, the bare number, or an ISO date. What a chart plots. */ value: string; /** What prose substitutes. Derived from `value` when the row sets no override. */ display: string; } /** * How a snippet reads in a sentence. * * **The one place the CMS makes a formatting decision, and it is forced**: substituting into prose * has to produce *some* string, and `4500` in the middle of "Tuition is 4500 per year" is not what * anybody means. An editor who wants something else sets `display` and this defers to it entirely. * * `en-US` rather than a configurable locale, deliberately for now. A locale setting is a real * feature with a real surface — it would want to reach dates in the admin, the delivery payload and * the consumer — and inventing half of it here, readable by nothing else, is how a setting ends up * configured and unread. `display` is the escape hatch until that exists. */ export declare function renderSnippet(snippet: Pick): string; export declare function toResolved(snippet: Snippet): ResolvedSnippet; export declare function listSnippets(db: Kysely): Promise; export declare function getSnippet(db: Kysely, id: string): Promise; /** * Load snippets by `api_id`, which is how content names them. * * One query for however many a page uses — the "cost is per page, not per item" rule * `resolveDelivery` follows for media and terms. An empty list short-circuits rather than sending * `in ()`, which is a syntax error; the same trap `listMedia` documents. */ export declare function snippetsByApiId(db: Kysely, apiIds: string[]): Promise>; export interface SnippetInput { api_id: string; name: string; description?: string | null; kind: SnippetKind; value: string; display?: string | null; } export declare function createSnippet(db: Kysely, input: SnippetInput): Promise; /** * Update everything except `api_id`. * * **The omission is the point.** `api_id` is what every stored `{{ token }}` names, so changing it * silently breaks content that no screen would show as broken. It is excluded from the input type * rather than ignored at runtime, so a caller trying to change it fails to compile — the same shape * as `status` being excluded from `countItemsByStatus`'s filters. */ export declare function updateSnippet(db: Kysely, id: string, input: Partial>): Promise; /** * How many content items refer to this snippet. * * A `LIKE` over the `data` blob, which is what `countBlockUsage` already does for block types and is * acceptable for the same reason: it runs when somebody is about to delete something, not on any * read path. * * The pattern is `{{`-and-the-name rather than the exact rendered token, because whitespace inside * the braces is optional — `{{tuition}}` and `{{ tuition }}` are the same reference. That makes this * an over-count in one direction only: a page whose prose happens to contain `{{ tuition` without a * closing brace would be counted. Refusing a delete that would in fact have been safe is the * survivable error here; permitting one that breaks a live page is not. */ export declare function countSnippetUsage(db: Kysely, apiId: string): Promise; /** * Delete, refused while anything still refers to it. * * The same rule reusable blocks enforce, for the same reason: a reference with no target renders as * a gap on exactly the pages nobody is watching. Here it is slightly worse — an unresolved token * leaves visible `{{ tuition }}` braces in a sentence — which is discoverable, but discovering it on * a live page is not the plan. */ export declare function deleteSnippet(db: Kysely, id: string): Promise<{ deleted: boolean; blocker?: string; }>; /** * A stamp that changes whenever any snippet does, for the delivery ETag. * * Identical in shape and purpose to `reusableBlockLibraryVersion`, and for the identical reason: * a page's own `updated_at` does not move when a snippet it uses is edited, so a validator built * from the page alone answers 304 forever — and per RFC 9111 §4.3.4 a 304 *refreshes* the stored * copy's freshness, so the staleness is unbounded rather than capped by the TTL. That bug has been * shipped here once already. * * Over-broad in the same way `SITE_TAG` is: editing one snippet invalidates every page's validator. * That is rare by construction and costs a revalidation rather than a re-render. * * Returns `0` for an empty table so the stamp is a stable number rather than sometimes absent — a * validator that changes shape when the first row is created would invalidate every page once, for * nothing. */ export declare function snippetLibraryVersion(db: Kysely): Promise; /** Whether any of an item's stored values carries a token at all — the cheap pre-check. */ export declare function dataHasSnippetTokens(value: unknown): boolean;