{"version":3,"file":"registry-CYfo6zb6.mjs","names":[],"sources":["../src/media/usage/activation.ts","../src/schema/registry.ts"],"sourcesContent":["import { sql, type Kysely, type RawBuilder, type Selectable } from \"kysely\";\nimport { ulid } from \"ulidx\";\n\nimport { isPostgres, tableExists } from \"../../database/dialect-helpers.js\";\nimport type { Database, MediaUsageActivationTable } from \"../../database/types.js\";\nimport {\n\tinstallMediaUsageCaptureTriggers,\n\tverifyMediaUsageCaptureTriggers,\n} from \"./capture-triggers.js\";\n\nconst ACTIVATION_KEY = \"incremental_capture\";\nconst ACTIVATION_ERROR_CODE = \"MEDIA_USAGE_ACTIVATION_FAILED\";\nexport const MEDIA_USAGE_ACTIVATION_RUNTIME_GENERATION = 1;\n\nexport const MEDIA_USAGE_ACTIVATION_LIMITS = Object.freeze({\n\tcollectionsPerCall: 1,\n\tleaseDurationSeconds: 5 * 60,\n});\n\nexport type MediaUsageActivationResult =\n\t| { outcome: \"active\"; processedCollections: number }\n\t| {\n\t\t\toutcome: \"activating\";\n\t\t\tprocessedCollections: number;\n\t\t\tcollectionCursor: string | null;\n\t  }\n\t| { outcome: \"lease_active\"; leaseExpiresAt: string }\n\t| { outcome: \"conflict\"; processedCollections: number };\n\nexport interface MediaUsageCollectionCapturePreparation {\n\tcaptureRequired: boolean;\n\tcollectionId: string;\n\tregistrationExists: boolean;\n\tresuming: boolean;\n}\n\nexport async function canResumeMediaUsageCollectionCapture(\n\tdb: Kysely<Database>,\n\tidentity: { collectionId: string; collectionSlug: string; creationFingerprint?: string },\n): Promise<boolean> {\n\tconst activation = await findActivationIfAvailable(db);\n\tif (!activation) return false;\n\tassertRuntimeGeneration(activation);\n\tif (activation.state !== \"active\") return false;\n\n\tconst lifecycle = await db\n\t\t.selectFrom(\"_emdash_media_usage_index_status\")\n\t\t.select([\"collection_id\", \"cursor\"])\n\t\t.where(\"adapter_id\", \"=\", \"content-media\")\n\t\t.where(\"scope_type\", \"=\", \"collection\")\n\t\t.where(\"scope_key\", \"=\", identity.collectionSlug)\n\t\t.where(\"collection_id\", \"=\", identity.collectionId)\n\t\t.where(\"capture_state\", \"in\", [\"installing\", \"ready\"])\n\t\t.executeTakeFirst();\n\treturn (\n\t\tlifecycle?.collection_id === identity.collectionId &&\n\t\t(identity.creationFingerprint\n\t\t\t? lifecycle.cursor === identity.creationFingerprint\n\t\t\t: lifecycle.cursor === null)\n\t);\n}\n\nexport async function prepareMediaUsageCollectionCapture(\n\tdb: Kysely<Database>,\n\tinput: {\n\t\tcollectionId: string;\n\t\tcollectionSlug: string;\n\t\tcreationFingerprint?: string;\n\t\tregisteredCollectionId?: string;\n\t},\n): Promise<MediaUsageCollectionCapturePreparation> {\n\tconst activation = await findActivationIfAvailable(db);\n\tif (!activation) {\n\t\treturn {\n\t\t\tcaptureRequired: false,\n\t\t\tcollectionId: input.collectionId,\n\t\t\tregistrationExists: input.registeredCollectionId !== undefined,\n\t\t\tresuming: false,\n\t\t};\n\t}\n\tassertRuntimeGeneration(activation);\n\tif (activation.state !== \"active\") {\n\t\treturn {\n\t\t\tcaptureRequired: false,\n\t\t\tcollectionId: input.collectionId,\n\t\t\tregistrationExists: input.registeredCollectionId !== undefined,\n\t\t\tresuming: false,\n\t\t};\n\t}\n\n\tconst existing = await db\n\t\t.selectFrom(\"_emdash_media_usage_index_status\")\n\t\t.select([\"collection_id\", \"capture_state\", \"cursor\"])\n\t\t.where(\"adapter_id\", \"=\", \"content-media\")\n\t\t.where(\"scope_type\", \"=\", \"collection\")\n\t\t.where(\"scope_key\", \"=\", input.collectionSlug)\n\t\t.executeTakeFirst();\n\tif (existing) {\n\t\tif (\n\t\t\texisting.collection_id &&\n\t\t\t(existing.capture_state === \"installing\" || existing.capture_state === \"ready\") &&\n\t\t\t(input.creationFingerprint\n\t\t\t\t? existing.cursor === input.creationFingerprint\n\t\t\t\t: existing.cursor === null) &&\n\t\t\t(input.registeredCollectionId === undefined ||\n\t\t\t\tinput.registeredCollectionId === existing.collection_id)\n\t\t) {\n\t\t\treturn {\n\t\t\t\tcaptureRequired: true,\n\t\t\t\tcollectionId: existing.collection_id,\n\t\t\t\tregistrationExists: input.registeredCollectionId !== undefined,\n\t\t\t\tresuming: true,\n\t\t\t};\n\t\t}\n\t\tthrow new Error(\"Media usage collection lifecycle identity conflict\");\n\t}\n\tif (input.registeredCollectionId !== undefined) {\n\t\tthrow new Error(\"Media usage collection lifecycle is missing\");\n\t}\n\n\tawait db\n\t\t.insertInto(\"_emdash_media_usage_index_status\")\n\t\t.values({\n\t\t\tadapter_id: \"content-media\",\n\t\t\tscope_type: \"collection\",\n\t\t\tscope_key: input.collectionSlug,\n\t\t\tstatus: \"never\",\n\t\t\tcollection_id: input.collectionId,\n\t\t\treconciliation_required: 1,\n\t\t\tcapture_state: \"installing\",\n\t\t\tcursor: input.creationFingerprint ?? null,\n\t\t\tupdated_at: timestampOffset(db, 0),\n\t\t})\n\t\t.execute();\n\treturn {\n\t\tcaptureRequired: true,\n\t\tcollectionId: input.collectionId,\n\t\tregistrationExists: false,\n\t\tresuming: false,\n\t};\n}\n\nexport async function installPreparedMediaUsageCollectionCapture(\n\tdb: Kysely<Database>,\n\tidentity: { collectionId: string; collectionSlug: string },\n): Promise<void> {\n\tawait installMediaUsageCaptureTriggers(db, identity, { replaceExisting: false });\n\tif (!(await verifyMediaUsageCaptureTriggers(db, identity))) {\n\t\tthrow new Error(\"Media usage capture trigger verification failed\");\n\t}\n}\n\nexport async function markMediaUsageCollectionCaptureReady(\n\tdb: Kysely<Database>,\n\tidentity: { collectionId: string; collectionSlug: string },\n): Promise<void> {\n\tconst result = await db\n\t\t.updateTable(\"_emdash_media_usage_index_status\")\n\t\t.set({ capture_state: \"ready\", updated_at: timestampOffset(db, 0) })\n\t\t.where(\"adapter_id\", \"=\", \"content-media\")\n\t\t.where(\"scope_type\", \"=\", \"collection\")\n\t\t.where(\"scope_key\", \"=\", identity.collectionSlug)\n\t\t.where(\"collection_id\", \"=\", identity.collectionId)\n\t\t.where(\"capture_state\", \"in\", [\"installing\", \"ready\"])\n\t\t.where(\n\t\t\tsql<boolean>`EXISTS (\n\t\t\t\tSELECT 1 FROM _emdash_media_usage_activation AS activation\n\t\t\t\tWHERE activation.task_key = ${ACTIVATION_KEY}\n\t\t\t\t\tAND activation.state = 'active'\n\t\t\t\t\tAND activation.runtime_generation = ${MEDIA_USAGE_ACTIVATION_RUNTIME_GENERATION}\n\t\t\t)`,\n\t\t)\n\t\t.executeTakeFirst();\n\tif (Number(result.numUpdatedRows ?? 0) === 0) {\n\t\tthrow new Error(\"Media usage collection readiness lost its fence\");\n\t}\n}\n\nexport async function finalizeMediaUsageCollectionCapture(\n\tdb: Kysely<Database>,\n\tidentity: { collectionId: string; collectionSlug: string },\n): Promise<void> {\n\tconst result = await db\n\t\t.updateTable(\"_emdash_media_usage_index_status\")\n\t\t.set({ capture_state: \"active\", cursor: null, updated_at: timestampOffset(db, 0) })\n\t\t.where(\"adapter_id\", \"=\", \"content-media\")\n\t\t.where(\"scope_type\", \"=\", \"collection\")\n\t\t.where(\"scope_key\", \"=\", identity.collectionSlug)\n\t\t.where(\"collection_id\", \"=\", identity.collectionId)\n\t\t.where(\"capture_state\", \"=\", \"ready\")\n\t\t.where(\n\t\t\tsql<boolean>`EXISTS (\n\t\t\t\tSELECT 1 FROM _emdash_collections AS collection\n\t\t\t\tWHERE collection.id = ${identity.collectionId}\n\t\t\t\t\tAND collection.slug = ${identity.collectionSlug}\n\t\t\t)`,\n\t\t)\n\t\t.where(\n\t\t\tsql<boolean>`EXISTS (\n\t\t\t\tSELECT 1 FROM _emdash_media_usage_activation AS activation\n\t\t\t\tWHERE activation.task_key = ${ACTIVATION_KEY}\n\t\t\t\t\tAND activation.state = 'active'\n\t\t\t\t\tAND activation.runtime_generation = ${MEDIA_USAGE_ACTIVATION_RUNTIME_GENERATION}\n\t\t\t)`,\n\t\t)\n\t\t.executeTakeFirst();\n\tif (Number(result.numUpdatedRows ?? 0) === 0) {\n\t\tthrow new Error(\"Media usage collection activation lost its fence\");\n\t}\n}\n\nexport async function activateMediaUsageCapture(\n\tdb: Kysely<Database>,\n\tinput: { writersDrained: true },\n): Promise<MediaUsageActivationResult> {\n\tif (input.writersDrained !== true) {\n\t\tthrow new Error(\"Media usage activation requires confirmation that writers are drained\");\n\t}\n\n\tconst before = await findActivation(db);\n\tassertRuntimeGeneration(before);\n\tif (before.state === \"active\") {\n\t\treturn { outcome: \"active\", processedCollections: 0 };\n\t}\n\n\tconst leaseToken = ulid();\n\tconst lease = await claimActivation(db, leaseToken);\n\tif (!lease) return activationClaimLoss(db);\n\n\tlet processedCollections = 0;\n\ttry {\n\t\tconst candidates = await findActivationCandidates(db, lease.collection_cursor);\n\t\tfor (const collection of candidates.slice(\n\t\t\t0,\n\t\t\tMEDIA_USAGE_ACTIVATION_LIMITS.collectionsPerCall,\n\t\t)) {\n\t\t\tawait requireActivationLease(db, leaseToken);\n\t\t\tawait prepareCollectionForActivation(db, collection, leaseToken);\n\t\t\tawait requireActivationLease(db, leaseToken);\n\t\t\tawait installMediaUsageCaptureTriggers(\n\t\t\t\tdb,\n\t\t\t\t{\n\t\t\t\t\tcollectionId: collection.id,\n\t\t\t\t\tcollectionSlug: collection.slug,\n\t\t\t\t},\n\t\t\t\t{ replaceExisting: false },\n\t\t\t);\n\t\t\tawait requireActivationLease(db, leaseToken);\n\t\t\tif (\n\t\t\t\t!(await verifyMediaUsageCaptureTriggers(db, {\n\t\t\t\t\tcollectionId: collection.id,\n\t\t\t\t\tcollectionSlug: collection.slug,\n\t\t\t\t}))\n\t\t\t) {\n\t\t\t\tthrow new Error(\"Media usage capture trigger verification failed\");\n\t\t\t}\n\t\t\tif (!(await activateCollectionLifecycle(db, collection, leaseToken))) {\n\t\t\t\tthrow new Error(\"Media usage collection activation lost its fence\");\n\t\t\t}\n\t\t\tprocessedCollections++;\n\t\t}\n\n\t\tconst collectionCursor =\n\t\t\tcandidates[Math.min(processedCollections, candidates.length) - 1]?.slug ??\n\t\t\tlease.collection_cursor;\n\t\tif (candidates.length > MEDIA_USAGE_ACTIVATION_LIMITS.collectionsPerCall) {\n\t\t\tif (!(await releaseActivationBatch(db, leaseToken, collectionCursor))) {\n\t\t\t\tthrow new Error(\"Media usage activation cursor lost its fence\");\n\t\t\t}\n\t\t\treturn { outcome: \"activating\", processedCollections, collectionCursor };\n\t\t}\n\n\t\tconst incomplete = await findIncompleteCollection(db);\n\t\tif (incomplete) {\n\t\t\tif (!(await releaseActivationBatch(db, leaseToken, null))) {\n\t\t\t\tthrow new Error(\"Media usage activation restart lost its fence\");\n\t\t\t}\n\t\t\treturn { outcome: \"activating\", processedCollections, collectionCursor: null };\n\t\t}\n\n\t\tif (!(await finalizeActivation(db, leaseToken, collectionCursor))) {\n\t\t\tthrow new Error(\"Media usage activation finalization lost its fence\");\n\t\t}\n\t\treturn { outcome: \"active\", processedCollections };\n\t} catch (error) {\n\t\tif (!(await activationLeaseIsLive(db, leaseToken))) {\n\t\t\treturn { outcome: \"conflict\", processedCollections };\n\t\t}\n\t\tawait recordActivationFailure(db, leaseToken);\n\t\tthrow new Error(\"Media usage activation failed\", { cause: error });\n\t}\n}\n\nasync function findActivation(\n\tdb: Kysely<Database>,\n): Promise<Selectable<MediaUsageActivationTable>> {\n\treturn db\n\t\t.selectFrom(\"_emdash_media_usage_activation\")\n\t\t.selectAll()\n\t\t.where(\"task_key\", \"=\", ACTIVATION_KEY)\n\t\t.executeTakeFirstOrThrow();\n}\n\nasync function findActivationIfAvailable(\n\tdb: Kysely<Database>,\n): Promise<Selectable<MediaUsageActivationTable> | null> {\n\tif (!(await tableExists(db, \"_emdash_media_usage_activation\"))) return null;\n\treturn findActivation(db);\n}\n\nfunction assertRuntimeGeneration(activation: Selectable<MediaUsageActivationTable>): void {\n\tif (activation.runtime_generation !== MEDIA_USAGE_ACTIVATION_RUNTIME_GENERATION) {\n\t\tthrow new Error(\"Media usage activation runtime generation mismatch\");\n\t}\n}\n\nasync function claimActivation(\n\tdb: Kysely<Database>,\n\tleaseToken: string,\n): Promise<Selectable<MediaUsageActivationTable> | null> {\n\tconst now = timestampOffset(db, 0);\n\treturn (\n\t\t(await db\n\t\t\t.updateTable(\"_emdash_media_usage_activation\")\n\t\t\t.set({\n\t\t\t\tstate: \"activating\",\n\t\t\t\tdrain_confirmed_at: now,\n\t\t\t\tlease_token: leaseToken,\n\t\t\t\tlease_expires_at: timestampOffset(db, MEDIA_USAGE_ACTIVATION_LIMITS.leaseDurationSeconds),\n\t\t\t\tattempt_count: sql<number>`attempt_count + 1`,\n\t\t\t\tlast_attempted_at: now,\n\t\t\t\tlast_error_code: null,\n\t\t\t\tupdated_at: now,\n\t\t\t})\n\t\t\t.where(\"task_key\", \"=\", ACTIVATION_KEY)\n\t\t\t.where(\"runtime_generation\", \"=\", MEDIA_USAGE_ACTIVATION_RUNTIME_GENERATION)\n\t\t\t.where((eb) =>\n\t\t\t\teb.or([\n\t\t\t\t\teb(\"state\", \"=\", \"expanded\"),\n\t\t\t\t\teb.and([\n\t\t\t\t\t\teb(\"state\", \"=\", \"activating\"),\n\t\t\t\t\t\teb.or([\n\t\t\t\t\t\t\teb(\"lease_token\", \"is\", null),\n\t\t\t\t\t\t\teb.and([\n\t\t\t\t\t\t\t\teb(\"lease_expires_at\", \"is not\", null),\n\t\t\t\t\t\t\t\ttimestampIsDue(db, \"lease_expires_at\"),\n\t\t\t\t\t\t\t]),\n\t\t\t\t\t\t]),\n\t\t\t\t\t]),\n\t\t\t\t]),\n\t\t\t)\n\t\t\t.returningAll()\n\t\t\t.executeTakeFirst()) ?? null\n\t);\n}\n\nasync function activationClaimLoss(db: Kysely<Database>): Promise<MediaUsageActivationResult> {\n\tconst current = await findActivation(db);\n\tassertRuntimeGeneration(current);\n\tif (current.state === \"active\") return { outcome: \"active\", processedCollections: 0 };\n\tif (\n\t\tcurrent.state === \"activating\" &&\n\t\tcurrent.lease_token &&\n\t\tcurrent.lease_expires_at &&\n\t\t(await activationLeaseIsLive(db, current.lease_token))\n\t) {\n\t\treturn { outcome: \"lease_active\", leaseExpiresAt: current.lease_expires_at };\n\t}\n\tthrow new Error(\"Media usage activation state is not claimable\");\n}\n\nasync function activationLeaseIsLive(db: Kysely<Database>, leaseToken: string): Promise<boolean> {\n\tconst row = await db\n\t\t.selectFrom(\"_emdash_media_usage_activation\")\n\t\t.select(\"task_key\")\n\t\t.where(\"task_key\", \"=\", ACTIVATION_KEY)\n\t\t.where(\"state\", \"=\", \"activating\")\n\t\t.where(\"runtime_generation\", \"=\", MEDIA_USAGE_ACTIVATION_RUNTIME_GENERATION)\n\t\t.where(\"lease_token\", \"=\", leaseToken)\n\t\t.where(\"lease_expires_at\", \"is not\", null)\n\t\t.where(timestampIsLive(db, \"lease_expires_at\"))\n\t\t.executeTakeFirst();\n\treturn row !== undefined;\n}\n\nasync function findActivationCandidates(\n\tdb: Kysely<Database>,\n\tcollectionCursor: string | null,\n): Promise<Array<{ id: string; slug: string }>> {\n\tlet query = db.selectFrom(\"_emdash_collections\").select([\"id\", \"slug\"]);\n\tif (collectionCursor) query = query.where(\"slug\", \">\", collectionCursor);\n\treturn query\n\t\t.orderBy(\"slug\", \"asc\")\n\t\t.limit(MEDIA_USAGE_ACTIVATION_LIMITS.collectionsPerCall + 1)\n\t\t.execute();\n}\n\nasync function prepareCollectionForActivation(\n\tdb: Kysely<Database>,\n\tcollection: { id: string; slug: string },\n\tleaseToken: string,\n): Promise<void> {\n\tconst now = timestampOffset(db, 0);\n\tconst existingStatus = sql.ref(\"_emdash_media_usage_index_status.status\");\n\tconst existingCompletedAt = sql.ref(\"_emdash_media_usage_index_status.completed_at\");\n\tconst existingCollectionId = sql.ref(\"_emdash_media_usage_index_status.collection_id\");\n\tconst existingCaptureState = sql.ref(\"_emdash_media_usage_index_status.capture_state\");\n\tconst row = await db\n\t\t.insertInto(\"_emdash_media_usage_index_status\")\n\t\t.values({\n\t\t\tadapter_id: \"content-media\",\n\t\t\tscope_type: \"collection\",\n\t\t\tscope_key: collection.slug,\n\t\t\tstatus: \"never\",\n\t\t\tcollection_id: collection.id,\n\t\t\treconciliation_required: 1,\n\t\t\tcapture_state: \"installing\",\n\t\t\tupdated_at: now,\n\t\t})\n\t\t.onConflict((conflict) =>\n\t\t\tconflict\n\t\t\t\t.columns([\"adapter_id\", \"scope_type\", \"scope_key\"])\n\t\t\t\t.doUpdateSet({\n\t\t\t\t\tstatus: sql<string>`CASE WHEN ${existingStatus} IN ('complete', 'running') THEN 'stale' ELSE ${existingStatus} END`,\n\t\t\t\t\tcompleted_at: sql<\n\t\t\t\t\t\tstring | null\n\t\t\t\t\t>`CASE WHEN ${existingStatus} IN ('complete', 'running') THEN NULL ELSE ${existingCompletedAt} END`,\n\t\t\t\t\tcursor: null,\n\t\t\t\t\tcollection_id: collection.id,\n\t\t\t\t\treconciliation_required: 1,\n\t\t\t\t\tcapture_state: \"installing\",\n\t\t\t\t\tupdated_at: now,\n\t\t\t\t})\n\t\t\t\t.where((eb) =>\n\t\t\t\t\teb.and([\n\t\t\t\t\t\teb.or([\n\t\t\t\t\t\t\teb(existingCollectionId, \"is\", null),\n\t\t\t\t\t\t\teb(existingCollectionId, \"=\", collection.id),\n\t\t\t\t\t\t]),\n\t\t\t\t\t\teb.or([\n\t\t\t\t\t\t\teb(existingCaptureState, \"is\", null),\n\t\t\t\t\t\t\teb(existingCaptureState, \"!=\", \"deleting\"),\n\t\t\t\t\t\t]),\n\t\t\t\t\t\tactiveActivationLease(db, leaseToken),\n\t\t\t\t\t]),\n\t\t\t\t),\n\t\t)\n\t\t.returning(\"collection_id\")\n\t\t.executeTakeFirst();\n\tif (row?.collection_id !== collection.id) {\n\t\tthrow new Error(\"Media usage collection lifecycle identity conflict\");\n\t}\n}\n\nasync function requireActivationLease(db: Kysely<Database>, leaseToken: string): Promise<void> {\n\tif (!(await activationLeaseIsLive(db, leaseToken))) {\n\t\tthrow new Error(\"Media usage activation lease is no longer live\");\n\t}\n}\n\nasync function activateCollectionLifecycle(\n\tdb: Kysely<Database>,\n\tcollection: { id: string; slug: string },\n\tleaseToken: string,\n): Promise<boolean> {\n\tconst result = await db\n\t\t.updateTable(\"_emdash_media_usage_index_status\")\n\t\t.set({ capture_state: \"active\", updated_at: timestampOffset(db, 0) })\n\t\t.where(\"adapter_id\", \"=\", \"content-media\")\n\t\t.where(\"scope_type\", \"=\", \"collection\")\n\t\t.where(\"scope_key\", \"=\", collection.slug)\n\t\t.where(\"collection_id\", \"=\", collection.id)\n\t\t.where(\"capture_state\", \"=\", \"installing\")\n\t\t.where(\n\t\t\tsql<boolean>`EXISTS (\n\t\t\t\tSELECT 1 FROM _emdash_collections AS collection\n\t\t\t\tWHERE collection.id = ${collection.id}\n\t\t\t\t\tAND collection.slug = ${collection.slug}\n\t\t\t)`,\n\t\t)\n\t\t.where(activeActivationLease(db, leaseToken))\n\t\t.executeTakeFirst();\n\treturn Number(result.numUpdatedRows ?? 0) > 0;\n}\n\nasync function releaseActivationBatch(\n\tdb: Kysely<Database>,\n\tleaseToken: string,\n\tcollectionCursor: string | null,\n): Promise<boolean> {\n\tconst result = await db\n\t\t.updateTable(\"_emdash_media_usage_activation\")\n\t\t.set({\n\t\t\tcollection_cursor: collectionCursor,\n\t\t\tlease_token: null,\n\t\t\tlease_expires_at: null,\n\t\t\tupdated_at: timestampOffset(db, 0),\n\t\t})\n\t\t.where(\"task_key\", \"=\", ACTIVATION_KEY)\n\t\t.where(\"state\", \"=\", \"activating\")\n\t\t.where(\"runtime_generation\", \"=\", MEDIA_USAGE_ACTIVATION_RUNTIME_GENERATION)\n\t\t.where(\"lease_token\", \"=\", leaseToken)\n\t\t.where(\"lease_expires_at\", \"is not\", null)\n\t\t.where(timestampIsLive(db, \"lease_expires_at\"))\n\t\t.executeTakeFirst();\n\treturn Number(result.numUpdatedRows ?? 0) > 0;\n}\n\nasync function findIncompleteCollection(\n\tdb: Kysely<Database>,\n): Promise<{ id: string; slug: string } | null> {\n\tconst row = await db\n\t\t.selectFrom(\"_emdash_collections as collection\")\n\t\t.select([\"collection.id\", \"collection.slug\"])\n\t\t.where((eb) =>\n\t\t\teb.not(\n\t\t\t\teb.exists(\n\t\t\t\t\teb\n\t\t\t\t\t\t.selectFrom(\"_emdash_media_usage_index_status as status\")\n\t\t\t\t\t\t.select(\"status.scope_key\")\n\t\t\t\t\t\t.where(\"status.adapter_id\", \"=\", \"content-media\")\n\t\t\t\t\t\t.where(\"status.scope_type\", \"=\", \"collection\")\n\t\t\t\t\t\t.whereRef(\"status.scope_key\", \"=\", \"collection.slug\")\n\t\t\t\t\t\t.whereRef(\"status.collection_id\", \"=\", \"collection.id\")\n\t\t\t\t\t\t.where(\"status.capture_state\", \"=\", \"active\"),\n\t\t\t\t),\n\t\t\t),\n\t\t)\n\t\t.orderBy(\"collection.slug\", \"asc\")\n\t\t.limit(1)\n\t\t.executeTakeFirst();\n\treturn row ?? null;\n}\n\nasync function finalizeActivation(\n\tdb: Kysely<Database>,\n\tleaseToken: string,\n\tcollectionCursor: string | null,\n): Promise<boolean> {\n\tconst now = timestampOffset(db, 0);\n\tconst result = await db\n\t\t.updateTable(\"_emdash_media_usage_activation\")\n\t\t.set({\n\t\t\tstate: \"active\",\n\t\t\tcollection_cursor: collectionCursor,\n\t\t\tlease_token: null,\n\t\t\tlease_expires_at: null,\n\t\t\tlast_error_code: null,\n\t\t\tactivated_at: now,\n\t\t\tupdated_at: now,\n\t\t})\n\t\t.where(\"task_key\", \"=\", ACTIVATION_KEY)\n\t\t.where(\"state\", \"=\", \"activating\")\n\t\t.where(\"runtime_generation\", \"=\", MEDIA_USAGE_ACTIVATION_RUNTIME_GENERATION)\n\t\t.where(\"lease_token\", \"=\", leaseToken)\n\t\t.where(\"lease_expires_at\", \"is not\", null)\n\t\t.where(timestampIsLive(db, \"lease_expires_at\"))\n\t\t.where(\n\t\t\tsql<boolean>`NOT EXISTS (\n\t\t\t\tSELECT 1\n\t\t\t\tFROM _emdash_collections AS collection\n\t\t\t\tWHERE NOT EXISTS (\n\t\t\t\t\tSELECT 1\n\t\t\t\t\tFROM _emdash_media_usage_index_status AS status\n\t\t\t\t\tWHERE status.adapter_id = 'content-media'\n\t\t\t\t\t\tAND status.scope_type = 'collection'\n\t\t\t\t\t\tAND status.scope_key = collection.slug\n\t\t\t\t\t\tAND status.collection_id = collection.id\n\t\t\t\t\t\tAND status.capture_state = 'active'\n\t\t\t\t)\n\t\t\t)`,\n\t\t)\n\t\t.executeTakeFirst();\n\treturn Number(result.numUpdatedRows ?? 0) > 0;\n}\n\nasync function recordActivationFailure(db: Kysely<Database>, leaseToken: string): Promise<void> {\n\tawait db\n\t\t.updateTable(\"_emdash_media_usage_activation\")\n\t\t.set({\n\t\t\tlease_token: null,\n\t\t\tlease_expires_at: null,\n\t\t\tlast_error_code: ACTIVATION_ERROR_CODE,\n\t\t\tupdated_at: timestampOffset(db, 0),\n\t\t})\n\t\t.where(\"task_key\", \"=\", ACTIVATION_KEY)\n\t\t.where(\"state\", \"=\", \"activating\")\n\t\t.where(\"runtime_generation\", \"=\", MEDIA_USAGE_ACTIVATION_RUNTIME_GENERATION)\n\t\t.where(\"lease_token\", \"=\", leaseToken)\n\t\t.execute();\n}\n\nfunction activeActivationLease(db: Kysely<Database>, leaseToken: string): RawBuilder<boolean> {\n\treturn sql<boolean>`EXISTS (\n\t\tSELECT 1\n\t\tFROM _emdash_media_usage_activation AS activation\n\t\tWHERE activation.task_key = ${ACTIVATION_KEY}\n\t\t\tAND activation.state = 'activating'\n\t\t\tAND activation.runtime_generation = ${MEDIA_USAGE_ACTIVATION_RUNTIME_GENERATION}\n\t\t\tAND activation.lease_token = ${leaseToken}\n\t\t\tAND activation.lease_expires_at IS NOT NULL\n\t\t\tAND ${timestampIsLive(db, \"activation.lease_expires_at\")}\n\t)`;\n}\n\nfunction timestampIsDue(db: Kysely<Database>, column: string): RawBuilder<boolean> {\n\treturn isPostgres(db)\n\t\t? sql<boolean>`${sql.ref(column)}::timestamptz <= clock_timestamp()`\n\t\t: sql<boolean>`${sql.ref(column)} <= strftime('%Y-%m-%dT%H:%M:%fZ', 'now')`;\n}\n\nfunction timestampIsLive(db: Kysely<Database>, column: string): RawBuilder<boolean> {\n\treturn isPostgres(db)\n\t\t? sql<boolean>`${sql.ref(column)}::timestamptz > clock_timestamp()`\n\t\t: sql<boolean>`${sql.ref(column)} > strftime('%Y-%m-%dT%H:%M:%fZ', 'now')`;\n}\n\nfunction timestampOffset(db: Kysely<Database>, seconds: number): RawBuilder<string> {\n\tif (isPostgres(db)) {\n\t\treturn sql<string>`to_char(\n\t\t\tclock_timestamp() AT TIME ZONE 'UTC' + (${seconds} * interval '1 second'),\n\t\t\t'YYYY-MM-DD\"T\"HH24:MI:SS.MS\"Z\"'\n\t\t)`;\n\t}\n\treturn sql<string>`strftime('%Y-%m-%dT%H:%M:%fZ', 'now', ${`+${seconds} seconds`})`;\n}\n","import type {\n\tColumnDataType,\n\tCreateTableBuilder,\n\tInsertable,\n\tKysely,\n\tSelectable,\n\tUpdateable,\n} from \"kysely\";\nimport { sql } from \"kysely\";\nimport { ulid } from \"ulidx\";\n\nimport { currentTimestamp, listTablesLike, tableExists } from \"../database/dialect-helpers.js\";\nimport { withTransaction } from \"../database/transaction.js\";\nimport type { CollectionTable, Database, FieldTable } from \"../database/types.js\";\nimport { validateIdentifier } from \"../database/validate.js\";\nimport {\n\tcanResumeMediaUsageCollectionCapture,\n\tfinalizeMediaUsageCollectionCapture,\n\tinstallPreparedMediaUsageCollectionCapture,\n\tmarkMediaUsageCollectionCaptureReady,\n\tprepareMediaUsageCollectionCapture,\n} from \"../media/usage/activation.js\";\nimport {\n\tdeleteActivatedMediaUsageCollection,\n\tisMediaUsageCollectionSlugDeleting,\n} from \"../media/usage/collection-deletion.js\";\nimport {\n\tdeleteContentMediaUsageCollection,\n\tinvalidateContentMediaUsageSchemaChange,\n\tmarkContentMediaUsageCollectionStaleSafely,\n} from \"../media/usage/content-refresh.js\";\nimport { FTSManager } from \"../search/fts-manager.js\";\nimport { chunks, SQL_BATCH_SIZE } from \"../utils/chunks.js\";\nimport {\n\ttype Collection,\n\ttype CollectionAdminConfig,\n\ttype CollectionSource,\n\ttype CollectionSupport,\n\ttype ColumnType,\n\ttype Field,\n\ttype CreateCollectionInput,\n\ttype UpdateCollectionInput,\n\ttype CreateFieldInput,\n\ttype UpdateFieldInput,\n\ttype CollectionWithFields,\n\ttype FieldType,\n\tFIELD_TYPE_TO_COLUMN,\n\tisIndexableFieldType,\n\tRESERVED_FIELD_SLUGS,\n\tRESERVED_COLLECTION_SLUGS,\n} from \"./types.js\";\n\n// Regex patterns for schema registry\nconst SLUG_VALIDATION_PATTERN = /^[a-z][a-z0-9_]*$/;\nconst EC_PREFIX_PATTERN = /^ec_/;\nconst SINGLE_QUOTE_PATTERN = /'/g;\nconst UNDERSCORE_PATTERN = /_/g;\nconst WORD_BOUNDARY_PATTERN = /\\b\\w/g;\nconst FIELD_ID_PATTERN = /^[0-9A-Z]{26}$/;\n\n/** Valid column types for runtime validation */\nconst COLUMN_TYPES: ReadonlySet<string> = new Set([\"TEXT\", \"REAL\", \"INTEGER\", \"JSON\"]);\nconst COLUMN_TYPE_TO_DATA_TYPE = {\n\tTEXT: \"text\",\n\tREAL: \"real\",\n\tINTEGER: \"integer\",\n\tJSON: \"json\",\n} satisfies Record<ColumnType, ColumnDataType>;\nconst TEXT_ALIAS_FIELD_TYPES: ReadonlySet<FieldType> = new Set([\"string\", \"text\", \"slug\"]);\n\n/** Field types usable as a `titleField` — plain text that reads well as a title. */\nconst TITLE_FIELD_TYPES: ReadonlySet<string> = new Set([\"string\", \"text\", \"slug\"]);\n\n/** Valid collection source prefixes/values */\nconst VALID_SOURCES: ReadonlySet<string> = new Set([\"manual\", \"discovered\", \"seed\"]);\n\nfunction isCollectionSource(value: string): value is CollectionSource {\n\treturn VALID_SOURCES.has(value) || value.startsWith(\"template:\") || value.startsWith(\"import:\");\n}\n\nfunction isFieldType(value: string): value is FieldType {\n\treturn value in FIELD_TYPE_TO_COLUMN;\n}\n\nfunction isColumnType(value: string): value is ColumnType {\n\treturn COLUMN_TYPES.has(value);\n}\n\nconst VALID_COLLECTION_SUPPORTS: ReadonlySet<string> = new Set<CollectionSupport>([\n\t\"drafts\",\n\t\"revisions\",\n\t\"preview\",\n\t\"scheduling\",\n\t\"search\",\n\t\"seo\",\n]);\n\n// Each _emdash_fields row uses 16 bound parameters. Six rows keep every\n// multi-row INSERT below D1's 100-parameter statement limit.\nconst SEED_FIELD_INSERT_BATCH_SIZE = 6;\n\n/**\n * Rank given to collections without an explicit `sort_order`. SQLite sorts\n * NULL first on ASC while Postgres sorts it last, so the fallback is\n * materialised with COALESCE rather than left to the dialect.\n */\nconst UNORDERED_COLLECTION_RANK = 2147483647;\n\n/**\n * Collection ordering shared by every list read: explicit `sort_order`\n * first (ascending), then alphabetically by slug.\n */\nconst collectionOrder = sql<number>`coalesce(sort_order, ${sql.lit(UNORDERED_COLLECTION_RANK)})`;\n\nfunction assertIndexableField(type: FieldType, indexed: boolean | undefined, slug: string): void {\n\tif (indexed && !isIndexableFieldType(type)) {\n\t\tthrow new SchemaError(\n\t\t\t`Field \"${slug}\" cannot be indexed because type \"${type}\" is not a scalar query type`,\n\t\t\t\"FIELD_NOT_INDEXABLE\",\n\t\t);\n\t}\n}\n\nfunction isCollectionSupport(value: unknown): value is CollectionSupport {\n\treturn typeof value === \"string\" && VALID_COLLECTION_SUPPORTS.has(value);\n}\n\n/**\n * Parse a collection's `supports` column (stored as a JSON array of\n * CollectionSupport keys). Unknown/invalid entries are filtered out so the\n * runtime value matches the declared `CollectionSupport[]` type.\n *\n * Throws on malformed JSON so corruption surfaces loudly; returns an empty\n * array only for explicitly null/empty values or non-array JSON.\n */\nfunction parseSupports(raw: string | null | undefined): CollectionSupport[] {\n\tif (!raw) return [];\n\tconst parsed: unknown = JSON.parse(raw);\n\tif (!Array.isArray(parsed)) return [];\n\treturn parsed.filter(isCollectionSupport);\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction parseCollectionAdmin(raw: string | null | undefined): CollectionAdminConfig | undefined {\n\tif (!raw) return undefined;\n\tconst parsed: unknown = JSON.parse(raw);\n\tif (!isRecord(parsed)) return undefined;\n\tconst listColumns = parsed.listColumns;\n\treturn {\n\t\tlistColumns: Array.isArray(listColumns)\n\t\t\t? listColumns.filter((value): value is string => typeof value === \"string\")\n\t\t\t: undefined,\n\t};\n}\n\nexport async function buildSeedCollectionCaptureFingerprint(\n\tinput: Omit<CreateCollectionInput, \"source\">,\n\tfields: readonly CreateFieldInput[],\n): Promise<string> {\n\tconst supports = input.supports ?? [\"drafts\", \"revisions\"];\n\tconst hasSeo = input.hasSeo ?? supports.includes(\"seo\") ?? false;\n\tlet maxSortOrder = -1;\n\tconst definitions = fields.map((field) => {\n\t\tconst sortOrder = field.sortOrder ?? maxSortOrder + 1;\n\t\tmaxSortOrder = Math.max(maxSortOrder, sortOrder);\n\t\treturn {\n\t\t\tslug: field.slug,\n\t\t\tlabel: field.label,\n\t\t\ttype: field.type,\n\t\t\trequired: field.required ?? false,\n\t\t\tunique: field.unique ?? false,\n\t\t\tdefaultValue: field.defaultValue === undefined ? null : JSON.stringify(field.defaultValue),\n\t\t\tvalidation: field.validation ? JSON.stringify(field.validation) : null,\n\t\t\twidget: field.widget ?? null,\n\t\t\toptions: field.options ? JSON.stringify(field.options) : null,\n\t\t\tsortOrder,\n\t\t\tsearchable: field.searchable ?? false,\n\t\t\ttranslatable: field.translatable ?? true,\n\t\t};\n\t});\n\tconst payload = JSON.stringify(\n\t\tcanonicalizeFingerprintValue({\n\t\t\tversion: 1,\n\t\t\tcollection: {\n\t\t\t\tslug: input.slug,\n\t\t\t\tlabel: input.label,\n\t\t\t\tlabelSingular: input.labelSingular ?? null,\n\t\t\t\tdescription: input.description ?? null,\n\t\t\t\ticon: input.icon ?? null,\n\t\t\t\tadmin: input.admin ?? null,\n\t\t\t\tsupports,\n\t\t\t\thasSeo,\n\t\t\t\thidden: input.hidden ?? false,\n\t\t\t\tsortOrder: input.sortOrder ?? null,\n\t\t\t\tcommentsEnabled: input.commentsEnabled ?? false,\n\t\t\t\turlPattern: input.urlPattern ?? null,\n\t\t\t\troutable: input.routable ?? true,\n\t\t\t},\n\t\t\tfields: definitions,\n\t\t}),\n\t);\n\tconst digest = await crypto.subtle.digest(\"SHA-256\", new TextEncoder().encode(payload));\n\tconst hex = Array.from(new Uint8Array(digest), (byte) => byte.toString(16).padStart(2, \"0\")).join(\n\t\t\"\",\n\t);\n\treturn `media-usage-seed:v1:sha256:${hex}`;\n}\n\nfunction canonicalizeFingerprintValue(value: unknown): unknown {\n\tif (value === undefined) return { __emdashUndefined: true };\n\tif (Array.isArray(value)) return value.map(canonicalizeFingerprintValue);\n\tif (typeof value !== \"object\" || value === null) return value;\n\n\tconst canonical: Record<string, unknown> = {};\n\tfor (const [key, entry] of Object.entries(value).toSorted(([a], [b]) => a.localeCompare(b))) {\n\t\tcanonical[key] = canonicalizeFingerprintValue(entry);\n\t}\n\treturn canonical;\n}\n\n/**\n * Error thrown when a schema operation fails\n */\nexport class SchemaError extends Error {\n\tconstructor(\n\t\tmessage: string,\n\t\tpublic code: string,\n\t\tpublic details?: Record<string, unknown>,\n\t) {\n\t\tsuper(message);\n\t\tthis.name = \"SchemaError\";\n\t}\n}\n\n/**\n * Schema Registry\n *\n * Manages collection and field definitions stored in D1.\n * Handles runtime DDL operations (CREATE TABLE, ALTER TABLE).\n */\nexport class SchemaRegistry {\n\tconstructor(private db: Kysely<Database>) {}\n\n\t// ============================================\n\t// Collection Operations\n\t// ============================================\n\n\t/**\n\t * List all collections\n\t */\n\tasync listCollections(): Promise<Collection[]> {\n\t\tconst rows = await this.db\n\t\t\t.selectFrom(\"_emdash_collections\")\n\t\t\t.selectAll()\n\t\t\t.orderBy(collectionOrder, \"asc\")\n\t\t\t.orderBy(\"slug\", \"asc\")\n\t\t\t.execute();\n\n\t\treturn rows.map(this.mapCollectionRow);\n\t}\n\n\t/**\n\t * Get a collection by slug\n\t */\n\tasync getCollection(slug: string): Promise<Collection | null> {\n\t\tconst row = await this.db\n\t\t\t.selectFrom(\"_emdash_collections\")\n\t\t\t.where(\"slug\", \"=\", slug)\n\t\t\t.selectAll()\n\t\t\t.executeTakeFirst();\n\n\t\treturn row ? this.mapCollectionRow(row) : null;\n\t}\n\n\t/**\n\t * Get a collection with all its fields\n\t */\n\tasync getCollectionWithFields(slug: string): Promise<CollectionWithFields | null> {\n\t\tconst collection = await this.getCollection(slug);\n\t\tif (!collection) return null;\n\n\t\tconst fields = await this.listFields(collection.id);\n\n\t\treturn { ...collection, fields };\n\t}\n\n\t/**\n\t * List every collection together with its fields in O(1) query shapes\n\t * — one for collections, then one batched query for the fields of every\n\t * returned collection — instead of the N+1 pattern of `listCollections`\n\t * + per-collection `listFields`. The fields query is chunked at\n\t * `SQL_BATCH_SIZE` to stay under D1's bound-parameter limit, so on\n\t * sites with more than `SQL_BATCH_SIZE` collections the field fetch\n\t * becomes `ceil(collectionCount / SQL_BATCH_SIZE)` queries — still\n\t * a constant factor, not N+1. Typical sites have well under\n\t * `SQL_BATCH_SIZE` collections, so this is two queries in practice.\n\t *\n\t * Used by the manifest build, which previously paid N+1 round-trips on\n\t * every admin request. Each round-trip costs ~80–150ms against the D1\n\t * primary on a busy link, so a 10-collection site spent ~1 s rebuilding\n\t * a manifest that is now built fresh per admin request (no cache).\n\t */\n\tasync listCollectionsWithFields(): Promise<CollectionWithFields[]> {\n\t\tconst collectionRows = await this.db\n\t\t\t.selectFrom(\"_emdash_collections\")\n\t\t\t.selectAll()\n\t\t\t.orderBy(collectionOrder, \"asc\")\n\t\t\t.orderBy(\"slug\", \"asc\")\n\t\t\t.execute();\n\n\t\tif (collectionRows.length === 0) return [];\n\n\t\tconst fieldsByCollection = new Map<string, Field[]>();\n\t\t// Chunk to stay under D1's bound-parameter limit. Typical sites have\n\t\t// well under SQL_BATCH_SIZE collections, so this is a single query\n\t\t// in practice; on larger sites it becomes a small constant number\n\t\t// of queries, never N+1.\n\t\tfor (const idChunk of chunks(\n\t\t\tcollectionRows.map((c) => c.id),\n\t\t\tSQL_BATCH_SIZE,\n\t\t)) {\n\t\t\tconst fieldRows = await this.db\n\t\t\t\t.selectFrom(\"_emdash_fields\")\n\t\t\t\t.where(\"collection_id\", \"in\", idChunk)\n\t\t\t\t.selectAll()\n\t\t\t\t.orderBy(\"collection_id\", \"asc\")\n\t\t\t\t.orderBy(\"sort_order\", \"asc\")\n\t\t\t\t.orderBy(\"created_at\", \"asc\")\n\t\t\t\t.execute();\n\t\t\tfor (const row of fieldRows) {\n\t\t\t\tconst list = fieldsByCollection.get(row.collection_id) ?? [];\n\t\t\t\tlist.push(this.mapFieldRow(row));\n\t\t\t\tfieldsByCollection.set(row.collection_id, list);\n\t\t\t}\n\t\t}\n\n\t\treturn collectionRows.map((c) => ({\n\t\t\t...this.mapCollectionRow(c),\n\t\t\tfields: fieldsByCollection.get(c.id) ?? [],\n\t\t}));\n\t}\n\n\t/**\n\t * Validate `titleField`/`dateField` against the collection's fields:\n\t * `titleField` must be a text-like field; `dateField` must be a `datetime` field.\n\t * Only truthy values are checked (undefined = unchanged, null/\"\" = cleared).\n\t */\n\tprivate async validateTitleDateFields(\n\t\tcollectionId: string,\n\t\tcollectionSlug: string,\n\t\tinput: { titleField?: string | null; dateField?: string | null },\n\t\tdb: Kysely<Database> = this.db,\n\t): Promise<void> {\n\t\tconst slugs = [input.titleField, input.dateField].filter((slug): slug is string => !!slug);\n\t\tif (slugs.length === 0) return;\n\n\t\tconst rows = await db\n\t\t\t.selectFrom(\"_emdash_fields\")\n\t\t\t.where(\"collection_id\", \"=\", collectionId)\n\t\t\t.where(\"slug\", \"in\", slugs)\n\t\t\t.select([\"slug\", \"type\"])\n\t\t\t.execute();\n\t\tconst typeBySlug = new Map(rows.map((row) => [row.slug, row.type]));\n\n\t\tif (input.titleField) {\n\t\t\tconst type = typeBySlug.get(input.titleField);\n\t\t\tif (type === undefined) {\n\t\t\t\tthrow new SchemaError(\n\t\t\t\t\t`titleField \"${input.titleField}\" is not a field on \"${collectionSlug}\"`,\n\t\t\t\t\t\"INVALID_TITLE_FIELD\",\n\t\t\t\t);\n\t\t\t}\n\t\t\tif (!TITLE_FIELD_TYPES.has(type)) {\n\t\t\t\tthrow new SchemaError(\n\t\t\t\t\t`titleField \"${input.titleField}\" must be a text field (got \"${type}\")`,\n\t\t\t\t\t\"INVALID_TITLE_FIELD\",\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\tif (input.dateField) {\n\t\t\tconst type = typeBySlug.get(input.dateField);\n\t\t\tif (type === undefined) {\n\t\t\t\tthrow new SchemaError(\n\t\t\t\t\t`dateField \"${input.dateField}\" is not a field on \"${collectionSlug}\"`,\n\t\t\t\t\t\"INVALID_DATE_FIELD\",\n\t\t\t\t);\n\t\t\t}\n\t\t\tif (type !== \"datetime\") {\n\t\t\t\tthrow new SchemaError(\n\t\t\t\t\t`dateField \"${input.dateField}\" must be a datetime field (got \"${type}\")`,\n\t\t\t\t\t\"INVALID_DATE_FIELD\",\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Create a new collection\n\t */\n\tasync createCollection(input: CreateCollectionInput): Promise<Collection> {\n\t\t// Validate slug\n\t\tthis.validateSlug(input.slug, \"collection\");\n\t\tif (RESERVED_COLLECTION_SLUGS.includes(input.slug)) {\n\t\t\tthrow new SchemaError(`Collection slug \"${input.slug}\" is reserved`, \"RESERVED_SLUG\");\n\t\t}\n\t\tif (await isMediaUsageCollectionSlugDeleting(this.db, input.slug)) {\n\t\t\tthrow new SchemaError(`Collection \"${input.slug}\" already exists`, \"COLLECTION_EXISTS\");\n\t\t}\n\n\t\t// Check if collection already exists\n\t\tconst existing = await this.getCollection(input.slug);\n\t\tif (await isMediaUsageCollectionSlugDeleting(this.db, input.slug)) {\n\t\t\tthrow new SchemaError(`Collection \"${input.slug}\" already exists`, \"COLLECTION_EXISTS\");\n\t\t}\n\t\tif (\n\t\t\texisting &&\n\t\t\t!(await canResumeMediaUsageCollectionCapture(this.db, {\n\t\t\t\tcollectionId: existing.id,\n\t\t\t\tcollectionSlug: existing.slug,\n\t\t\t}))\n\t\t) {\n\t\t\tthrow new SchemaError(`Collection \"${input.slug}\" already exists`, \"COLLECTION_EXISTS\");\n\t\t}\n\n\t\tconst proposedId = existing?.id ?? ulid();\n\n\t\t// Default `supports` to drafts + revisions when the caller didn't\n\t\t// specify it. Explicit empty array (`[]`) is preserved as an opt-out\n\t\t// — only `undefined` triggers the default. This is the canonical\n\t\t// default for new collections; the MCP and admin UI layers used to\n\t\t// duplicate this default but now defer to the registry.\n\t\tconst supports = input.supports ?? [\"drafts\", \"revisions\"];\n\n\t\t// Insert collection record and create content table in a transaction\n\t\t// so a failure in table creation doesn't leave an orphaned row.\n\t\t// Uses withTransaction for D1 compatibility (no transaction support).\n\t\t// Derive hasSeo from supports array if not explicitly set\n\t\tconst hasSeo = input.hasSeo ?? supports.includes(\"seo\") ?? false;\n\n\t\tawait withTransaction(this.db, async (trx) => {\n\t\t\tconst capture = await prepareMediaUsageCollectionCapture(trx, {\n\t\t\t\tcollectionId: proposedId,\n\t\t\t\tcollectionSlug: input.slug,\n\t\t\t\tregisteredCollectionId: existing?.id,\n\t\t\t});\n\t\t\tconst values: Insertable<CollectionTable> = {\n\t\t\t\tid: capture.collectionId,\n\t\t\t\tslug: input.slug,\n\t\t\t\tlabel: input.label,\n\t\t\t\tlabel_singular: input.labelSingular ?? null,\n\t\t\t\tdescription: input.description ?? null,\n\t\t\t\ticon: input.icon ?? null,\n\t\t\t\tadmin_config: input.admin ? JSON.stringify(input.admin) : null,\n\t\t\t\tsupports: JSON.stringify(supports),\n\t\t\t\tsource: input.source ?? \"manual\",\n\t\t\t\tstorage: input.storage ?? \"db\",\n\t\t\t\thas_seo: hasSeo ? 1 : 0,\n\t\t\t\troutable: input.routable === false ? 0 : 1,\n\t\t\t\thidden: input.hidden ? 1 : 0,\n\t\t\t\tsort_order: input.sortOrder ?? null,\n\t\t\t\tcomments_enabled: input.commentsEnabled ? 1 : 0,\n\t\t\t\turl_pattern: input.urlPattern ?? null,\n\t\t\t};\n\n\t\t\tif (capture.captureRequired) {\n\t\t\t\tawait this.createContentTable(input.slug, trx, [], {\n\t\t\t\t\tifNotExists: capture.resuming,\n\t\t\t\t});\n\t\t\t\tawait installPreparedMediaUsageCollectionCapture(trx, {\n\t\t\t\t\tcollectionId: capture.collectionId,\n\t\t\t\t\tcollectionSlug: input.slug,\n\t\t\t\t});\n\t\t\t\tawait markMediaUsageCollectionCaptureReady(trx, {\n\t\t\t\t\tcollectionId: capture.collectionId,\n\t\t\t\t\tcollectionSlug: input.slug,\n\t\t\t\t});\n\t\t\t\tif (!capture.registrationExists) {\n\t\t\t\t\tawait trx.insertInto(\"_emdash_collections\").values(values).execute();\n\t\t\t\t}\n\t\t\t\tawait finalizeMediaUsageCollectionCapture(trx, {\n\t\t\t\t\tcollectionId: capture.collectionId,\n\t\t\t\t\tcollectionSlug: input.slug,\n\t\t\t\t});\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tawait trx.insertInto(\"_emdash_collections\").values(values).execute();\n\t\t\tawait this.createContentTable(input.slug, trx);\n\t\t});\n\n\t\tconst collection = await this.getCollection(input.slug);\n\t\tif (!collection) {\n\t\t\tthrow new SchemaError(\"Failed to create collection\", \"CREATE_FAILED\");\n\t\t}\n\n\t\treturn collection;\n\t}\n\n\t/**\n\t * Create a seed-owned collection and all of its fields in bulk.\n\t *\n\t * Fresh seeds can define dozens of fields. Creating them through\n\t * `createField` performs multiple reads, one ALTER TABLE, and one media\n\t * usage invalidation per field, which can exhaust D1's per-request query\n\t * budget. This path validates the full schema before mutating it, creates\n\t * the complete content table in one statement, and inserts field metadata\n\t * in parameter-safe batches.\n\t */\n\tasync createSeedCollection(\n\t\tinput: Omit<CreateCollectionInput, \"source\">,\n\t\tfields: readonly CreateFieldInput[],\n\t): Promise<void> {\n\t\tthis.validateSlug(input.slug, \"collection\");\n\t\tif (RESERVED_COLLECTION_SLUGS.includes(input.slug)) {\n\t\t\tthrow new SchemaError(`Collection slug \"${input.slug}\" is reserved`, \"RESERVED_SLUG\");\n\t\t}\n\t\tif (await isMediaUsageCollectionSlugDeleting(this.db, input.slug)) {\n\t\t\tthrow new SchemaError(`Collection \"${input.slug}\" already exists`, \"COLLECTION_EXISTS\");\n\t\t}\n\n\t\tconst fieldSlugs = new Set<string>();\n\t\tfor (const field of fields) {\n\t\t\tthis.validateSlug(field.slug, \"field\");\n\t\t\tassertIndexableField(field.type, field.indexed, field.slug);\n\t\t\tif (RESERVED_FIELD_SLUGS.includes(field.slug)) {\n\t\t\t\tthrow new SchemaError(`Field slug \"${field.slug}\" is reserved`, \"RESERVED_SLUG\");\n\t\t\t}\n\t\t\tif (fieldSlugs.has(field.slug)) {\n\t\t\t\tthrow new SchemaError(\n\t\t\t\t\t`Field \"${field.slug}\" already exists in collection \"${input.slug}\"`,\n\t\t\t\t\t\"FIELD_EXISTS\",\n\t\t\t\t);\n\t\t\t}\n\t\t\tfieldSlugs.add(field.slug);\n\t\t}\n\n\t\tconst supports = input.supports ?? [\"drafts\", \"revisions\"];\n\t\tconst hasSeo = input.hasSeo ?? supports.includes(\"seo\") ?? false;\n\t\tconst creationFingerprint = await buildSeedCollectionCaptureFingerprint(input, fields);\n\t\tconst existing = await this.getCollection(input.slug);\n\t\tif (await isMediaUsageCollectionSlugDeleting(this.db, input.slug)) {\n\t\t\tthrow new SchemaError(`Collection \"${input.slug}\" already exists`, \"COLLECTION_EXISTS\");\n\t\t}\n\t\tif (\n\t\t\texisting &&\n\t\t\t!(await canResumeMediaUsageCollectionCapture(this.db, {\n\t\t\t\tcollectionId: existing.id,\n\t\t\t\tcollectionSlug: existing.slug,\n\t\t\t\tcreationFingerprint,\n\t\t\t}))\n\t\t) {\n\t\t\tthrow new SchemaError(`Collection \"${input.slug}\" already exists`, \"COLLECTION_EXISTS\");\n\t\t}\n\n\t\tconst proposedCollectionId = existing?.id ?? ulid();\n\t\tlet maxSortOrder = -1;\n\t\tconst fieldRows: Insertable<FieldTable>[] = fields.map((field) => {\n\t\t\tconst sortOrder = field.sortOrder ?? maxSortOrder + 1;\n\t\t\tmaxSortOrder = Math.max(maxSortOrder, sortOrder);\n\n\t\t\treturn {\n\t\t\t\tid: ulid(),\n\t\t\t\tcollection_id: proposedCollectionId,\n\t\t\t\tslug: field.slug,\n\t\t\t\tlabel: field.label,\n\t\t\t\ttype: field.type,\n\t\t\t\tcolumn_type: FIELD_TYPE_TO_COLUMN[field.type],\n\t\t\t\trequired: field.required ? 1 : 0,\n\t\t\t\tunique: field.unique ? 1 : 0,\n\t\t\t\tdefault_value: field.defaultValue !== undefined ? JSON.stringify(field.defaultValue) : null,\n\t\t\t\tvalidation: field.validation ? JSON.stringify(field.validation) : null,\n\t\t\t\twidget: field.widget ?? null,\n\t\t\t\toptions: field.options ? JSON.stringify(field.options) : null,\n\t\t\t\tsort_order: sortOrder,\n\t\t\t\tsearchable: field.searchable ? 1 : 0,\n\t\t\t\tindexed: field.indexed ? 1 : 0,\n\t\t\t\ttranslatable: field.translatable === false ? 0 : 1,\n\t\t\t};\n\t\t});\n\n\t\tlet schemaMutated = false;\n\t\ttry {\n\t\t\tawait withTransaction(this.db, async (trx) => {\n\t\t\t\tconst capture = await prepareMediaUsageCollectionCapture(trx, {\n\t\t\t\t\tcollectionId: proposedCollectionId,\n\t\t\t\t\tcollectionSlug: input.slug,\n\t\t\t\t\tcreationFingerprint,\n\t\t\t\t\tregisteredCollectionId: existing?.id,\n\t\t\t\t});\n\t\t\t\tconst collectionValues: Insertable<CollectionTable> = {\n\t\t\t\t\tid: capture.collectionId,\n\t\t\t\t\tslug: input.slug,\n\t\t\t\t\tlabel: input.label,\n\t\t\t\t\tlabel_singular: input.labelSingular ?? null,\n\t\t\t\t\tdescription: input.description ?? null,\n\t\t\t\t\ticon: input.icon ?? null,\n\t\t\t\t\tadmin_config: input.admin ? JSON.stringify(input.admin) : null,\n\t\t\t\t\tsupports: JSON.stringify(supports),\n\t\t\t\t\tsource: \"seed\",\n\t\t\t\t\tstorage: input.storage ?? \"db\",\n\t\t\t\t\thas_seo: hasSeo ? 1 : 0,\n\t\t\t\t\troutable: input.routable === false ? 0 : 1,\n\t\t\t\t\thidden: input.hidden ? 1 : 0,\n\t\t\t\t\tsort_order: input.sortOrder ?? null,\n\t\t\t\t\tcomments_enabled: input.commentsEnabled ? 1 : 0,\n\t\t\t\t\turl_pattern: input.urlPattern ?? null,\n\t\t\t\t};\n\t\t\t\tconst rows = fieldRows.map((row) => ({\n\t\t\t\t\t...row,\n\t\t\t\t\tcollection_id: capture.collectionId,\n\t\t\t\t}));\n\n\t\t\t\tif (capture.captureRequired) {\n\t\t\t\t\tawait this.createContentTable(input.slug, trx, fields, {\n\t\t\t\t\t\tifNotExists: capture.resuming,\n\t\t\t\t\t});\n\t\t\t\t\tawait installPreparedMediaUsageCollectionCapture(trx, {\n\t\t\t\t\t\tcollectionId: capture.collectionId,\n\t\t\t\t\t\tcollectionSlug: input.slug,\n\t\t\t\t\t});\n\t\t\t\t\tawait markMediaUsageCollectionCaptureReady(trx, {\n\t\t\t\t\t\tcollectionId: capture.collectionId,\n\t\t\t\t\t\tcollectionSlug: input.slug,\n\t\t\t\t\t});\n\t\t\t\t\tif (!capture.registrationExists) {\n\t\t\t\t\t\tawait trx.insertInto(\"_emdash_collections\").values(collectionValues).execute();\n\t\t\t\t\t\tschemaMutated = true;\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tawait trx.insertInto(\"_emdash_collections\").values(collectionValues).execute();\n\t\t\t\t\tschemaMutated = true;\n\t\t\t\t\tawait this.createContentTable(input.slug, trx, fields);\n\t\t\t\t}\n\n\t\t\t\tfor (const fieldBatch of chunks(rows, SEED_FIELD_INSERT_BATCH_SIZE)) {\n\t\t\t\t\tlet insert = trx.insertInto(\"_emdash_fields\").values(fieldBatch);\n\t\t\t\t\tif (capture.resuming) {\n\t\t\t\t\t\tinsert = insert.onConflict((conflict) =>\n\t\t\t\t\t\t\tconflict.columns([\"collection_id\", \"slug\"]).doNothing(),\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t\tawait insert.execute();\n\t\t\t\t}\n\t\t\t\tlet indexedRows: readonly { id: string; slug: string; indexed?: number }[] = rows;\n\t\t\t\tif (capture.resuming) {\n\t\t\t\t\tindexedRows = await this.assertSeedFieldDefinitions(capture.collectionId, fields, trx);\n\t\t\t\t}\n\t\t\t\tfor (const field of indexedRows) {\n\t\t\t\t\tif (field.indexed === 1) {\n\t\t\t\t\t\tawait this.createFieldIndex(input.slug, field.id, field.slug, trx);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (capture.captureRequired) {\n\t\t\t\t\tawait finalizeMediaUsageCollectionCapture(trx, {\n\t\t\t\t\t\tcollectionId: capture.collectionId,\n\t\t\t\t\t\tcollectionSlug: input.slug,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t});\n\n\t\t\tawait markContentMediaUsageCollectionStaleSafely(this.db, input.slug, \"CONTENT_USAGE_STALE\");\n\t\t} catch (error) {\n\t\t\tif (schemaMutated) {\n\t\t\t\tawait markContentMediaUsageCollectionStaleSafely(\n\t\t\t\t\tthis.db,\n\t\t\t\t\tinput.slug,\n\t\t\t\t\t\"CONTENT_USAGE_STALE\",\n\t\t\t\t);\n\t\t\t}\n\t\t\tthrow error;\n\t\t}\n\t}\n\n\tprivate async assertSeedFieldDefinitions(\n\t\tcollectionId: string,\n\t\tfields: readonly CreateFieldInput[],\n\t\tdb: Kysely<Database>,\n\t): Promise<Selectable<FieldTable>[]> {\n\t\tconst stored = await db\n\t\t\t.selectFrom(\"_emdash_fields\")\n\t\t\t.selectAll()\n\t\t\t.where(\"collection_id\", \"=\", collectionId)\n\t\t\t.execute();\n\t\tif (stored.length !== fields.length) {\n\t\t\tthrow new SchemaError(\"Interrupted seed collection fields do not match\", \"CREATE_FAILED\");\n\t\t}\n\n\t\tlet maxSortOrder = -1;\n\t\tfor (const field of fields) {\n\t\t\tconst sortOrder = field.sortOrder ?? maxSortOrder + 1;\n\t\t\tmaxSortOrder = Math.max(maxSortOrder, sortOrder);\n\t\t\tconst row = stored.find((candidate) => candidate.slug === field.slug);\n\t\t\tif (\n\t\t\t\t!row ||\n\t\t\t\trow.label !== field.label ||\n\t\t\t\trow.type !== field.type ||\n\t\t\t\trow.column_type !== FIELD_TYPE_TO_COLUMN[field.type] ||\n\t\t\t\trow.required !== (field.required ? 1 : 0) ||\n\t\t\t\trow.unique !== (field.unique ? 1 : 0) ||\n\t\t\t\trow.default_value !==\n\t\t\t\t\t(field.defaultValue !== undefined ? JSON.stringify(field.defaultValue) : null) ||\n\t\t\t\trow.validation !== (field.validation ? JSON.stringify(field.validation) : null) ||\n\t\t\t\trow.widget !== (field.widget ?? null) ||\n\t\t\t\trow.options !== (field.options ? JSON.stringify(field.options) : null) ||\n\t\t\t\trow.sort_order !== sortOrder ||\n\t\t\t\trow.searchable !== (field.searchable ? 1 : 0) ||\n\t\t\t\trow.indexed !== (field.indexed ? 1 : 0) ||\n\t\t\t\trow.translatable !== (field.translatable === false ? 0 : 1)\n\t\t\t) {\n\t\t\t\tthrow new SchemaError(\"Interrupted seed collection fields do not match\", \"CREATE_FAILED\");\n\t\t\t}\n\t\t}\n\t\treturn stored;\n\t}\n\n\t/**\n\t * Update a collection\n\t */\n\tasync updateCollection(slug: string, input: UpdateCollectionInput): Promise<Collection> {\n\t\treturn withTransaction(this.db, async (trx) => {\n\t\t\tconst existingRow = await trx\n\t\t\t\t.selectFrom(\"_emdash_collections\")\n\t\t\t\t.where(\"slug\", \"=\", slug)\n\t\t\t\t.selectAll()\n\t\t\t\t.executeTakeFirst();\n\t\t\tif (!existingRow) {\n\t\t\t\tthrow new SchemaError(`Collection \"${slug}\" not found`, \"COLLECTION_NOT_FOUND\");\n\t\t\t}\n\t\t\tconst existing = this.mapCollectionRow(existingRow);\n\t\t\tawait this.validateTitleDateFields(\n\t\t\t\texisting.id,\n\t\t\t\tslug,\n\t\t\t\t{\n\t\t\t\t\ttitleField: input.titleField,\n\t\t\t\t\tdateField: input.dateField,\n\t\t\t\t},\n\t\t\t\ttrx,\n\t\t\t);\n\t\t\tconst updates: Updateable<CollectionTable> = {};\n\n\t\t\tif (input.label !== undefined) updates.label = input.label;\n\t\t\tif (input.labelSingular !== undefined) updates.label_singular = input.labelSingular;\n\t\t\tif (input.description !== undefined) updates.description = input.description;\n\t\t\tif (input.icon !== undefined) updates.icon = input.icon;\n\t\t\tif (input.admin !== undefined) updates.admin_config = JSON.stringify(input.admin);\n\t\t\tif (input.supports !== undefined) updates.supports = JSON.stringify(input.supports);\n\t\t\tif (input.urlPattern !== undefined) updates.url_pattern = input.urlPattern;\n\t\t\tif (input.routable !== undefined) updates.routable = input.routable ? 1 : 0;\n\t\t\tif (input.hasSeo !== undefined) {\n\t\t\t\tupdates.has_seo = input.hasSeo ? 1 : 0;\n\t\t\t} else if (input.supports !== undefined) {\n\t\t\t\tupdates.has_seo = input.supports.includes(\"seo\") ? 1 : 0;\n\t\t\t}\n\t\t\tif (input.hidden !== undefined) updates.hidden = input.hidden ? 1 : 0;\n\t\t\tif (input.storage !== undefined) updates.storage = input.storage;\n\t\t\tif (input.sortOrder !== undefined) updates.sort_order = input.sortOrder;\n\t\t\tif (input.titleField !== undefined) updates.title_field = input.titleField || null;\n\t\t\tif (input.dateField !== undefined) updates.date_field = input.dateField || null;\n\t\t\tif (input.commentsEnabled !== undefined) {\n\t\t\t\tupdates.comments_enabled = input.commentsEnabled ? 1 : 0;\n\t\t\t}\n\t\t\tif (input.commentsModeration !== undefined) {\n\t\t\t\tupdates.comments_moderation = input.commentsModeration;\n\t\t\t}\n\t\t\tif (input.commentsClosedAfterDays !== undefined) {\n\t\t\t\tupdates.comments_closed_after_days = input.commentsClosedAfterDays;\n\t\t\t}\n\t\t\tif (input.commentsAutoApproveUsers !== undefined) {\n\t\t\t\tupdates.comments_auto_approve_users = input.commentsAutoApproveUsers ? 1 : 0;\n\t\t\t}\n\n\t\t\tupdates.updated_at = new Date().toISOString();\n\t\t\tawait trx\n\t\t\t\t.updateTable(\"_emdash_collections\")\n\t\t\t\t.set(updates)\n\t\t\t\t.where(\"id\", \"=\", existing.id)\n\t\t\t\t.execute();\n\n\t\t\tconst row = await trx\n\t\t\t\t.selectFrom(\"_emdash_collections\")\n\t\t\t\t.where(\"slug\", \"=\", slug)\n\t\t\t\t.selectAll()\n\t\t\t\t.executeTakeFirst();\n\n\t\t\tif (!row) {\n\t\t\t\tthrow new SchemaError(\"Failed to update collection\", \"UPDATE_FAILED\");\n\t\t\t}\n\n\t\t\t// Sync FTS state when the supports array changes (e.g. search toggled on/off)\n\t\t\tif (input.supports !== undefined) {\n\t\t\t\tconst hadSearch = existing.supports.includes(\"search\");\n\t\t\t\tconst hasSearch = parseSupports(row.supports).includes(\"search\");\n\t\t\t\tif (hadSearch !== hasSearch) {\n\t\t\t\t\tawait this.syncSearchState(slug, trx);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn this.mapCollectionRow(row);\n\t\t});\n\t}\n\n\t/**\n\t * Delete a collection\n\t */\n\tasync deleteCollection(slug: string, options?: { force?: boolean }): Promise<void> {\n\t\tconst existing = await this.getCollection(slug);\n\t\tif (existing && !options?.force && (await this.collectionHasContent(slug))) {\n\t\t\tthrow new SchemaError(\n\t\t\t\t`Collection \"${slug}\" has content. Use force: true to delete.`,\n\t\t\t\t\"COLLECTION_HAS_CONTENT\",\n\t\t\t);\n\t\t}\n\t\tconst activated = await deleteActivatedMediaUsageCollection(this.db, {\n\t\t\tcollectionId: existing?.id,\n\t\t\tcollectionSlug: slug,\n\t\t\tforceDelete: options?.force === true,\n\t\t});\n\t\tif (activated === \"has_content\") {\n\t\t\tthrow new SchemaError(\n\t\t\t\t`Collection \"${slug}\" has content. Use force: true to delete.`,\n\t\t\t\t\"COLLECTION_HAS_CONTENT\",\n\t\t\t);\n\t\t}\n\t\tif (activated === \"in_progress\") {\n\t\t\tthrow new SchemaError(`Collection \"${slug}\" deletion is already in progress`, \"CONFLICT\");\n\t\t}\n\t\tif (activated === \"deleted\") return;\n\t\tif (!existing) {\n\t\t\tthrow new SchemaError(`Collection \"${slug}\" not found`, \"COLLECTION_NOT_FOUND\");\n\t\t}\n\n\t\tlet contentTableDropped = false;\n\t\ttry {\n\t\t\tawait withTransaction(this.db, async (trx) => {\n\t\t\t\t// Drop FTS table and triggers before dropping the content table\n\t\t\t\tconst ftsManager = new FTSManager(trx);\n\t\t\t\tawait ftsManager.dropFtsTable(slug);\n\n\t\t\t\t// Drop the content table\n\t\t\t\tconst tableName = this.getTableName(slug);\n\t\t\t\tawait sql`DROP TABLE IF EXISTS ${sql.ref(tableName)}`.execute(trx);\n\t\t\t\tcontentTableDropped = true;\n\n\t\t\t\t// Delete the collection record (fields will cascade)\n\t\t\t\tawait trx.deleteFrom(\"_emdash_collections\").where(\"id\", \"=\", existing.id).execute();\n\t\t\t});\n\t\t\tawait deleteContentMediaUsageCollection(this.db, slug);\n\t\t} catch (error) {\n\t\t\tif (contentTableDropped && !(await tableExists(this.db, this.getTableName(slug)))) {\n\t\t\t\tawait deleteContentMediaUsageCollection(this.db, slug);\n\t\t\t}\n\t\t\tthrow error;\n\t\t}\n\t}\n\n\t// ============================================\n\t// Field Operations\n\t// ============================================\n\n\t/**\n\t * List fields for a collection\n\t */\n\tasync listFields(collectionId: string): Promise<Field[]> {\n\t\tconst rows = await this.db\n\t\t\t.selectFrom(\"_emdash_fields\")\n\t\t\t.where(\"collection_id\", \"=\", collectionId)\n\t\t\t.selectAll()\n\t\t\t.orderBy(\"sort_order\", \"asc\")\n\t\t\t.orderBy(\"created_at\", \"asc\")\n\t\t\t.execute();\n\n\t\treturn rows.map(this.mapFieldRow);\n\t}\n\n\t/**\n\t * Get a field by slug within a collection\n\t */\n\tasync getField(collectionSlug: string, fieldSlug: string): Promise<Field | null> {\n\t\tconst collection = await this.getCollection(collectionSlug);\n\t\tif (!collection) return null;\n\n\t\tconst row = await this.db\n\t\t\t.selectFrom(\"_emdash_fields\")\n\t\t\t.where(\"collection_id\", \"=\", collection.id)\n\t\t\t.where(\"slug\", \"=\", fieldSlug)\n\t\t\t.selectAll()\n\t\t\t.executeTakeFirst();\n\n\t\treturn row ? this.mapFieldRow(row) : null;\n\t}\n\n\t/**\n\t * Create a new field\n\t */\n\tasync createField(collectionSlug: string, input: CreateFieldInput): Promise<Field> {\n\t\tconst collection = await this.getCollection(collectionSlug);\n\t\tif (!collection) {\n\t\t\tthrow new SchemaError(`Collection \"${collectionSlug}\" not found`, \"COLLECTION_NOT_FOUND\");\n\t\t}\n\n\t\t// Validate slug\n\t\tthis.validateSlug(input.slug, \"field\");\n\t\tif (RESERVED_FIELD_SLUGS.includes(input.slug)) {\n\t\t\tthrow new SchemaError(`Field slug \"${input.slug}\" is reserved`, \"RESERVED_SLUG\");\n\t\t}\n\n\t\t// Check if field already exists\n\t\tconst existing = await this.getField(collectionSlug, input.slug);\n\t\tif (existing) {\n\t\t\tthrow new SchemaError(\n\t\t\t\t`Field \"${input.slug}\" already exists in collection \"${collectionSlug}\"`,\n\t\t\t\t\"FIELD_EXISTS\",\n\t\t\t);\n\t\t}\n\n\t\tconst id = ulid();\n\t\tconst columnType = FIELD_TYPE_TO_COLUMN[input.type];\n\t\tassertIndexableField(input.type, input.indexed, input.slug);\n\n\t\t// Get max sort order\n\t\tconst maxSort = await this.db\n\t\t\t.selectFrom(\"_emdash_fields\")\n\t\t\t.where(\"collection_id\", \"=\", collection.id)\n\t\t\t.select((eb) => eb.fn.max<number>(\"sort_order\").as(\"max\"))\n\t\t\t.executeTakeFirst();\n\n\t\tconst sortOrder = input.sortOrder ?? (maxSort?.max ?? -1) + 1;\n\t\tconst activeCoverageInvalidated = await invalidateContentMediaUsageSchemaChange(\n\t\t\tthis.db,\n\t\t\tcollectionSlug,\n\t\t);\n\n\t\tlet schemaMutated = false;\n\t\ttry {\n\t\t\tconst created = await withTransaction(this.db, async (trx) => {\n\t\t\t\t// Insert field record\n\t\t\t\tawait trx\n\t\t\t\t\t.insertInto(\"_emdash_fields\")\n\t\t\t\t\t.values({\n\t\t\t\t\t\tid,\n\t\t\t\t\t\tcollection_id: collection.id,\n\t\t\t\t\t\tslug: input.slug,\n\t\t\t\t\t\tlabel: input.label,\n\t\t\t\t\t\ttype: input.type,\n\t\t\t\t\t\tcolumn_type: columnType,\n\t\t\t\t\t\trequired: input.required ? 1 : 0,\n\t\t\t\t\t\tunique: input.unique ? 1 : 0,\n\t\t\t\t\t\tdefault_value:\n\t\t\t\t\t\t\tinput.defaultValue !== undefined ? JSON.stringify(input.defaultValue) : null,\n\t\t\t\t\t\tvalidation: input.validation ? JSON.stringify(input.validation) : null,\n\t\t\t\t\t\twidget: input.widget ?? null,\n\t\t\t\t\t\toptions: input.options ? JSON.stringify(input.options) : null,\n\t\t\t\t\t\tsort_order: sortOrder,\n\t\t\t\t\t\tsearchable: input.searchable ? 1 : 0,\n\t\t\t\t\t\tindexed: input.indexed ? 1 : 0,\n\t\t\t\t\t\ttranslatable: input.translatable === false ? 0 : 1,\n\t\t\t\t\t})\n\t\t\t\t\t.execute();\n\t\t\t\tschemaMutated = true;\n\n\t\t\t\t// Add column to content table — pass trx to stay on the same connection\n\t\t\t\tawait this.addColumn(\n\t\t\t\t\tcollectionSlug,\n\t\t\t\t\tinput.slug,\n\t\t\t\t\tinput.type,\n\t\t\t\t\t{\n\t\t\t\t\t\trequired: input.required,\n\t\t\t\t\t\tdefaultValue: input.defaultValue,\n\t\t\t\t\t},\n\t\t\t\t\ttrx,\n\t\t\t\t);\n\n\t\t\t\tif (input.indexed) {\n\t\t\t\t\tawait this.createFieldIndex(collectionSlug, id, input.slug, trx);\n\t\t\t\t}\n\n\t\t\t\t// Read the created field via trx (not this.db) to avoid connection mutex deadlock\n\t\t\t\tconst fieldRow = await trx\n\t\t\t\t\t.selectFrom(\"_emdash_fields\")\n\t\t\t\t\t.where(\"collection_id\", \"=\", collection.id)\n\t\t\t\t\t.where(\"slug\", \"=\", input.slug)\n\t\t\t\t\t.selectAll()\n\t\t\t\t\t.executeTakeFirst();\n\n\t\t\t\tif (!fieldRow) {\n\t\t\t\t\tthrow new SchemaError(\"Failed to create field\", \"CREATE_FAILED\");\n\t\t\t\t}\n\n\t\t\t\tconst field = this.mapFieldRow(fieldRow);\n\n\t\t\t\t// Sync search state if this field is searchable; support checks are handled by syncSearchState()\n\t\t\t\tif (input.searchable) {\n\t\t\t\t\tawait this.syncSearchState(collectionSlug, trx);\n\t\t\t\t}\n\n\t\t\t\treturn field;\n\t\t\t});\n\t\t\tif (activeCoverageInvalidated) {\n\t\t\t\tawait invalidateContentMediaUsageSchemaChange(this.db, collectionSlug);\n\t\t\t} else {\n\t\t\t\tawait markContentMediaUsageCollectionStaleSafely(\n\t\t\t\t\tthis.db,\n\t\t\t\t\tcollectionSlug,\n\t\t\t\t\t\"CONTENT_USAGE_STALE\",\n\t\t\t\t);\n\t\t\t}\n\t\t\treturn created;\n\t\t} catch (error) {\n\t\t\tif (schemaMutated) {\n\t\t\t\tif (activeCoverageInvalidated) {\n\t\t\t\t\tawait invalidateContentMediaUsageSchemaChange(this.db, collectionSlug);\n\t\t\t\t} else {\n\t\t\t\t\tawait markContentMediaUsageCollectionStaleSafely(\n\t\t\t\t\t\tthis.db,\n\t\t\t\t\t\tcollectionSlug,\n\t\t\t\t\t\t\"CONTENT_USAGE_STALE\",\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\t\t\tthrow error;\n\t\t}\n\t}\n\n\t/**\n\t * Update a field\n\t */\n\tasync updateField(\n\t\tcollectionSlug: string,\n\t\tfieldSlug: string,\n\t\tinput: UpdateFieldInput,\n\t): Promise<Field> {\n\t\tlet activeCoverageInvalidated = false;\n\t\tlet schemaMutated = false;\n\t\ttry {\n\t\t\tconst updatedField = await withTransaction(this.db, async (trx) => {\n\t\t\t\tconst collectionRow = await trx\n\t\t\t\t\t.selectFrom(\"_emdash_collections\")\n\t\t\t\t\t.where(\"slug\", \"=\", collectionSlug)\n\t\t\t\t\t.select([\"id\", \"title_field\", \"date_field\"])\n\t\t\t\t\t.executeTakeFirst();\n\t\t\t\tconst fieldRow = collectionRow\n\t\t\t\t\t? await trx\n\t\t\t\t\t\t\t.selectFrom(\"_emdash_fields\")\n\t\t\t\t\t\t\t.where(\"collection_id\", \"=\", collectionRow.id)\n\t\t\t\t\t\t\t.where(\"slug\", \"=\", fieldSlug)\n\t\t\t\t\t\t\t.selectAll()\n\t\t\t\t\t\t\t.executeTakeFirst()\n\t\t\t\t\t: undefined;\n\t\t\t\tif (!fieldRow) {\n\t\t\t\t\tthrow new SchemaError(\n\t\t\t\t\t\t`Field \"${fieldSlug}\" not found in collection \"${collectionSlug}\"`,\n\t\t\t\t\t\t\"FIELD_NOT_FOUND\",\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tconst field = this.mapFieldRow(fieldRow);\n\t\t\t\tconst updates: Updateable<FieldTable> = {};\n\t\t\t\tlet nextType = field.type;\n\n\t\t\t\tif (input.type !== undefined && input.type !== field.type) {\n\t\t\t\t\tconst newColumnType = FIELD_TYPE_TO_COLUMN[input.type];\n\t\t\t\t\tif (newColumnType !== field.columnType) {\n\t\t\t\t\t\tthrow new SchemaError(\n\t\t\t\t\t\t\t`Cannot change field \"${fieldSlug}\" in collection \"${collectionSlug}\" from type ` +\n\t\t\t\t\t\t\t\t`\"${field.type}\" to \"${input.type}\": the underlying column type would change from ` +\n\t\t\t\t\t\t\t\t`${field.columnType} to ${newColumnType}, which requires a manual content migration.`,\n\t\t\t\t\t\t\t\"FIELD_TYPE_COLUMN_CHANGE\",\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t\tif (!TEXT_ALIAS_FIELD_TYPES.has(field.type) || !TEXT_ALIAS_FIELD_TYPES.has(input.type)) {\n\t\t\t\t\t\tthrow new SchemaError(\n\t\t\t\t\t\t\t`Cannot change field \"${fieldSlug}\" in collection \"${collectionSlug}\" from type ` +\n\t\t\t\t\t\t\t\t`\"${field.type}\" to \"${input.type}\" without a manual content migration.`,\n\t\t\t\t\t\t\t\"FIELD_TYPE_CHANGE_REQUIRES_MIGRATION\",\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t\tif (collectionRow?.title_field === fieldSlug && !TITLE_FIELD_TYPES.has(input.type)) {\n\t\t\t\t\t\tthrow new SchemaError(\n\t\t\t\t\t\t\t`titleField \"${fieldSlug}\" must stay a text field, not \"${input.type}\"`,\n\t\t\t\t\t\t\t\"INVALID_TITLE_FIELD\",\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t\tif (collectionRow?.date_field === fieldSlug && input.type !== \"datetime\") {\n\t\t\t\t\t\tthrow new SchemaError(\n\t\t\t\t\t\t\t`dateField \"${fieldSlug}\" must stay a datetime field, not \"${input.type}\"`,\n\t\t\t\t\t\t\t\"INVALID_DATE_FIELD\",\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t\tnextType = input.type;\n\t\t\t\t\tupdates.type = input.type;\n\t\t\t\t\tupdates.column_type = newColumnType;\n\t\t\t\t}\n\n\t\t\t\tif (input.required !== undefined && input.required !== field.required) {\n\t\t\t\t\tthrow new SchemaError(\n\t\t\t\t\t\t`Changing required for field \"${fieldSlug}\" requires a manual content migration.`,\n\t\t\t\t\t\t\"FIELD_UPDATE_REQUIRES_MIGRATION\",\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tif (input.unique !== undefined && input.unique !== field.unique) {\n\t\t\t\t\tthrow new SchemaError(\n\t\t\t\t\t\t`Changing unique for field \"${fieldSlug}\" requires a manual content migration.`,\n\t\t\t\t\t\t\"FIELD_UPDATE_REQUIRES_MIGRATION\",\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tif (input.translatable === false && field.translatable) {\n\t\t\t\t\tthrow new SchemaError(\n\t\t\t\t\t\t`Changing field \"${fieldSlug}\" to non-translatable requires a manual content migration.`,\n\t\t\t\t\t\t\"FIELD_UPDATE_REQUIRES_MIGRATION\",\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\tif (input.label !== undefined) updates.label = input.label;\n\t\t\t\tif (input.required !== undefined) updates.required = input.required ? 1 : 0;\n\t\t\t\tif (input.unique !== undefined) updates.unique = input.unique ? 1 : 0;\n\t\t\t\tif (input.searchable !== undefined) updates.searchable = input.searchable ? 1 : 0;\n\t\t\t\tconst indexedChanged = input.indexed !== undefined && input.indexed !== field.indexed;\n\t\t\t\tif (indexedChanged) updates.indexed = input.indexed ? 1 : 0;\n\t\t\t\tif (input.translatable !== undefined) {\n\t\t\t\t\tupdates.translatable = input.translatable ? 1 : 0;\n\t\t\t\t}\n\t\t\t\tif (input.defaultValue !== undefined) {\n\t\t\t\t\tupdates.default_value = JSON.stringify(input.defaultValue);\n\t\t\t\t}\n\t\t\t\tif (input.validation !== undefined) {\n\t\t\t\t\tupdates.validation = input.validation ? JSON.stringify(input.validation) : null;\n\t\t\t\t}\n\t\t\t\tif (input.widget !== undefined) updates.widget = input.widget;\n\t\t\t\tif (input.options !== undefined) updates.options = JSON.stringify(input.options);\n\t\t\t\tif (input.sortOrder !== undefined) updates.sort_order = input.sortOrder;\n\n\t\t\t\tassertIndexableField(nextType, input.indexed ?? field.indexed, fieldSlug);\n\t\t\t\tif (Object.keys(updates).length === 0) return field;\n\n\t\t\t\tactiveCoverageInvalidated = await invalidateContentMediaUsageSchemaChange(\n\t\t\t\t\ttrx,\n\t\t\t\t\tcollectionSlug,\n\t\t\t\t);\n\n\t\t\t\tif (Object.keys(updates).length > 0) {\n\t\t\t\t\tawait trx.updateTable(\"_emdash_fields\").set(updates).where(\"id\", \"=\", field.id).execute();\n\t\t\t\t\tschemaMutated = true;\n\t\t\t\t}\n\n\t\t\t\tif (indexedChanged) {\n\t\t\t\t\tif (input.indexed) {\n\t\t\t\t\t\tawait this.createFieldIndex(collectionSlug, field.id, fieldSlug, trx);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tawait this.dropFieldIndex(field.id, trx);\n\t\t\t\t\t}\n\t\t\t\t\tschemaMutated = true;\n\t\t\t\t}\n\n\t\t\t\t// Read the updated field via trx (not this.db) to avoid connection mutex deadlock\n\t\t\t\tconst updatedRow = await trx\n\t\t\t\t\t.selectFrom(\"_emdash_fields\")\n\t\t\t\t\t.where(\"collection_id\", \"=\", field.collectionId)\n\t\t\t\t\t.where(\"slug\", \"=\", fieldSlug)\n\t\t\t\t\t.selectAll()\n\t\t\t\t\t.executeTakeFirst();\n\n\t\t\t\tif (!updatedRow) {\n\t\t\t\t\tthrow new SchemaError(\"Failed to update field\", \"UPDATE_FAILED\");\n\t\t\t\t}\n\n\t\t\t\tconst updated = this.mapFieldRow(updatedRow);\n\n\t\t\t\t// If searchable changed, sync FTS state for this collection\n\t\t\t\tconst searchableChanged =\n\t\t\t\t\tschemaMutated && input.searchable !== undefined && input.searchable !== field.searchable;\n\t\t\t\tif (searchableChanged) {\n\t\t\t\t\tawait this.syncSearchState(collectionSlug, trx);\n\t\t\t\t}\n\n\t\t\t\treturn updated;\n\t\t\t});\n\t\t\tif (schemaMutated) {\n\t\t\t\tif (activeCoverageInvalidated) {\n\t\t\t\t\tawait invalidateContentMediaUsageSchemaChange(this.db, collectionSlug);\n\t\t\t\t} else {\n\t\t\t\t\tawait markContentMediaUsageCollectionStaleSafely(\n\t\t\t\t\t\tthis.db,\n\t\t\t\t\t\tcollectionSlug,\n\t\t\t\t\t\t\"CONTENT_USAGE_STALE\",\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn updatedField;\n\t\t} catch (error) {\n\t\t\tif (schemaMutated) {\n\t\t\t\tif (activeCoverageInvalidated) {\n\t\t\t\t\tawait invalidateContentMediaUsageSchemaChange(this.db, collectionSlug);\n\t\t\t\t} else {\n\t\t\t\t\tawait markContentMediaUsageCollectionStaleSafely(\n\t\t\t\t\t\tthis.db,\n\t\t\t\t\t\tcollectionSlug,\n\t\t\t\t\t\t\"CONTENT_USAGE_STALE\",\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\t\t\tthrow error;\n\t\t}\n\t}\n\n\t/**\n\t * Synchronize an existing FTS index with the collection's current state.\n\t *\n\t * Only rebuilds or disables — never first-time enables. First-time FTS\n\t * enablement is handled by the seed's explicit enableSearch call (which\n\t * is try-caught) or the admin UI toggle.\n\t *\n\t * - FTS active + still has search support and searchable fields → rebuild\n\t * - FTS active + lost search support or no searchable fields    → disable\n\t * - FTS not active                                              → no-op\n\t *\n\t * Pass `db` when calling from within a transaction so FTS operations\n\t * participate in the same transaction and are rolled back on failure.\n\t */\n\tprivate async syncSearchState(collectionSlug: string, db?: Kysely<Database>): Promise<void> {\n\t\tconst conn = db ?? this.db;\n\t\tconst ftsManager = new FTSManager(conn);\n\n\t\t// Query via conn (not this.db) to avoid connection mutex deadlock when called inside a transaction\n\t\tconst row = await conn\n\t\t\t.selectFrom(\"_emdash_collections\")\n\t\t\t.where(\"slug\", \"=\", collectionSlug)\n\t\t\t.select(\"supports\")\n\t\t\t.executeTakeFirst();\n\t\tif (!row) return;\n\n\t\tconst wantsSearch = parseSupports(row.supports).includes(\"search\");\n\t\tconst searchableFields = await ftsManager.getSearchableFields(collectionSlug);\n\t\tconst config = await ftsManager.getSearchConfig(collectionSlug);\n\t\tconst ftsActive = config?.enabled === true;\n\n\t\tif (wantsSearch && searchableFields.length > 0 && ftsActive) {\n\t\t\tawait ftsManager.rebuildIndex(\n\t\t\t\tcollectionSlug,\n\t\t\t\tsearchableFields,\n\t\t\t\tconfig?.weights,\n\t\t\t\tconfig?.tokenize,\n\t\t\t);\n\t\t} else if (ftsActive && (!wantsSearch || searchableFields.length === 0)) {\n\t\t\tawait ftsManager.disableSearch(collectionSlug);\n\t\t}\n\t}\n\n\t/**\n\t * Delete a field\n\t */\n\tasync deleteField(collectionSlug: string, fieldSlug: string): Promise<void> {\n\t\tconst field = await this.getField(collectionSlug, fieldSlug);\n\t\tif (!field) {\n\t\t\tthrow new SchemaError(\n\t\t\t\t`Field \"${fieldSlug}\" not found in collection \"${collectionSlug}\"`,\n\t\t\t\t\"FIELD_NOT_FOUND\",\n\t\t\t);\n\t\t}\n\t\tconst activeCoverageInvalidated = await invalidateContentMediaUsageSchemaChange(\n\t\t\tthis.db,\n\t\t\tcollectionSlug,\n\t\t);\n\n\t\t// If this field powers the collection's titleField/dateField,\n\t\t// clear that reference in the same transaction — otherwise the metadata\n\t\t// would point at a dropped column and later crash the content list sort.\n\t\tconst collection = await this.getCollection(collectionSlug);\n\t\tconst clearTitle = collection?.titleField === fieldSlug;\n\t\tconst clearDate = collection?.dateField === fieldSlug;\n\n\t\tlet schemaMutated = false;\n\t\ttry {\n\t\t\tawait withTransaction(this.db, async (trx) => {\n\t\t\t\t// Delete the field record first so syncSearchState sees the updated field list.\n\t\t\t\t// This ordering matters for searchable fields: SQLite prevents dropping a column\n\t\t\t\t// that is still referenced by a trigger. syncSearchState drops and recreates the\n\t\t\t\t// FTS triggers based on the remaining searchable fields, clearing the dependency\n\t\t\t\t// before we attempt the ALTER TABLE DROP COLUMN below.\n\t\t\t\tawait trx.deleteFrom(\"_emdash_fields\").where(\"id\", \"=\", field.id).execute();\n\t\t\t\tschemaMutated = true;\n\n\t\t\t\tif (clearTitle || clearDate) {\n\t\t\t\t\tawait trx\n\t\t\t\t\t\t.updateTable(\"_emdash_collections\")\n\t\t\t\t\t\t.set({\n\t\t\t\t\t\t\t...(clearTitle ? { title_field: null } : {}),\n\t\t\t\t\t\t\t...(clearDate ? { date_field: null } : {}),\n\t\t\t\t\t\t\tupdated_at: new Date().toISOString(),\n\t\t\t\t\t\t})\n\t\t\t\t\t\t.where(\"slug\", \"=\", collectionSlug)\n\t\t\t\t\t\t.execute();\n\t\t\t\t}\n\n\t\t\t\t// If the deleted field was searchable, sync FTS state (removes old triggers)\n\t\t\t\tif (field.searchable) {\n\t\t\t\t\tawait this.syncSearchState(collectionSlug, trx);\n\t\t\t\t}\n\n\t\t\t\tif (field.indexed) {\n\t\t\t\t\tawait this.dropFieldIndex(field.id, trx);\n\t\t\t\t}\n\n\t\t\t\t// Drop column from content table — safe now because FTS triggers are gone\n\t\t\t\tawait this.dropColumn(collectionSlug, fieldSlug, trx);\n\t\t\t});\n\t\t\tif (activeCoverageInvalidated) {\n\t\t\t\tawait invalidateContentMediaUsageSchemaChange(this.db, collectionSlug);\n\t\t\t} else {\n\t\t\t\tawait markContentMediaUsageCollectionStaleSafely(\n\t\t\t\t\tthis.db,\n\t\t\t\t\tcollectionSlug,\n\t\t\t\t\t\"CONTENT_USAGE_STALE\",\n\t\t\t\t);\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tif (schemaMutated) {\n\t\t\t\tif (activeCoverageInvalidated) {\n\t\t\t\t\tawait invalidateContentMediaUsageSchemaChange(this.db, collectionSlug);\n\t\t\t\t} else {\n\t\t\t\t\tawait markContentMediaUsageCollectionStaleSafely(\n\t\t\t\t\t\tthis.db,\n\t\t\t\t\t\tcollectionSlug,\n\t\t\t\t\t\t\"CONTENT_USAGE_STALE\",\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\t\t\tthrow error;\n\t\t}\n\t}\n\n\t/**\n\t * Reorder collections in the admin sidebar.\n\t *\n\t * `slugs` is the full desired order: every listed collection gets its\n\t * index as `sort_order`, and any collection left out has its explicit\n\t * position cleared, dropping it back to the alphabetical tail. Unknown or\n\t * duplicate slugs throw before anything is written.\n\t */\n\tasync reorderCollections(slugs: string[]): Promise<void> {\n\t\tconst known = new Set((await this.listCollections()).map((collection) => collection.slug));\n\n\t\tconst unknown = slugs.filter((slug) => !known.has(slug));\n\t\tif (unknown.length > 0) {\n\t\t\tthrow new SchemaError(\n\t\t\t\t`Unknown collection${unknown.length > 1 ? \"s\" : \"\"}: ${unknown.join(\", \")}`,\n\t\t\t\t\"COLLECTION_NOT_FOUND\",\n\t\t\t\t{ slugs: unknown },\n\t\t\t);\n\t\t}\n\n\t\tconst duplicates = slugs.filter((slug, index) => slugs.indexOf(slug) !== index);\n\t\tif (duplicates.length > 0) {\n\t\t\tthrow new SchemaError(\n\t\t\t\t`Duplicate collection${duplicates.length > 1 ? \"s\" : \"\"}: ${[...new Set(duplicates)].join(\", \")}`,\n\t\t\t\t\"DUPLICATE_SLUG\",\n\t\t\t\t{ slugs: [...new Set(duplicates)] },\n\t\t\t);\n\t\t}\n\n\t\tconst now = new Date().toISOString();\n\t\tconst ordered = new Set(slugs);\n\n\t\tawait withTransaction(this.db, async (trx) => {\n\t\t\tfor (const [index, slug] of slugs.entries()) {\n\t\t\t\tawait trx\n\t\t\t\t\t.updateTable(\"_emdash_collections\")\n\t\t\t\t\t.set({ sort_order: index, updated_at: now })\n\t\t\t\t\t.where(\"slug\", \"=\", slug)\n\t\t\t\t\t.execute();\n\t\t\t}\n\n\t\t\tconst cleared = [...known].filter((slug) => !ordered.has(slug));\n\t\t\t// Chunked to stay under D1's bound-parameter limit; typical sites\n\t\t\t// clear far fewer than one chunk.\n\t\t\tfor (const slugChunk of chunks(cleared, SQL_BATCH_SIZE)) {\n\t\t\t\tawait trx\n\t\t\t\t\t.updateTable(\"_emdash_collections\")\n\t\t\t\t\t.set({ sort_order: null, updated_at: now })\n\t\t\t\t\t.where(\"slug\", \"in\", slugChunk)\n\t\t\t\t\t.execute();\n\t\t\t}\n\t\t});\n\t}\n\n\t/**\n\t * Reorder fields\n\t */\n\tasync reorderFields(collectionSlug: string, fieldSlugs: string[]): Promise<void> {\n\t\tconst collection = await this.getCollection(collectionSlug);\n\t\tif (!collection) {\n\t\t\tthrow new SchemaError(`Collection \"${collectionSlug}\" not found`, \"COLLECTION_NOT_FOUND\");\n\t\t}\n\n\t\t// Update sort_order for each field\n\t\tfor (let i = 0; i < fieldSlugs.length; i++) {\n\t\t\tawait this.db\n\t\t\t\t.updateTable(\"_emdash_fields\")\n\t\t\t\t.set({ sort_order: i })\n\t\t\t\t.where(\"collection_id\", \"=\", collection.id)\n\t\t\t\t.where(\"slug\", \"=\", fieldSlugs[i])\n\t\t\t\t.execute();\n\t\t}\n\t}\n\n\t// ============================================\n\t// DDL Operations\n\t// ============================================\n\n\t/**\n\t * Create a content table for a collection\n\t */\n\tprivate async createContentTable(\n\t\tslug: string,\n\t\tdb?: Kysely<Database>,\n\t\tfields: readonly CreateFieldInput[] = [],\n\t\toptions: { ifNotExists?: boolean } = {},\n\t): Promise<void> {\n\t\tconst conn = db ?? this.db;\n\t\tconst tableName = this.getTableName(slug);\n\n\t\tlet table: CreateTableBuilder<string, string> = conn.schema\n\t\t\t.createTable(tableName)\n\t\t\t.addColumn(\"id\", \"text\", (col) => col.primaryKey())\n\t\t\t.addColumn(\"slug\", \"text\")\n\t\t\t.addColumn(\"status\", \"text\", (col) => col.defaultTo(\"draft\"))\n\t\t\t.addColumn(\"author_id\", \"text\")\n\t\t\t.addColumn(\"primary_byline_id\", \"text\")\n\t\t\t.addColumn(\"created_at\", \"text\", (col) => col.defaultTo(currentTimestamp(conn)))\n\t\t\t.addColumn(\"updated_at\", \"text\", (col) => col.defaultTo(currentTimestamp(conn)))\n\t\t\t.addColumn(\"published_at\", \"text\")\n\t\t\t.addColumn(\"scheduled_at\", \"text\")\n\t\t\t.addColumn(\"deleted_at\", \"text\")\n\t\t\t.addColumn(\"version\", \"integer\", (col) => col.defaultTo(1))\n\t\t\t.addColumn(\"live_revision_id\", \"text\", (col) => col.references(\"revisions.id\"))\n\t\t\t.addColumn(\"draft_revision_id\", \"text\", (col) => col.references(\"revisions.id\"))\n\t\t\t.addColumn(\"locale\", \"text\", (col) => col.notNull().defaultTo(\"en\"))\n\t\t\t.addColumn(\"translation_group\", \"text\");\n\t\tif (options.ifNotExists) table = table.ifNotExists();\n\n\t\tfor (const field of fields) {\n\t\t\tconst columnName = this.getColumnName(field.slug);\n\t\t\tconst columnType = COLUMN_TYPE_TO_DATA_TYPE[FIELD_TYPE_TO_COLUMN[field.type]];\n\t\t\ttable = table.addColumn(columnName, columnType, (column) => {\n\t\t\t\tif (!field.required) return column;\n\n\t\t\t\tconst defaultValue =\n\t\t\t\t\tfield.defaultValue !== undefined\n\t\t\t\t\t\t? this.formatDefaultValue(field.defaultValue, field.type)\n\t\t\t\t\t\t: this.getEmptyDefault(field.type);\n\t\t\t\treturn column.notNull().defaultTo(sql.raw(defaultValue));\n\t\t\t});\n\t\t}\n\n\t\tawait table\n\t\t\t.addUniqueConstraint(`${tableName}_slug_locale_unique`, [\"slug\", \"locale\"])\n\t\t\t.execute();\n\n\t\tconst createIndex = options.ifNotExists ? sql`CREATE INDEX IF NOT EXISTS` : sql`CREATE INDEX`;\n\n\t\t// Create standard indexes\n\t\tawait sql`\n\t\t\t${createIndex} ${sql.ref(`idx_${tableName}_slug`)}\n\t\t\tON ${sql.ref(tableName)} (slug)\n\t\t`.execute(conn);\n\n\t\tawait sql`\n\t\t\t${createIndex} ${sql.ref(`idx_${tableName}_scheduled`)}\n\t\t\tON ${sql.ref(tableName)} (scheduled_at)\n\t\t\tWHERE scheduled_at IS NOT NULL\n\t\t`.execute(conn);\n\n\t\tawait sql`\n\t\t\t${createIndex} ${sql.ref(`idx_${tableName}_live_revision`)}\n\t\t\tON ${sql.ref(tableName)} (live_revision_id)\n\t\t`.execute(conn);\n\n\t\tawait sql`\n\t\t\t${createIndex} ${sql.ref(`idx_${tableName}_draft_revision`)}\n\t\t\tON ${sql.ref(tableName)} (draft_revision_id)\n\t\t`.execute(conn);\n\n\t\tawait sql`\n\t\t\t${createIndex} ${sql.ref(`idx_${tableName}_author`)}\n\t\t\tON ${sql.ref(tableName)} (author_id)\n\t\t`.execute(conn);\n\n\t\tawait sql`\n\t\t\t${createIndex} ${sql.ref(`idx_${tableName}_primary_byline`)}\n\t\t\tON ${sql.ref(tableName)} (primary_byline_id)\n\t\t`.execute(conn);\n\n\t\tawait sql`\n\t\t\t${createIndex} ${sql.ref(`idx_${tableName}_locale`)}\n\t\t\tON ${sql.ref(tableName)} (locale)\n\t\t`.execute(conn);\n\n\t\t// Names must stay identical to migration 055, which creates these indexes\n\t\t// on tables that already exist. Lookups that don't constrain `deleted_at`\n\t\t// (menu and reference resolution) need the first; reads that do need the\n\t\t// second.\n\t\tawait sql`\n\t\t\t${createIndex} ${sql.ref(`idx_${tableName}_tg_locale`)}\n\t\t\tON ${sql.ref(tableName)} (translation_group, locale)\n\t\t`.execute(conn);\n\n\t\tawait sql`\n\t\t\t${createIndex} ${sql.ref(`idx_${tableName}_del_tg_locale`)}\n\t\t\tON ${sql.ref(tableName)} (deleted_at, translation_group, locale)\n\t\t`.execute(conn);\n\n\t\t// Composite indexes for optimized query performance (see migration 033)\n\t\tawait sql`\n\t\t\t${createIndex} ${sql.ref(`idx_${tableName}_deleted_updated_id`)}\n\t\t\tON ${sql.ref(tableName)} (deleted_at, updated_at DESC, id DESC)\n\t\t`.execute(conn);\n\n\t\tawait sql`\n\t\t\t${createIndex} ${sql.ref(`idx_${tableName}_deleted_status`)}\n\t\t\tON ${sql.ref(tableName)} (deleted_at, status)\n\t\t`.execute(conn);\n\n\t\tawait sql`\n\t\t\t${createIndex} ${sql.ref(`idx_${tableName}_deleted_created_id`)}\n\t\t\tON ${sql.ref(tableName)} (deleted_at, created_at DESC, id DESC)\n\t\t`.execute(conn);\n\n\t\tawait sql`\n\t\t\t${createIndex} ${sql.ref(`idx_${tableName}_deleted_published_id`)}\n\t\t\tON ${sql.ref(tableName)} (deleted_at, published_at DESC, id DESC)\n\t\t`.execute(conn);\n\n\t\t// Locale-aware composite indexes for i18n content lists (see migration 041).\n\t\t// Short `loc_upd`/`loc_crt` suffix keeps the updated/created discriminator\n\t\t// inside Postgres's 63-byte identifier limit for long slugs; keep these\n\t\t// names identical to migration 041.\n\t\tawait sql`\n\t\t\t${createIndex} ${sql.ref(`idx_${tableName}_loc_upd`)}\n\t\t\tON ${sql.ref(tableName)} (deleted_at, locale, updated_at DESC, id DESC)\n\t\t`.execute(conn);\n\n\t\tawait sql`\n\t\t\t${createIndex} ${sql.ref(`idx_${tableName}_loc_crt`)}\n\t\t\tON ${sql.ref(tableName)} (deleted_at, locale, created_at DESC, id DESC)\n\t\t`.execute(conn);\n\t}\n\n\tprivate getFieldIndexName(fieldId: string): string {\n\t\tif (!FIELD_ID_PATTERN.test(fieldId)) {\n\t\t\tthrow new SchemaError(`Invalid field id \"${fieldId}\"`, \"INVALID_FIELD_ID\");\n\t\t}\n\t\treturn `idx_cf_${fieldId.toLowerCase()}`;\n\t}\n\n\tprivate getLocaleFieldIndexName(fieldId: string): string {\n\t\treturn `${this.getFieldIndexName(fieldId)}_loc`;\n\t}\n\n\tprivate async createFieldIndex(\n\t\tcollectionSlug: string,\n\t\tfieldId: string,\n\t\tfieldSlug: string,\n\t\tdb?: Kysely<Database>,\n\t): Promise<void> {\n\t\tconst conn = db ?? this.db;\n\t\tconst tableName = this.getTableName(collectionSlug);\n\t\tconst columnName = this.getColumnName(fieldSlug);\n\t\tconst indexName = this.getFieldIndexName(fieldId);\n\t\tconst localeIndexName = this.getLocaleFieldIndexName(fieldId);\n\n\t\tawait sql`\n\t\t\tCREATE INDEX IF NOT EXISTS ${sql.ref(indexName)}\n\t\t\tON ${sql.ref(tableName)} (\n\t\t\t\t(${sql.ref(columnName)} IS NOT NULL),\n\t\t\t\t${sql.ref(columnName)},\n\t\t\t\tid\n\t\t\t)\n\t\t\tWHERE deleted_at IS NULL\n\t\t`.execute(conn);\n\n\t\tawait sql`\n\t\t\tCREATE INDEX IF NOT EXISTS ${sql.ref(localeIndexName)}\n\t\t\tON ${sql.ref(tableName)} (\n\t\t\t\tlocale,\n\t\t\t\t(${sql.ref(columnName)} IS NOT NULL),\n\t\t\t\t${sql.ref(columnName)},\n\t\t\t\tid\n\t\t\t)\n\t\t\tWHERE deleted_at IS NULL\n\t\t`.execute(conn);\n\t}\n\n\tprivate async dropFieldIndex(fieldId: string, db?: Kysely<Database>): Promise<void> {\n\t\tconst conn = db ?? this.db;\n\t\tawait sql`DROP INDEX IF EXISTS ${sql.ref(this.getFieldIndexName(fieldId))}`.execute(conn);\n\t\tawait sql`DROP INDEX IF EXISTS ${sql.ref(this.getLocaleFieldIndexName(fieldId))}`.execute(conn);\n\t}\n\n\t/**\n\t * Add a column to a content table\n\t */\n\tprivate async addColumn(\n\t\tcollectionSlug: string,\n\t\tfieldSlug: string,\n\t\tfieldType: FieldType,\n\t\toptions?: { required?: boolean; defaultValue?: unknown },\n\t\tdb?: Kysely<Database>,\n\t): Promise<void> {\n\t\tconst conn = db ?? this.db;\n\t\tconst tableName = this.getTableName(collectionSlug);\n\t\tconst columnType = FIELD_TYPE_TO_COLUMN[fieldType];\n\t\tconst columnName = this.getColumnName(fieldSlug);\n\n\t\t// Build ALTER TABLE statement\n\t\t// Note: SQLite requires DEFAULT for NOT NULL columns in ALTER TABLE\n\t\tif (options?.required && options?.defaultValue !== undefined) {\n\t\t\tconst defaultVal = this.formatDefaultValue(options.defaultValue, fieldType);\n\t\t\tawait sql`\n\t\t\t\tALTER TABLE ${sql.ref(tableName)}\n\t\t\t\tADD COLUMN ${sql.ref(columnName)} ${sql.raw(columnType)} NOT NULL DEFAULT ${sql.raw(defaultVal)}\n\t\t\t`.execute(conn);\n\t\t} else if (options?.required) {\n\t\t\t// For required fields without default, use empty string/0 as default\n\t\t\tconst defaultVal = this.getEmptyDefault(fieldType);\n\t\t\tawait sql`\n\t\t\t\tALTER TABLE ${sql.ref(tableName)}\n\t\t\t\tADD COLUMN ${sql.ref(columnName)} ${sql.raw(columnType)} NOT NULL DEFAULT ${sql.raw(defaultVal)}\n\t\t\t`.execute(conn);\n\t\t} else {\n\t\t\tawait sql`\n\t\t\t\tALTER TABLE ${sql.ref(tableName)}\n\t\t\t\tADD COLUMN ${sql.ref(columnName)} ${sql.raw(columnType)}\n\t\t\t`.execute(conn);\n\t\t}\n\t}\n\n\t/**\n\t * Drop a column from a content table\n\t */\n\tprivate async dropColumn(\n\t\tcollectionSlug: string,\n\t\tfieldSlug: string,\n\t\tdb?: Kysely<Database>,\n\t): Promise<void> {\n\t\tconst tableName = this.getTableName(collectionSlug);\n\t\tconst columnName = this.getColumnName(fieldSlug);\n\n\t\tawait sql`\n\t\t\tALTER TABLE ${sql.ref(tableName)}\n\t\t\tDROP COLUMN ${sql.ref(columnName)}\n\t\t`.execute(db ?? this.db);\n\t}\n\n\t// ============================================\n\t// Helpers\n\t// ============================================\n\n\t/**\n\t * Check if a collection has any content\n\t */\n\tprivate async collectionHasContent(slug: string): Promise<boolean> {\n\t\tconst tableName = this.getTableName(slug);\n\t\ttry {\n\t\t\tconst result = await sql<{ count: number }>`\n\t\t\t\tSELECT COUNT(*) as count FROM ${sql.ref(tableName)}\n\t\t\t\tWHERE deleted_at IS NULL\n\t\t\t`.execute(this.db);\n\t\t\treturn (result.rows[0]?.count ?? 0) > 0;\n\t\t} catch {\n\t\t\t// Table might not exist\n\t\t\treturn false;\n\t\t}\n\t}\n\n\t/**\n\t * Get table name for a collection\n\t */\n\tprivate getTableName(slug: string): string {\n\t\tvalidateIdentifier(slug, \"collection slug\");\n\t\treturn `ec_${slug}`;\n\t}\n\n\t/**\n\t * Get column name for a field\n\t */\n\tprivate getColumnName(slug: string): string {\n\t\tvalidateIdentifier(slug, \"field slug\");\n\t\treturn slug;\n\t}\n\n\t/**\n\t * Validate a slug\n\t */\n\tprivate validateSlug(slug: string, type: \"collection\" | \"field\"): void {\n\t\tif (!slug || typeof slug !== \"string\") {\n\t\t\tthrow new SchemaError(`${type} slug is required`, \"INVALID_SLUG\");\n\t\t}\n\n\t\tif (!SLUG_VALIDATION_PATTERN.test(slug)) {\n\t\t\tthrow new SchemaError(\n\t\t\t\t`${type} slug must start with a letter and contain only lowercase letters, numbers, and underscores`,\n\t\t\t\t\"INVALID_SLUG\",\n\t\t\t);\n\t\t}\n\n\t\tif (slug.length > 63) {\n\t\t\tthrow new SchemaError(`${type} slug must be 63 characters or less`, \"INVALID_SLUG\");\n\t\t}\n\t}\n\n\t/**\n\t * Format a default value for SQL.\n\t *\n\t * SQLite `ALTER TABLE ADD COLUMN ... DEFAULT` requires a literal constant\n\t * expression — parameterized values cannot be used here. We manually escape\n\t * single quotes and coerce types to ensure the output is safe.\n\t *\n\t * INTEGER/REAL values are coerced through `Number()` which can only produce\n\t * digits, `.`, `-`, `e`, `Infinity`, or `NaN` — all safe in SQL.\n\t * TEXT/JSON values have single quotes escaped via SQL standard doubling (`''`).\n\t */\n\tprivate formatDefaultValue(value: unknown, fieldType: FieldType): string {\n\t\tif (value === null || value === undefined) {\n\t\t\treturn \"NULL\";\n\t\t}\n\n\t\tconst columnType = FIELD_TYPE_TO_COLUMN[fieldType];\n\n\t\tif (columnType === \"JSON\") {\n\t\t\t// JSON.stringify produces valid JSON; escape single quotes for SQL literal\n\t\t\tconst json = JSON.stringify(value);\n\t\t\treturn `'${json.replace(SINGLE_QUOTE_PATTERN, \"''\")}'`;\n\t\t}\n\n\t\tif (columnType === \"INTEGER\") {\n\t\t\tif (typeof value === \"boolean\") {\n\t\t\t\treturn value ? \"1\" : \"0\";\n\t\t\t}\n\t\t\tconst num = Number(value);\n\t\t\tif (!Number.isFinite(num)) {\n\t\t\t\treturn \"0\";\n\t\t\t}\n\t\t\treturn String(Math.trunc(num));\n\t\t}\n\n\t\tif (columnType === \"REAL\") {\n\t\t\tconst num = Number(value);\n\t\t\tif (!Number.isFinite(num)) {\n\t\t\t\treturn \"0\";\n\t\t\t}\n\t\t\treturn String(num);\n\t\t}\n\n\t\t// TEXT — escape single quotes via SQL standard doubling\n\t\tlet text: string;\n\t\tif (typeof value === \"string\") {\n\t\t\ttext = value;\n\t\t} else if (typeof value === \"number\" || typeof value === \"boolean\") {\n\t\t\ttext = String(value);\n\t\t} else if (typeof value === \"object\" && value !== null) {\n\t\t\ttext = JSON.stringify(value);\n\t\t} else {\n\t\t\ttext = \"\";\n\t\t}\n\t\treturn `'${text.replace(SINGLE_QUOTE_PATTERN, \"''\")}'`;\n\t}\n\n\t/**\n\t * Get empty default for a field type\n\t */\n\tprivate getEmptyDefault(fieldType: FieldType): string {\n\t\tconst columnType = FIELD_TYPE_TO_COLUMN[fieldType];\n\n\t\tswitch (columnType) {\n\t\t\tcase \"INTEGER\":\n\t\t\t\treturn \"0\";\n\t\t\tcase \"REAL\":\n\t\t\t\treturn \"0.0\";\n\t\t\tcase \"JSON\":\n\t\t\t\treturn \"'null'\";\n\t\t\tdefault:\n\t\t\t\treturn \"''\";\n\t\t}\n\t}\n\n\t/**\n\t * Map a collection row to a Collection object\n\t */\n\tprivate mapCollectionRow = (row: Selectable<CollectionTable>): Collection => {\n\t\tconst moderation = row.comments_moderation;\n\t\treturn {\n\t\t\tid: row.id,\n\t\t\tslug: row.slug,\n\t\t\tlabel: row.label,\n\t\t\tlabelSingular: row.label_singular ?? undefined,\n\t\t\tdescription: row.description ?? undefined,\n\t\t\ticon: row.icon ?? undefined,\n\t\t\tadmin: parseCollectionAdmin(row.admin_config),\n\t\t\tsupports: parseSupports(row.supports),\n\t\t\tsource: row.source && isCollectionSource(row.source) ? row.source : undefined,\n\t\t\tstorage: row.storage === \"git\" ? \"git\" : \"db\",\n\t\t\thasSeo: row.has_seo === 1,\n\t\t\t// Raw value; undefined when unset. The admin list resolves the\n\t\t\t// default (title fallback chain / updatedAt) at the point of use.\n\t\t\ttitleField: row.title_field ?? undefined,\n\t\t\tdateField: row.date_field ?? undefined,\n\t\t\turlPattern: row.url_pattern ?? undefined,\n\t\t\troutable: row.routable !== 0,\n\t\t\thidden: row.hidden === 1,\n\t\t\tsortOrder: row.sort_order ?? undefined,\n\t\t\tcommentsEnabled: row.comments_enabled === 1,\n\t\t\tcommentsModeration:\n\t\t\t\tmoderation === \"all\" || moderation === \"first_time\" || moderation === \"none\"\n\t\t\t\t\t? moderation\n\t\t\t\t\t: \"first_time\",\n\t\t\tcommentsClosedAfterDays: row.comments_closed_after_days ?? 90,\n\t\t\tcommentsAutoApproveUsers: row.comments_auto_approve_users === 1,\n\t\t\tcreatedAt: row.created_at,\n\t\t\tupdatedAt: row.updated_at,\n\t\t};\n\t};\n\n\t/**\n\t * Map a field row to a Field object\n\t */\n\tprivate mapFieldRow = (row: Selectable<FieldTable>): Field => {\n\t\treturn {\n\t\t\tid: row.id,\n\t\t\tcollectionId: row.collection_id,\n\t\t\tslug: row.slug,\n\t\t\tlabel: row.label,\n\t\t\ttype: isFieldType(row.type) ? row.type : \"string\",\n\t\t\tcolumnType: isColumnType(row.column_type) ? row.column_type : \"TEXT\",\n\t\t\trequired: row.required === 1,\n\t\t\tunique: row.unique === 1,\n\t\t\tdefaultValue: row.default_value ? JSON.parse(row.default_value) : undefined,\n\t\t\tvalidation: row.validation ? JSON.parse(row.validation) : undefined,\n\t\t\twidget: row.widget ?? undefined,\n\t\t\toptions: row.options ? JSON.parse(row.options) : undefined,\n\t\t\tsortOrder: row.sort_order,\n\t\t\tsearchable: row.searchable === 1,\n\t\t\tindexed: row.indexed === 1,\n\t\t\ttranslatable: row.translatable !== 0,\n\t\t\tcreatedAt: row.created_at,\n\t\t};\n\t};\n\n\t// ============================================\n\t// Discovery\n\t// ============================================\n\n\t/**\n\t * Discover orphaned content tables\n\t *\n\t * Finds ec_* tables that exist in the database but don't have a\n\t * corresponding entry in _emdash_collections.\n\t */\n\tasync discoverOrphanedTables(): Promise<\n\t\tArray<{ slug: string; tableName: string; rowCount: number }>\n\t> {\n\t\t// Get all ec_* tables\n\t\t// Content tables are ec_* (e.g., ec_posts, ec_pages)\n\t\t// Internal tables are _emdash_* (e.g., _emdash_collections, _emdash_fts_posts)\n\t\tconst allTables = await listTablesLike(this.db, \"ec_%\");\n\n\t\t// Get registered collections\n\t\tconst registered = await this.listCollections();\n\t\tconst registeredSlugs = new Set(registered.map((c) => c.slug));\n\n\t\t// Find orphans\n\t\tconst orphans: Array<{\n\t\t\tslug: string;\n\t\t\ttableName: string;\n\t\t\trowCount: number;\n\t\t}> = [];\n\n\t\tfor (const tableName of allTables) {\n\t\t\tconst slug = tableName.replace(EC_PREFIX_PATTERN, \"\");\n\n\t\t\tif (!registeredSlugs.has(slug)) {\n\t\t\t\t// Count rows in the orphaned table\n\t\t\t\ttry {\n\t\t\t\t\tconst countResult = await sql<{ count: number }>`\n\t\t\t\t\t\tSELECT COUNT(*) as count FROM ${sql.ref(tableName)}\n\t\t\t\t\t\tWHERE deleted_at IS NULL\n\t\t\t\t\t`.execute(this.db);\n\n\t\t\t\t\torphans.push({\n\t\t\t\t\t\tslug,\n\t\t\t\t\t\ttableName,\n\t\t\t\t\t\trowCount: countResult.rows[0]?.count ?? 0,\n\t\t\t\t\t});\n\t\t\t\t} catch {\n\t\t\t\t\t// Table might have unexpected schema, still report it\n\t\t\t\t\torphans.push({\n\t\t\t\t\t\tslug,\n\t\t\t\t\t\ttableName,\n\t\t\t\t\t\trowCount: 0,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn orphans;\n\t}\n\n\t/**\n\t * Register an orphaned table as a collection\n\t *\n\t * Creates a _emdash_collections entry for an existing ec_* table.\n\t */\n\tasync registerOrphanedTable(\n\t\tslug: string,\n\t\toptions?: {\n\t\t\tlabel?: string;\n\t\t\tlabelSingular?: string;\n\t\t\tdescription?: string;\n\t\t},\n\t): Promise<Collection> {\n\t\t// Verify table exists\n\t\tconst tableName = this.getTableName(slug);\n\t\tif (await isMediaUsageCollectionSlugDeleting(this.db, slug)) {\n\t\t\tthrow new SchemaError(`Collection \"${slug}\" is already registered`, \"COLLECTION_EXISTS\");\n\t\t}\n\t\tconst exists = await tableExists(this.db, tableName);\n\n\t\tif (!exists) {\n\t\t\tthrow new SchemaError(`Table \"${tableName}\" does not exist`, \"TABLE_NOT_FOUND\");\n\t\t}\n\n\t\t// Check if already registered\n\t\tconst existing = await this.getCollection(slug);\n\t\tif (await isMediaUsageCollectionSlugDeleting(this.db, slug)) {\n\t\t\tthrow new SchemaError(`Collection \"${slug}\" is already registered`, \"COLLECTION_EXISTS\");\n\t\t}\n\t\tif (\n\t\t\texisting &&\n\t\t\t!(await canResumeMediaUsageCollectionCapture(this.db, {\n\t\t\t\tcollectionId: existing.id,\n\t\t\t\tcollectionSlug: existing.slug,\n\t\t\t}))\n\t\t) {\n\t\t\tthrow new SchemaError(`Collection \"${slug}\" is already registered`, \"COLLECTION_EXISTS\");\n\t\t}\n\n\t\t// Create collection entry\n\t\tconst proposedId = existing?.id ?? ulid();\n\t\tconst label = options?.label || this.slugToLabel(slug);\n\n\t\tlet collectionRegistered = false;\n\t\ttry {\n\t\t\tconst capture = await prepareMediaUsageCollectionCapture(this.db, {\n\t\t\t\tcollectionId: proposedId,\n\t\t\t\tcollectionSlug: slug,\n\t\t\t\tregisteredCollectionId: existing?.id,\n\t\t\t});\n\t\t\tif (capture.captureRequired) {\n\t\t\t\tawait installPreparedMediaUsageCollectionCapture(this.db, {\n\t\t\t\t\tcollectionId: capture.collectionId,\n\t\t\t\t\tcollectionSlug: slug,\n\t\t\t\t});\n\t\t\t\tawait markMediaUsageCollectionCaptureReady(this.db, {\n\t\t\t\t\tcollectionId: capture.collectionId,\n\t\t\t\t\tcollectionSlug: slug,\n\t\t\t\t});\n\t\t\t}\n\t\t\tif (!capture.registrationExists) {\n\t\t\t\tawait this.db\n\t\t\t\t\t.insertInto(\"_emdash_collections\")\n\t\t\t\t\t.values({\n\t\t\t\t\t\tid: capture.collectionId,\n\t\t\t\t\t\tslug,\n\t\t\t\t\t\tlabel,\n\t\t\t\t\t\tlabel_singular: options?.labelSingular ?? null,\n\t\t\t\t\t\tdescription: options?.description ?? null,\n\t\t\t\t\t\ticon: null,\n\t\t\t\t\t\tsupports: JSON.stringify([]),\n\t\t\t\t\t\tsource: \"discovered\",\n\t\t\t\t\t\thas_seo: 0,\n\t\t\t\t\t\turl_pattern: null,\n\t\t\t\t\t})\n\t\t\t\t\t.execute();\n\t\t\t\tcollectionRegistered = true;\n\t\t\t}\n\t\t\tif (capture.captureRequired) {\n\t\t\t\tawait finalizeMediaUsageCollectionCapture(this.db, {\n\t\t\t\t\tcollectionId: capture.collectionId,\n\t\t\t\t\tcollectionSlug: slug,\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tconst collection = await this.getCollection(slug);\n\t\t\tif (!collection) {\n\t\t\t\tthrow new SchemaError(\"Failed to register orphaned table\", \"REGISTER_FAILED\");\n\t\t\t}\n\t\t\tawait markContentMediaUsageCollectionStaleSafely(this.db, slug, \"CONTENT_USAGE_STALE\");\n\n\t\t\treturn collection;\n\t\t} catch (error) {\n\t\t\tif (collectionRegistered) {\n\t\t\t\tawait markContentMediaUsageCollectionStaleSafely(this.db, slug, \"CONTENT_USAGE_STALE\");\n\t\t\t}\n\t\t\tthrow error;\n\t\t}\n\t}\n\n\t/**\n\t * Convert slug to human-readable label\n\t */\n\tprivate slugToLabel(slug: string): string {\n\t\treturn slug\n\t\t\t.replace(UNDERSCORE_PATTERN, \" \")\n\t\t\t.replace(WORD_BOUNDARY_PATTERN, (c) => c.toUpperCase());\n\t}\n}\n"],"mappings":"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"}