{"version":3,"file":"types-75KIqzL9.mjs","names":[],"sources":["../src/schema/types.ts"],"sourcesContent":["/**\n * Schema Registry Types\n *\n * These types represent the schema definitions stored in D1.\n * They are the source of truth for all collections and fields.\n */\n\n/**\n * Supported field types\n */\nexport type FieldType =\n\t| \"string\"\n\t| \"text\"\n\t| \"url\"\n\t| \"number\"\n\t| \"integer\"\n\t| \"boolean\"\n\t| \"datetime\"\n\t| \"select\"\n\t| \"multiSelect\"\n\t| \"portableText\"\n\t| \"image\"\n\t| \"file\"\n\t| \"reference\"\n\t| \"json\"\n\t| \"slug\"\n\t| \"repeater\";\n\n/**\n * Array of all field types for validation\n */\nexport const FIELD_TYPES: readonly FieldType[] = [\n\t\"string\",\n\t\"text\",\n\t\"url\",\n\t\"number\",\n\t\"integer\",\n\t\"boolean\",\n\t\"datetime\",\n\t\"select\",\n\t\"multiSelect\",\n\t\"portableText\",\n\t\"image\",\n\t\"file\",\n\t\"reference\",\n\t\"json\",\n\t\"slug\",\n\t\"repeater\",\n] as const;\n\n/** Scalar field types that can be backed by a content-list query index. */\nexport const INDEXABLE_FIELD_TYPES: ReadonlySet<FieldType> = new Set([\n\t\"string\",\n\t\"url\",\n\t\"number\",\n\t\"integer\",\n\t\"boolean\",\n\t\"datetime\",\n\t\"select\",\n\t\"reference\",\n\t\"slug\",\n]);\n\nexport function isIndexableFieldType(type: FieldType): boolean {\n\treturn INDEXABLE_FIELD_TYPES.has(type);\n}\n\n/**\n * SQLite column types that map from field types\n */\nexport type ColumnType = \"TEXT\" | \"REAL\" | \"INTEGER\" | \"JSON\";\n\n/**\n * Map field types to their SQLite column types\n */\nexport const FIELD_TYPE_TO_COLUMN: Record<FieldType, ColumnType> = {\n\tstring: \"TEXT\",\n\ttext: \"TEXT\",\n\tnumber: \"REAL\",\n\tinteger: \"INTEGER\",\n\tboolean: \"INTEGER\",\n\tdatetime: \"TEXT\",\n\tselect: \"TEXT\",\n\tmultiSelect: \"JSON\",\n\tportableText: \"JSON\",\n\timage: \"TEXT\",\n\tfile: \"TEXT\",\n\treference: \"TEXT\",\n\tjson: \"JSON\",\n\tslug: \"TEXT\",\n\turl: \"TEXT\",\n\trepeater: \"JSON\",\n};\n\n/**\n * Features a collection can support\n */\nexport type CollectionSupport =\n\t| \"drafts\"\n\t| \"revisions\"\n\t| \"preview\"\n\t| \"scheduling\"\n\t| \"search\"\n\t| \"seo\";\n\n/**\n * Sources for how a collection was created\n */\n/**\n * Where a collection's entries live: the database (default) or the site's\n * git repo as `content/<collection>/<slug>.json` (admin saves are commits).\n */\nexport type CollectionStorage = \"db\" | \"git\";\n\nexport type CollectionSource =\n\t| `template:${string}`\n\t| `import:${string}`\n\t| \"manual\"\n\t| \"discovered\"\n\t| \"seed\";\n\n/**\n * Validation rules for a field\n */\n/** Sub-field definition for repeater fields */\nexport interface RepeaterSubField {\n\tslug: string;\n\ttype:\n\t\t| \"string\"\n\t\t| \"text\"\n\t\t| \"url\"\n\t\t| \"number\"\n\t\t| \"integer\"\n\t\t| \"boolean\"\n\t\t| \"datetime\"\n\t\t| \"select\"\n\t\t| \"image\";\n\tlabel: string;\n\trequired?: boolean;\n\toptions?: string[]; // For select sub-fields\n}\n\n/** Allowed types for repeater sub-fields (no nesting, no complex types) */\nexport const REPEATER_SUB_FIELD_TYPES = [\n\t\"string\",\n\t\"text\",\n\t\"url\",\n\t\"number\",\n\t\"integer\",\n\t\"boolean\",\n\t\"datetime\",\n\t\"select\",\n\t\"image\",\n] as const;\n\nexport interface FieldValidation {\n\trequired?: boolean;\n\tmin?: number;\n\tmax?: number;\n\tminLength?: number;\n\tmaxLength?: number;\n\tpattern?: string;\n\toptions?: string[]; // For select/multiSelect\n\tsubFields?: RepeaterSubField[]; // For repeater fields\n\tminItems?: number; // For repeater fields\n\tmaxItems?: number; // For repeater fields\n\tallowedMimeTypes?: string[];\n}\n\n/**\n * Widget options for field rendering\n */\nexport interface FieldWidgetOptions {\n\trows?: number; // For textarea\n\tshowPreview?: boolean; // For image/file\n\tcollection?: string; // For reference - which collection to reference\n\tallowMultiple?: boolean; // For reference\n\t[key: string]: unknown;\n}\n\nexport const MAX_COLLECTION_LIST_COLUMNS = 4;\n\n/** Collection-level admin presentation options. */\nexport interface CollectionAdminConfig {\n\t/** Custom field slugs to show in the content list. */\n\tlistColumns?: string[];\n}\n\n/**\n * A collection definition\n */\nexport interface Collection {\n\tid: string;\n\tslug: string;\n\tlabel: string;\n\tlabelSingular?: string;\n\tdescription?: string;\n\ticon?: string;\n\tadmin?: CollectionAdminConfig;\n\tsupports: CollectionSupport[];\n\tsource?: CollectionSource;\n\tstorage: CollectionStorage;\n\t/** Whether this collection has SEO metadata fields enabled */\n\thasSeo: boolean;\n\t/** Field slug powering the admin list Title column. Defaults to the standard title display. */\n\ttitleField?: string;\n\t/** Field slug powering the admin list Date column. Must be a `datetime` field. Defaults to last-updated. */\n\tdateField?: string;\n\t/** URL pattern with {slug} placeholder (e.g. \"/{slug}\", \"/blog/{slug}\") */\n\turlPattern?: string;\n\t/** Whether published entries require a public slug. Defaults to true. */\n\troutable?: boolean;\n\t/**\n\t * Omit this collection's auto-generated entry from the admin sidebar.\n\t * The collection stays fully functional everywhere else (API, MCP, hooks,\n\t * direct `/content/:collection` URLs) — this only hides the nav link, so a\n\t * plugin that owns the collection can point editors at its own admin UI.\n\t */\n\thidden: boolean;\n\t/**\n\t * Explicit position in the admin sidebar. Collections with a `sortOrder`\n\t * come first, in ascending order; the rest keep the alphabetical-by-slug\n\t * order and follow. `undefined` means \"no explicit position\".\n\t */\n\tsortOrder?: number;\n\t/** Whether comments are enabled for this collection */\n\tcommentsEnabled: boolean;\n\t/** Moderation strategy: \"all\" | \"first_time\" | \"none\" */\n\tcommentsModeration: \"all\" | \"first_time\" | \"none\";\n\t/** Auto-close comments after N days. 0 = never close. */\n\tcommentsClosedAfterDays: number;\n\t/** Auto-approve comments from authenticated CMS users */\n\tcommentsAutoApproveUsers: boolean;\n\tcreatedAt: string;\n\tupdatedAt: string;\n}\n\n/**\n * A field definition\n */\nexport interface Field {\n\tid: string;\n\tcollectionId: string;\n\tslug: string;\n\tlabel: string;\n\ttype: FieldType;\n\tcolumnType: ColumnType;\n\trequired: boolean;\n\tunique: boolean;\n\tdefaultValue?: unknown;\n\tvalidation?: FieldValidation;\n\twidget?: string;\n\toptions?: FieldWidgetOptions;\n\tsortOrder: number;\n\tsearchable: boolean;\n\t/** Whether this field has a physical index for structured list queries. */\n\tindexed: boolean;\n\t/** Whether this field is translatable (default true). Non-translatable fields are synced across locales. */\n\ttranslatable: boolean;\n\tcreatedAt: string;\n}\n\n/**\n * Input for creating a collection\n */\nexport interface CreateCollectionInput {\n\tslug: string;\n\tlabel: string;\n\tlabelSingular?: string;\n\tdescription?: string;\n\ticon?: string;\n\tadmin?: CollectionAdminConfig;\n\tsupports?: CollectionSupport[];\n\tsource?: CollectionSource;\n\tstorage?: CollectionStorage;\n\turlPattern?: string;\n\troutable?: boolean;\n\thasSeo?: boolean;\n\t/** Omit the auto-generated admin sidebar entry (defaults to false) */\n\thidden?: boolean;\n\t/** Explicit admin sidebar position (omit for the alphabetical fallback) */\n\tsortOrder?: number | null;\n\tcommentsEnabled?: boolean;\n}\n\n/**\n * Input for updating a collection\n */\nexport interface UpdateCollectionInput {\n\tlabel?: string;\n\tlabelSingular?: string;\n\tdescription?: string;\n\ticon?: string;\n\tadmin?: CollectionAdminConfig;\n\tsupports?: CollectionSupport[];\n\tstorage?: CollectionStorage;\n\turlPattern?: string | null;\n\troutable?: boolean;\n\thasSeo?: boolean;\n\t/** Omit the auto-generated admin sidebar entry */\n\thidden?: boolean;\n\t/** Explicit admin sidebar position; `null` clears it back to alphabetical */\n\tsortOrder?: number | null;\n\tcommentsEnabled?: boolean;\n\tcommentsModeration?: \"all\" | \"first_time\" | \"none\";\n\tcommentsClosedAfterDays?: number;\n\tcommentsAutoApproveUsers?: boolean;\n\t/** Field slug for the Title column; `null`/`\"\"` clears back to the default. */\n\ttitleField?: string | null;\n\t/** Datetime field slug for the Date column; `null`/`\"\"` clears back to the default. */\n\tdateField?: string | null;\n}\n\n/**\n * Input for creating a field\n */\nexport interface CreateFieldInput {\n\tslug: string;\n\tlabel: string;\n\ttype: FieldType;\n\trequired?: boolean;\n\tunique?: boolean;\n\tdefaultValue?: unknown;\n\tvalidation?: FieldValidation | null;\n\twidget?: string;\n\toptions?: FieldWidgetOptions;\n\tsortOrder?: number;\n\t/** Whether this field should be indexed for search */\n\tsearchable?: boolean;\n\t/** Create a physical index for structured sorting. */\n\tindexed?: boolean;\n\t/** Whether this field is translatable (default true). Non-translatable fields are synced across locales. */\n\ttranslatable?: boolean;\n}\n\n/**\n * Input for updating a field\n */\nexport interface UpdateFieldInput {\n\tlabel?: string;\n\t/**\n\t * Change the field's type. Only storage-compatible text aliases (`string`,\n\t * `text`, and `slug`) can be changed in place. Other changes require an\n\t * explicit content migration. Omit to keep the current type.\n\t */\n\ttype?: FieldType;\n\trequired?: boolean;\n\tunique?: boolean;\n\tdefaultValue?: unknown;\n\tvalidation?: FieldValidation | null;\n\twidget?: string;\n\toptions?: FieldWidgetOptions;\n\tsortOrder?: number;\n\t/** Whether this field should be indexed for search */\n\tsearchable?: boolean;\n\t/** Create or remove the physical index used by structured sorting. */\n\tindexed?: boolean;\n\t/** Whether this field is translatable (default true). Non-translatable fields are synced across locales. */\n\ttranslatable?: boolean;\n}\n\n/**\n * A collection with its fields\n */\nexport interface CollectionWithFields extends Collection {\n\tfields: Field[];\n}\n\n/**\n * Reserved field slugs that cannot be used.\n *\n * Includes names reserved for runtime hydration (`terms`, `bylines`, `byline`)\n * so user-defined fields never shadow the auto-hydrated values on entry.data.\n */\nexport const RESERVED_FIELD_SLUGS = [\n\t\"id\",\n\t\"slug\",\n\t\"status\",\n\t\"author_id\",\n\t\"primary_byline_id\",\n\t\"created_at\",\n\t\"updated_at\",\n\t\"published_at\",\n\t\"scheduled_at\",\n\t\"deleted_at\",\n\t\"version\",\n\t\"live_revision_id\",\n\t\"draft_revision_id\",\n\t// Runtime-hydrated fields\n\t\"terms\",\n\t\"bylines\",\n\t\"byline\",\n];\n\n/**\n * Reserved collection slugs that cannot be used\n */\nexport const RESERVED_COLLECTION_SLUGS = [\n\t\"content\",\n\t\"media\",\n\t\"users\",\n\t\"revisions\",\n\t\"taxonomies\",\n\t\"options\",\n\t\"audit_logs\",\n\t// Shadowed by the static POST /schema/collections/reorder route: a\n\t// collection with this slug could never be addressed at its own URL.\n\t\"reorder\",\n];\n\n/**\n * Byline custom fields (Discussion #1174).\n *\n * Sites declare site-specific byline metadata (`job_title`, `pronouns`,\n * `twitter_handle`, `company`, …) without touching emdash core. Definitions\n * live in `_emdash_byline_fields`; values in either\n * `_emdash_byline_field_values` (translatable, keyed by `byline_id`) or\n * `_emdash_byline_field_group_values` (non-translatable, keyed by\n * `translation_group`). The per-field `translatable` flag decides which\n * value table is used. See migration 041.\n */\n\n/**\n * The five v1 field types supported on byline custom fields. Deliberately\n * narrower than the content `FieldType` union — bylines don't need\n * `portableText`, `reference`, `image`, etc. v2 may extend this; v1 keeps\n * the storage and UI surfaces small.\n */\nexport type BylineFieldType = \"string\" | \"text\" | \"url\" | \"boolean\" | \"select\";\n\nexport const BYLINE_FIELD_TYPES: readonly BylineFieldType[] = [\n\t\"string\",\n\t\"text\",\n\t\"url\",\n\t\"boolean\",\n\t\"select\",\n] as const;\n\n/**\n * Validation rules for a byline custom field. v1 only exposes `options`\n * (the choice list for `select` fields). The shape mirrors the content-\n * field convention so the admin UI patterns transfer.\n */\nexport interface BylineFieldValidation {\n\t/** Choices for `select`-type fields. Ignored for other types. */\n\toptions?: string[];\n}\n\n/**\n * Runtime shape of a registered byline custom field. Stored in\n * `_emdash_byline_fields` (see migration 041).\n */\nexport interface BylineFieldDefinition {\n\tid: string;\n\tslug: string;\n\tlabel: string;\n\ttype: BylineFieldType;\n\trequired: boolean;\n\t/**\n\t * Whether values are stored per-locale (`true`, in\n\t * `_emdash_byline_field_values` keyed by `byline_id`) or shared across\n\t * every locale variant of the same byline identity (`false`, in\n\t * `_emdash_byline_field_group_values` keyed by `translation_group`).\n\t * Defaults to `true` at the DB level.\n\t */\n\ttranslatable: boolean;\n\tvalidation: BylineFieldValidation | null;\n\tsortOrder: number;\n\tcreatedAt: string;\n\tupdatedAt: string;\n}\n\n/**\n * Input for creating a byline custom field. `slug` and `type` are not\n * updatable post-create — changing either would invalidate stored values.\n */\nexport interface CreateBylineFieldInput {\n\tslug: string;\n\tlabel: string;\n\ttype: BylineFieldType;\n\trequired?: boolean;\n\ttranslatable?: boolean;\n\tvalidation?: BylineFieldValidation | null;\n\tsortOrder?: number;\n}\n\n/**\n * Input for updating a byline custom field. `slug` and `type` are\n * intentionally not present — see `CreateBylineFieldInput`.\n */\nexport interface UpdateBylineFieldInput {\n\tlabel?: string;\n\trequired?: boolean;\n\ttranslatable?: boolean;\n\tvalidation?: BylineFieldValidation | null;\n\tsortOrder?: number;\n}\n\n/**\n * Runtime value type for a byline custom field. The narrow union mirrors\n * what the five v1 field types can produce: `string`/`text`/`url`/`select`\n * → string, `boolean` → boolean, plus `null` for cleared values.\n */\nexport type CustomFieldValue = string | boolean | null;\n\n/**\n * Reserved byline-field slugs. Two reasons a slug ends up here:\n *\n * 1. **Column collision.** Slugs that match a fixed column on\n *    `_emdash_bylines` (migrations 031 + 040) would shadow that column\n *    on hydration. The first 12 entries cover this.\n * 2. **Route collision.** Static file routes under\n *    `/_emdash/api/admin/byline-fields/` take precedence over the\n *    `[slug].ts` dynamic route in Astro, so a custom field whose slug\n *    matches a sibling static file (e.g. `reorder.ts`) is unreachable\n *    via single-field CRUD — the static route handles only its own\n *    method (POST for `reorder`) and 405s everything else.\n *    `reorder` is the only such sibling today; new sibling routes\n *    (e.g. a hypothetical `import.ts`) must be added here.\n *    `[slug]/usage.ts` lives a level deeper so a slug of `usage` does\n *    not collide — it resolves cleanly to `[slug].ts`.\n *\n * Enforced at the registry layer (Phase 2) and the admin API zod layer\n * (Phase 4) so non-HTTP callers (seeds, scripts) get the same guarantee.\n */\nexport const RESERVED_BYLINE_FIELD_SLUGS = [\n\t// 1. Column-collision slugs (matches `_emdash_bylines` fixed columns).\n\t\"id\",\n\t\"slug\",\n\t\"display_name\",\n\t\"bio\",\n\t\"avatar_media_id\",\n\t\"website_url\",\n\t\"user_id\",\n\t\"is_guest\",\n\t\"locale\",\n\t\"translation_group\",\n\t\"created_at\",\n\t\"updated_at\",\n\t// 2. Route-collision slugs (matches static sibling files of `[slug].ts`).\n\t\"reorder\",\n] as const;\n"],"mappings":";;;;AA+BA,MAAa,cAAoC;CAChD;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;;AAGD,MAAa,wBAAgD,IAAI,IAAI;CACpE;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA,CAAC;AAEF,SAAgB,qBAAqB,MAA0B;AAC9D,QAAO,sBAAsB,IAAI,KAAK;;;;;AAWvC,MAAa,uBAAsD;CAClE,QAAQ;CACR,MAAM;CACN,QAAQ;CACR,SAAS;CACT,SAAS;CACT,UAAU;CACV,QAAQ;CACR,aAAa;CACb,cAAc;CACd,OAAO;CACP,MAAM;CACN,WAAW;CACX,MAAM;CACN,MAAM;CACN,KAAK;CACL,UAAU;CACV;AAwFD,MAAa,8BAA8B;;;;;;;AAkM3C,MAAa,uBAAuB;CACnC;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CAEA;CACA;CACA;CACA;;;;AAKD,MAAa,4BAA4B;CACxC;CACA;CACA;CACA;CACA;CACA;CACA;CAGA;CACA;AAsBD,MAAa,qBAAiD;CAC7D;CACA;CACA;CACA;CACA;CACA;;;;;;;;;;;;;;;;;;;;;AAyFD,MAAa,8BAA8B;CAE1C;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CAEA;CACA"}