emdash 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (111) hide show
  1. package/LICENSE +9 -0
  2. package/dist/{apply-Bjfq_b4-.mjs → apply-kC39ev1Z.mjs} +4 -4
  3. package/dist/{apply-Bjfq_b4-.mjs.map → apply-kC39ev1Z.mjs.map} +1 -1
  4. package/dist/astro/index.d.mts +3 -3
  5. package/dist/astro/index.mjs +16 -1
  6. package/dist/astro/index.mjs.map +1 -1
  7. package/dist/astro/middleware/auth.d.mts +3 -3
  8. package/dist/astro/middleware/request-context.mjs +84 -22
  9. package/dist/astro/middleware/request-context.mjs.map +1 -1
  10. package/dist/astro/middleware.mjs +41 -12
  11. package/dist/astro/middleware.mjs.map +1 -1
  12. package/dist/astro/types.d.mts +5 -4
  13. package/dist/astro/types.d.mts.map +1 -1
  14. package/dist/cli/index.mjs +65 -6
  15. package/dist/cli/index.mjs.map +1 -1
  16. package/dist/db/index.mjs +1 -1
  17. package/dist/{index-C1xF3OGh.d.mts → index-CLBc4gw-.d.mts} +42 -11
  18. package/dist/{index-C1xF3OGh.d.mts.map → index-CLBc4gw-.d.mts.map} +1 -1
  19. package/dist/index.d.mts +5 -5
  20. package/dist/index.mjs +9 -9
  21. package/dist/{manifest-schema-Dcl0R6nM.mjs → manifest-schema-CL8DWO9b.mjs} +5 -2
  22. package/dist/manifest-schema-CL8DWO9b.mjs.map +1 -0
  23. package/dist/media/index.d.mts +1 -1
  24. package/dist/media/index.mjs +1 -1
  25. package/dist/media/local-runtime.d.mts +4 -4
  26. package/dist/page/index.d.mts +1 -1
  27. package/dist/{placeholder-CmGAmqeO.d.mts → placeholder-SvFCKbz_.d.mts} +10 -2
  28. package/dist/{placeholder-CmGAmqeO.d.mts.map → placeholder-SvFCKbz_.d.mts.map} +1 -1
  29. package/dist/{placeholder-SmpOx-_v.mjs → placeholder-aiCD8aSZ.mjs} +27 -2
  30. package/dist/placeholder-aiCD8aSZ.mjs.map +1 -0
  31. package/dist/plugins/adapt-sandbox-entry.d.mts +3 -3
  32. package/dist/plugins/adapt-sandbox-entry.mjs +1 -1
  33. package/dist/{query-CS_iSj34.mjs → query-BVYN0PJ6.mjs} +2 -2
  34. package/dist/{query-CS_iSj34.mjs.map → query-BVYN0PJ6.mjs.map} +1 -1
  35. package/dist/{registry-D_w5HW4G.mjs → registry-BNYQKX_d.mjs} +23 -38
  36. package/dist/registry-BNYQKX_d.mjs.map +1 -0
  37. package/dist/{runner-C0hCbYnD.mjs → runner-BraqvGYk.mjs} +251 -158
  38. package/dist/runner-BraqvGYk.mjs.map +1 -0
  39. package/dist/runner-EAtf0ZIe.d.mts.map +1 -1
  40. package/dist/runtime.d.mts +4 -4
  41. package/dist/{search-DG603UrT.mjs → search-C1gg67nN.mjs} +125 -18
  42. package/dist/search-C1gg67nN.mjs.map +1 -0
  43. package/dist/seed/index.d.mts +1 -1
  44. package/dist/seed/index.mjs +3 -3
  45. package/dist/{types-DvhsUmSJ.d.mts → types-BQo5JS0J.d.mts} +15 -2
  46. package/dist/{types-DvhsUmSJ.d.mts.map → types-BQo5JS0J.d.mts.map} +1 -1
  47. package/dist/{types-DY5zk5HN.mjs → types-CiA5Gac0.mjs} +5 -3
  48. package/dist/types-CiA5Gac0.mjs.map +1 -0
  49. package/dist/{types-C4-fAxN3.d.mts → types-DPfzHnjW.d.mts} +13 -2
  50. package/dist/types-DPfzHnjW.d.mts.map +1 -0
  51. package/dist/{validate-CpBtVMsD.d.mts → validate-HtxZeaBi.d.mts} +2 -2
  52. package/dist/{validate-CpBtVMsD.d.mts.map → validate-HtxZeaBi.d.mts.map} +1 -1
  53. package/dist/{validate-O7PWmlnq.mjs → validate-_rsF-Dx_.mjs} +2 -2
  54. package/dist/{validate-O7PWmlnq.mjs.map → validate-_rsF-Dx_.mjs.map} +1 -1
  55. package/package.json +6 -4
  56. package/src/api/handlers/marketplace.ts +7 -4
  57. package/src/api/schemas/schema.ts +12 -0
  58. package/src/astro/integration/index.ts +17 -0
  59. package/src/astro/integration/runtime.ts +13 -0
  60. package/src/astro/integration/virtual-modules.ts +13 -1
  61. package/src/astro/routes/admin.astro +1 -1
  62. package/src/astro/routes/api/admin/plugins/marketplace/[id]/install.ts +3 -1
  63. package/src/astro/routes/api/auth/invite/complete.ts +2 -1
  64. package/src/astro/routes/api/auth/passkey/options.ts +2 -1
  65. package/src/astro/routes/api/auth/passkey/register/options.ts +2 -1
  66. package/src/astro/routes/api/auth/passkey/register/verify.ts +2 -1
  67. package/src/astro/routes/api/auth/passkey/verify.ts +2 -1
  68. package/src/astro/routes/api/auth/signup/complete.ts +2 -1
  69. package/src/astro/routes/api/import/wordpress/analyze.ts +24 -3
  70. package/src/astro/routes/api/import/wordpress/execute.ts +5 -1
  71. package/src/astro/routes/api/import/wordpress/prepare.ts +2 -2
  72. package/src/astro/routes/api/media.ts +16 -4
  73. package/src/astro/routes/api/search/index.ts +1 -5
  74. package/src/astro/routes/api/search/suggest.ts +1 -5
  75. package/src/astro/routes/api/setup/admin-verify.ts +2 -1
  76. package/src/astro/routes/api/setup/admin.ts +2 -1
  77. package/src/astro/types.ts +1 -0
  78. package/src/auth/passkey-config.ts +24 -3
  79. package/src/cli/commands/bundle-utils.ts +26 -0
  80. package/src/cli/commands/bundle.ts +15 -0
  81. package/src/cli/commands/content.ts +11 -1
  82. package/src/cli/commands/login.ts +2 -0
  83. package/src/cli/commands/media.ts +5 -1
  84. package/src/cli/commands/menu.ts +3 -1
  85. package/src/cli/commands/schema.ts +7 -1
  86. package/src/cli/commands/search-cmd.ts +2 -1
  87. package/src/cli/commands/taxonomy.ts +4 -1
  88. package/src/cli/output.ts +14 -0
  89. package/src/components/InlinePortableTextEditor.tsx +33 -3
  90. package/src/database/migrations/033_optimize_content_indexes.ts +113 -0
  91. package/src/database/migrations/runner.ts +40 -33
  92. package/src/database/repositories/comment.ts +32 -20
  93. package/src/emdash-runtime.ts +64 -2
  94. package/src/media/placeholder.ts +31 -0
  95. package/src/media/thumbnail.ts +32 -0
  96. package/src/plugins/hooks.ts +91 -0
  97. package/src/plugins/manager.ts +22 -0
  98. package/src/plugins/manifest-schema.ts +3 -0
  99. package/src/plugins/marketplace.ts +25 -12
  100. package/src/plugins/types.ts +24 -0
  101. package/src/schema/registry.ts +23 -27
  102. package/src/schema/types.ts +27 -1
  103. package/src/search/fts-manager.ts +1 -18
  104. package/src/visual-editing/toolbar.ts +84 -22
  105. package/dist/manifest-schema-Dcl0R6nM.mjs.map +0 -1
  106. package/dist/placeholder-SmpOx-_v.mjs.map +0 -1
  107. package/dist/registry-D_w5HW4G.mjs.map +0 -1
  108. package/dist/runner-C0hCbYnD.mjs.map +0 -1
  109. package/dist/search-DG603UrT.mjs.map +0 -1
  110. package/dist/types-C4-fAxN3.d.mts.map +0 -1
  111. package/dist/types-DY5zk5HN.mjs.map +0 -1
@@ -1 +0,0 @@
1
- {"version":3,"file":"placeholder-SmpOx-_v.mjs","names":[],"sources":["../src/media/normalize.ts","../src/media/placeholder.ts"],"sourcesContent":["/**\n * Media Value Normalization\n *\n * Normalizes media field values into a consistent shape regardless of\n * creation path (seed scripts, media picker, WP import, URL input).\n *\n * Called at content create/update time when a media provider is available,\n * filling in missing dimensions, storageKey, mimeType, and filename from\n * the provider's `get()` method.\n */\n\nimport type { MediaProvider, MediaProviderItem, MediaValue } from \"./types.js\";\n\nconst INTERNAL_MEDIA_PREFIX = \"/_emdash/api/media/file/\";\nconst URL_PATTERN = /^https?:\\/\\//;\n\n/**\n * Normalize a media field value into a consistent MediaValue shape.\n *\n * - `null`/`undefined` → `null`\n * - Bare URL string → `{ provider: \"external\", id: \"\", src: url }`\n * - Bare internal media URL → resolved via local provider's `get()`\n * - Object with `provider` + `id` → enriched with missing fields from provider\n */\nexport async function normalizeMediaValue(\n\tvalue: unknown,\n\tgetProvider: (id: string) => MediaProvider | undefined,\n): Promise<MediaValue | null> {\n\tif (value == null) return null;\n\n\t// Bare string URL\n\tif (typeof value === \"string\") {\n\t\treturn normalizeStringUrl(value, getProvider);\n\t}\n\n\t// Not an object — can't normalize\n\tif (!isRecord(value)) return null;\n\n\t// Must have at least an id to be a valid media value\n\tif (!(\"id\" in value) && !(\"src\" in value)) return null;\n\n\tconst provider = (typeof value.provider === \"string\" ? value.provider : undefined) || \"local\";\n\tconst id = typeof value.id === \"string\" ? value.id : \"\";\n\n\t// External URLs — return as-is, no server-side dimension detection\n\tif (provider === \"external\") {\n\t\treturn recordToMediaValue(value);\n\t}\n\n\t// Build the base value from the input\n\tconst result: MediaValue = { ...recordToMediaValue(value), provider };\n\n\t// For local media, strip `src` — it's derived at display time from storageKey\n\tif (provider === \"local\") {\n\t\tdelete result.src;\n\t}\n\n\t// Determine if we need to call the provider\n\tconst needsDimensions = result.width == null || result.height == null;\n\tconst needsStorageKey = provider === \"local\" && !result.meta?.storageKey;\n\tconst needsFileInfo = !result.mimeType || !result.filename;\n\tconst needsLookup = needsDimensions || needsStorageKey || needsFileInfo;\n\n\tif (!needsLookup || !id) return result;\n\n\t// Try to enrich from provider\n\tconst mediaProvider = getProvider(provider);\n\tif (!mediaProvider?.get) return result;\n\n\tlet providerItem: MediaProviderItem | null;\n\ttry {\n\t\tproviderItem = await mediaProvider.get(id);\n\t} catch {\n\t\treturn result;\n\t}\n\n\tif (!providerItem) return result;\n\n\treturn mergeProviderData(result, providerItem);\n}\n\nfunction normalizeStringUrl(\n\turl: string,\n\tgetProvider: (id: string) => MediaProvider | undefined,\n): Promise<MediaValue | null> {\n\t// Internal media URL — try to resolve via local provider\n\tif (url.startsWith(INTERNAL_MEDIA_PREFIX)) {\n\t\treturn resolveInternalUrl(url, getProvider);\n\t}\n\n\t// External HTTP(S) URL\n\tif (URL_PATTERN.test(url)) {\n\t\treturn Promise.resolve({\n\t\t\tprovider: \"external\",\n\t\t\tid: \"\",\n\t\t\tsrc: url,\n\t\t});\n\t}\n\n\t// Unrecognized string — treat as external\n\treturn Promise.resolve({\n\t\tprovider: \"external\",\n\t\tid: \"\",\n\t\tsrc: url,\n\t});\n}\n\nasync function resolveInternalUrl(\n\turl: string,\n\tgetProvider: (id: string) => MediaProvider | undefined,\n): Promise<MediaValue> {\n\tconst storageKey = url.slice(INTERNAL_MEDIA_PREFIX.length);\n\tconst localProvider = getProvider(\"local\");\n\n\tif (!localProvider?.get) {\n\t\treturn { provider: \"external\", id: \"\", src: url };\n\t}\n\n\tlet item: MediaProviderItem | null;\n\ttry {\n\t\titem = await localProvider.get(storageKey);\n\t} catch {\n\t\treturn { provider: \"external\", id: \"\", src: url };\n\t}\n\n\tif (!item) {\n\t\treturn { provider: \"external\", id: \"\", src: url };\n\t}\n\n\treturn {\n\t\tprovider: \"local\",\n\t\tid: item.id,\n\t\tfilename: item.filename,\n\t\tmimeType: item.mimeType,\n\t\twidth: item.width,\n\t\theight: item.height,\n\t\talt: item.alt,\n\t\tmeta: item.meta,\n\t};\n}\n\n/**\n * Merge provider data into an existing MediaValue, preserving caller-supplied fields.\n * Caller `alt` takes priority over provider `alt` (per-usage, not per-image).\n */\nfunction mergeProviderData(existing: MediaValue, item: MediaProviderItem): MediaValue {\n\tconst result = { ...existing };\n\n\t// Fill missing dimensions\n\tif (result.width == null && item.width != null) result.width = item.width;\n\tif (result.height == null && item.height != null) result.height = item.height;\n\n\t// Fill missing file info\n\tif (!result.filename && item.filename) result.filename = item.filename;\n\tif (!result.mimeType && item.mimeType) result.mimeType = item.mimeType;\n\n\t// Fill missing alt (provider alt is fallback, not override)\n\tif (!result.alt && item.alt) result.alt = item.alt;\n\n\t// Fill missing meta (merge, don't replace)\n\tif (item.meta) {\n\t\tresult.meta = { ...item.meta, ...result.meta };\n\t}\n\n\treturn result;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\n/**\n * Extract known MediaValue fields from a runtime-checked record.\n * Avoids unsafe `as MediaValue` cast by reading each property explicitly.\n */\nfunction recordToMediaValue(obj: Record<string, unknown>): MediaValue {\n\tconst result: MediaValue = {\n\t\tid: typeof obj.id === \"string\" ? obj.id : \"\",\n\t};\n\tif (typeof obj.provider === \"string\") result.provider = obj.provider;\n\tif (typeof obj.src === \"string\") result.src = obj.src;\n\tif (typeof obj.previewUrl === \"string\") result.previewUrl = obj.previewUrl;\n\tif (typeof obj.filename === \"string\") result.filename = obj.filename;\n\tif (typeof obj.mimeType === \"string\") result.mimeType = obj.mimeType;\n\tif (typeof obj.width === \"number\") result.width = obj.width;\n\tif (typeof obj.height === \"number\") result.height = obj.height;\n\tif (typeof obj.alt === \"string\") result.alt = obj.alt;\n\tif (isRecord(obj.meta)) result.meta = obj.meta;\n\treturn result;\n}\n","/**\n * Image Placeholder Generation\n *\n * Generates blurhash and dominant color from image buffers for LQIP support.\n * Decodes images via jpeg-js (pure JS) and upng-js (pure JS, uses pako for\n * deflate). No Node-specific dependencies — works in Workers and Node SSR.\n */\n\nimport { encode } from \"blurhash\";\n\nexport interface PlaceholderData {\n\tblurhash: string;\n\tdominantColor: string;\n}\n\nconst SUPPORTED_TYPES: Record<string, \"jpeg\" | \"png\"> = {\n\t\"image/jpeg\": \"jpeg\",\n\t\"image/jpg\": \"jpeg\",\n\t\"image/png\": \"png\",\n};\n\n/** Max width for blurhash input. Encode is O(w*h*components), so downsample first. */\nconst MAX_ENCODE_WIDTH = 32;\n\ninterface DecodedImage {\n\twidth: number;\n\theight: number;\n\tdata: Uint8Array;\n}\n\n/**\n * Decode a JPEG buffer into raw RGBA pixel data.\n */\nasync function decodeJpeg(buffer: Uint8Array): Promise<DecodedImage> {\n\tconst { decode } = await import(\"jpeg-js\");\n\tconst result = decode(buffer, { useTArray: true });\n\treturn { width: result.width, height: result.height, data: result.data };\n}\n\n/**\n * Decode a PNG buffer into raw RGBA pixel data.\n * Uses upng-js (pure JS with pako deflate) — no Node zlib dependency.\n */\nasync function decodePng(buffer: Uint8Array): Promise<DecodedImage> {\n\t// @ts-expect-error -- upng-js has no type declarations\n\tconst UPNG = (await import(\"upng-js\")).default;\n\tconst img = UPNG.decode(buffer.buffer);\n\t// toRGBA8 returns an array of frames; take the first frame\n\tconst frames: ArrayBuffer[] = UPNG.toRGBA8(img);\n\tconst rgba = new Uint8Array(frames[0]);\n\treturn { width: img.width, height: img.height, data: rgba };\n}\n\n/**\n * Extract the dominant color from RGBA pixel data.\n * Simple average of all non-transparent pixels.\n */\nfunction extractDominantColor(data: Uint8Array, width: number, height: number): string {\n\tlet r = 0;\n\tlet g = 0;\n\tlet b = 0;\n\tlet count = 0;\n\n\tconst len = width * height * 4;\n\tfor (let i = 0; i < len; i += 4) {\n\t\tconst a = data[i + 3];\n\t\tif (a < 128) continue; // skip mostly-transparent pixels\n\t\tr += data[i];\n\t\tg += data[i + 1];\n\t\tb += data[i + 2];\n\t\tcount++;\n\t}\n\n\tif (count === 0) return \"rgb(0,0,0)\";\n\n\tconst avgR = Math.round(r / count);\n\tconst avgG = Math.round(g / count);\n\tconst avgB = Math.round(b / count);\n\treturn `rgb(${avgR},${avgG},${avgB})`;\n}\n\n/**\n * Generate blurhash and dominant color from an image buffer.\n * Returns null for non-image MIME types or on failure.\n */\nexport async function generatePlaceholder(\n\tbuffer: Uint8Array,\n\tmimeType: string,\n): Promise<PlaceholderData | null> {\n\tconst format = SUPPORTED_TYPES[mimeType];\n\tif (!format) return null;\n\n\ttry {\n\t\tconst imageData = format === \"jpeg\" ? await decodeJpeg(buffer) : await decodePng(buffer);\n\t\tconst { width, height, data } = imageData;\n\n\t\tif (width === 0 || height === 0) return null;\n\n\t\t// Downsample for blurhash encoding if needed\n\t\tlet encodePixels: Uint8ClampedArray;\n\t\tlet encodeWidth: number;\n\t\tlet encodeHeight: number;\n\n\t\tif (width > MAX_ENCODE_WIDTH) {\n\t\t\tconst scale = MAX_ENCODE_WIDTH / width;\n\t\t\tencodeWidth = MAX_ENCODE_WIDTH;\n\t\t\tencodeHeight = Math.max(1, Math.round(height * scale));\n\t\t\tencodePixels = downsample(data, width, height, encodeWidth, encodeHeight);\n\t\t} else {\n\t\t\tencodeWidth = width;\n\t\t\tencodeHeight = height;\n\t\t\tencodePixels = new Uint8ClampedArray(data.buffer, data.byteOffset, data.byteLength);\n\t\t}\n\n\t\tconst blurhash = encode(encodePixels, encodeWidth, encodeHeight, 4, 3);\n\t\tconst dominantColor = extractDominantColor(data, width, height);\n\n\t\treturn { blurhash, dominantColor };\n\t} catch {\n\t\treturn null;\n\t}\n}\n\n/**\n * Nearest-neighbor downsample of RGBA pixel data.\n */\nfunction downsample(\n\tsrc: Uint8Array,\n\tsrcW: number,\n\tsrcH: number,\n\tdstW: number,\n\tdstH: number,\n): Uint8ClampedArray {\n\tconst dst = new Uint8ClampedArray(dstW * dstH * 4);\n\n\tfor (let y = 0; y < dstH; y++) {\n\t\tconst srcY = Math.floor((y * srcH) / dstH);\n\t\tfor (let x = 0; x < dstW; x++) {\n\t\t\tconst srcX = Math.floor((x * srcW) / dstW);\n\t\t\tconst srcIdx = (srcY * srcW + srcX) * 4;\n\t\t\tconst dstIdx = (y * dstW + x) * 4;\n\t\t\tdst[dstIdx] = src[srcIdx]!;\n\t\t\tdst[dstIdx + 1] = src[srcIdx + 1]!;\n\t\t\tdst[dstIdx + 2] = src[srcIdx + 2]!;\n\t\t\tdst[dstIdx + 3] = src[srcIdx + 3]!;\n\t\t}\n\t}\n\n\treturn dst;\n}\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"registry-D_w5HW4G.mjs","names":["dialectTableExists"],"sources":["../src/database/transaction.ts","../src/search/fts-manager.ts","../src/schema/registry.ts"],"sourcesContent":["/**\n * Transaction utility for D1 compatibility\n *\n * D1 (via kysely-d1) does not support transactions. On workerd, the error\n * from beginTransaction() crosses request contexts and can hang the worker.\n *\n * This utility provides a drop-in replacement that runs the callback directly\n * against the db instance when transactions are unavailable. D1 is single-writer\n * so atomicity is not a concern for individual statements — multi-statement\n * atomicity is lost, but that's a known D1 limitation.\n *\n * Usage:\n * import { withTransaction } from \"../database/transaction.js\";\n * const result = await withTransaction(db, async (trx) => { ... });\n */\n\nimport type { Kysely, Transaction } from \"kysely\";\n\n/**\n * Run a callback inside a transaction if supported, or directly if not.\n *\n * Probes the database once on first call to determine if transactions work.\n * The result is cached for the lifetime of the process/worker.\n */\nlet transactionsSupported: boolean | null = null;\nconst TRANSACTIONS_NOT_SUPPORTED_RE = /transactions are not supported/i;\n\nexport async function withTransaction<DB, T>(\n\tdb: Kysely<DB>,\n\tfn: (trx: Kysely<DB> | Transaction<DB>) => Promise<T>,\n): Promise<T> {\n\t// Fast path: we already know transactions work\n\tif (transactionsSupported === true) {\n\t\treturn db.transaction().execute(fn);\n\t}\n\n\t// Fast path: we already know they don't\n\tif (transactionsSupported === false) {\n\t\treturn fn(db);\n\t}\n\n\t// First call: probe\n\ttry {\n\t\tconst result = await db.transaction().execute(fn);\n\t\ttransactionsSupported = true;\n\t\treturn result;\n\t} catch (error) {\n\t\tif (error instanceof Error && TRANSACTIONS_NOT_SUPPORTED_RE.test(error.message)) {\n\t\t\ttransactionsSupported = false;\n\t\t\treturn fn(db);\n\t\t}\n\t\tthrow error;\n\t}\n}\n","/**\n * FTS5 Manager\n *\n * Manages FTS5 virtual tables and triggers for search indexing.\n */\n\nimport type { Kysely } from \"kysely\";\nimport { sql } from \"kysely\";\n\nimport { isSqlite, tableExists as dialectTableExists } from \"../database/dialect-helpers.js\";\nimport type { Database } from \"../database/types.js\";\nimport { validateIdentifier } from \"../database/validate.js\";\nimport type { SearchConfig } from \"./types.js\";\n\n/**\n * FTS5 Manager\n *\n * Handles creation, deletion, and management of FTS5 virtual tables\n * for full-text search on content collections.\n */\nexport class FTSManager {\n\tconstructor(private db: Kysely<Database>) {}\n\n\t/**\n\t * Validate a collection slug and its searchable field names.\n\t * Must be called before any raw SQL interpolation.\n\t */\n\tprivate validateInputs(collectionSlug: string, searchableFields?: string[]): void {\n\t\tvalidateIdentifier(collectionSlug, \"collection slug\");\n\t\tif (searchableFields) {\n\t\t\tfor (const field of searchableFields) {\n\t\t\t\tvalidateIdentifier(field, \"searchable field name\");\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Get the FTS table name for a collection\n\t * Uses _emdash_ prefix to clearly mark as internal/system table\n\t */\n\tgetFtsTableName(collectionSlug: string): string {\n\t\treturn `_emdash_fts_${collectionSlug}`;\n\t}\n\n\t/**\n\t * Get the content table name for a collection\n\t */\n\tgetContentTableName(collectionSlug: string): string {\n\t\treturn `ec_${collectionSlug}`;\n\t}\n\n\t/**\n\t * Check if an FTS table exists for a collection\n\t */\n\tasync ftsTableExists(collectionSlug: string): Promise<boolean> {\n\t\tconst ftsTable = this.getFtsTableName(collectionSlug);\n\t\treturn dialectTableExists(this.db, ftsTable);\n\t}\n\n\t/**\n\t * Create an FTS5 virtual table for a collection.\n\t * FTS5 is SQLite-only; on other dialects this is a no-op.\n\t *\n\t * @param collectionSlug - The collection slug\n\t * @param searchableFields - Array of field names to index\n\t * @param weights - Optional field weights for ranking\n\t */\n\tasync createFtsTable(\n\t\tcollectionSlug: string,\n\t\tsearchableFields: string[],\n\t\t_weights?: Record<string, number>,\n\t): Promise<void> {\n\t\tif (!isSqlite(this.db)) return;\n\t\tthis.validateInputs(collectionSlug, searchableFields);\n\t\tconst ftsTable = this.getFtsTableName(collectionSlug);\n\t\tconst contentTable = this.getContentTableName(collectionSlug);\n\n\t\t// Build the column list for FTS5\n\t\t// id and locale are UNINDEXED (used for joining/filtering, not searched)\n\t\tconst columns = [\"id UNINDEXED\", \"locale UNINDEXED\", ...searchableFields].join(\", \");\n\n\t\t// Create the FTS5 virtual table\n\t\t// Using content= to make it a contentless FTS table (we manage sync ourselves)\n\t\t// tokenize='porter unicode61' enables stemming (run matches running, ran, etc.)\n\t\tawait sql\n\t\t\t.raw(`\n\t\t\tCREATE VIRTUAL TABLE IF NOT EXISTS \"${ftsTable}\" USING fts5(\n\t\t\t\t${columns},\n\t\t\t\tcontent='${contentTable}',\n\t\t\t\tcontent_rowid='rowid',\n\t\t\t\ttokenize='porter unicode61'\n\t\t\t)\n\t\t`)\n\t\t\t.execute(this.db);\n\n\t\t// Create triggers for automatic sync\n\t\tawait this.createTriggers(collectionSlug, searchableFields);\n\t}\n\n\t/**\n\t * Create triggers to keep FTS table in sync with content table\n\t */\n\tprivate async createTriggers(collectionSlug: string, searchableFields: string[]): Promise<void> {\n\t\tconst ftsTable = this.getFtsTableName(collectionSlug);\n\t\tconst contentTable = this.getContentTableName(collectionSlug);\n\t\tconst fieldList = searchableFields.join(\", \");\n\t\tconst newFieldList = searchableFields.map((f) => `NEW.${f}`).join(\", \");\n\n\t\t// Insert trigger\n\t\tawait sql\n\t\t\t.raw(`\n\t\t\tCREATE TRIGGER IF NOT EXISTS \"${ftsTable}_insert\" \n\t\t\tAFTER INSERT ON \"${contentTable}\" \n\t\t\tBEGIN\n\t\t\t\tINSERT INTO \"${ftsTable}\"(rowid, id, locale, ${fieldList})\n\t\t\t\tVALUES (NEW.rowid, NEW.id, NEW.locale, ${newFieldList});\n\t\t\tEND\n\t\t`)\n\t\t\t.execute(this.db);\n\n\t\t// Update trigger - delete old, insert new\n\t\tawait sql\n\t\t\t.raw(`\n\t\t\tCREATE TRIGGER IF NOT EXISTS \"${ftsTable}_update\" \n\t\t\tAFTER UPDATE ON \"${contentTable}\" \n\t\t\tBEGIN\n\t\t\t\tDELETE FROM \"${ftsTable}\" WHERE rowid = OLD.rowid;\n\t\t\t\tINSERT INTO \"${ftsTable}\"(rowid, id, locale, ${fieldList})\n\t\t\t\tVALUES (NEW.rowid, NEW.id, NEW.locale, ${newFieldList});\n\t\t\tEND\n\t\t`)\n\t\t\t.execute(this.db);\n\n\t\t// Delete trigger\n\t\tawait sql\n\t\t\t.raw(`\n\t\t\tCREATE TRIGGER IF NOT EXISTS \"${ftsTable}_delete\" \n\t\t\tAFTER DELETE ON \"${contentTable}\" \n\t\t\tBEGIN\n\t\t\t\tDELETE FROM \"${ftsTable}\" WHERE rowid = OLD.rowid;\n\t\t\tEND\n\t\t`)\n\t\t\t.execute(this.db);\n\t}\n\n\t/**\n\t * Drop triggers for a collection\n\t */\n\tprivate async dropTriggers(collectionSlug: string): Promise<void> {\n\t\tconst ftsTable = this.getFtsTableName(collectionSlug);\n\n\t\tawait sql.raw(`DROP TRIGGER IF EXISTS \"${ftsTable}_insert\"`).execute(this.db);\n\t\tawait sql.raw(`DROP TRIGGER IF EXISTS \"${ftsTable}_update\"`).execute(this.db);\n\t\tawait sql.raw(`DROP TRIGGER IF EXISTS \"${ftsTable}_delete\"`).execute(this.db);\n\t}\n\n\t/**\n\t * Drop the FTS table and triggers for a collection\n\t */\n\tasync dropFtsTable(collectionSlug: string): Promise<void> {\n\t\tif (!isSqlite(this.db)) return;\n\t\tthis.validateInputs(collectionSlug);\n\t\tconst ftsTable = this.getFtsTableName(collectionSlug);\n\n\t\t// Drop triggers first\n\t\tawait this.dropTriggers(collectionSlug);\n\n\t\t// Drop the FTS table\n\t\tawait sql.raw(`DROP TABLE IF EXISTS \"${ftsTable}\"`).execute(this.db);\n\t}\n\n\t/**\n\t * Rebuild the FTS index for a collection\n\t *\n\t * This is useful after bulk imports or if the index gets out of sync.\n\t */\n\tasync rebuildIndex(\n\t\tcollectionSlug: string,\n\t\tsearchableFields: string[],\n\t\tweights?: Record<string, number>,\n\t): Promise<void> {\n\t\tif (!isSqlite(this.db)) return;\n\t\t// Drop existing table and triggers\n\t\tawait this.dropFtsTable(collectionSlug);\n\n\t\t// Recreate table and triggers\n\t\tawait this.createFtsTable(collectionSlug, searchableFields, weights);\n\n\t\t// Populate from existing content\n\t\tawait this.populateFromContent(collectionSlug, searchableFields);\n\t}\n\n\t/**\n\t * Populate the FTS table from existing content\n\t */\n\tasync populateFromContent(collectionSlug: string, searchableFields: string[]): Promise<void> {\n\t\tif (!isSqlite(this.db)) return;\n\t\tthis.validateInputs(collectionSlug, searchableFields);\n\t\tconst ftsTable = this.getFtsTableName(collectionSlug);\n\t\tconst contentTable = this.getContentTableName(collectionSlug);\n\t\tconst fieldList = searchableFields.join(\", \");\n\n\t\t// Insert all existing content into FTS table\n\t\tawait sql\n\t\t\t.raw(`\n\t\t\tINSERT INTO \"${ftsTable}\"(rowid, id, locale, ${fieldList})\n\t\t\tSELECT rowid, id, locale, ${fieldList} FROM \"${contentTable}\"\n\t\t\tWHERE deleted_at IS NULL\n\t\t`)\n\t\t\t.execute(this.db);\n\t}\n\n\t/**\n\t * Get the search configuration for a collection\n\t */\n\tasync getSearchConfig(collectionSlug: string): Promise<SearchConfig | null> {\n\t\tconst result = await this.db\n\t\t\t.selectFrom(\"_emdash_collections\")\n\t\t\t.select(\"search_config\")\n\t\t\t.where(\"slug\", \"=\", collectionSlug)\n\t\t\t.executeTakeFirst();\n\n\t\tif (!result?.search_config) {\n\t\t\treturn null;\n\t\t}\n\n\t\ttry {\n\t\t\tconst parsed: unknown = JSON.parse(result.search_config);\n\t\t\tif (\n\t\t\t\ttypeof parsed !== \"object\" ||\n\t\t\t\tparsed === null ||\n\t\t\t\t!(\"enabled\" in parsed) ||\n\t\t\t\ttypeof parsed.enabled !== \"boolean\"\n\t\t\t) {\n\t\t\t\treturn null;\n\t\t\t}\n\t\t\tconst config: SearchConfig = { enabled: parsed.enabled };\n\t\t\tif (\"weights\" in parsed && typeof parsed.weights === \"object\" && parsed.weights !== null) {\n\t\t\t\t// weights is a JSON-parsed object — safe to treat as Record<string, number>\n\t\t\t\tconst weights: Record<string, number> = {};\n\t\t\t\tfor (const [k, v] of Object.entries(parsed.weights)) {\n\t\t\t\t\tif (typeof v === \"number\") {\n\t\t\t\t\t\tweights[k] = v;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tconfig.weights = weights;\n\t\t\t}\n\t\t\treturn config;\n\t\t} catch {\n\t\t\treturn null;\n\t\t}\n\t}\n\n\t/**\n\t * Update the search configuration for a collection\n\t */\n\tasync setSearchConfig(collectionSlug: string, config: SearchConfig): Promise<void> {\n\t\tawait this.db\n\t\t\t.updateTable(\"_emdash_collections\")\n\t\t\t.set({ search_config: JSON.stringify(config) })\n\t\t\t.where(\"slug\", \"=\", collectionSlug)\n\t\t\t.execute();\n\t}\n\n\t/**\n\t * Get searchable fields for a collection\n\t */\n\tasync getSearchableFields(collectionSlug: string): Promise<string[]> {\n\t\tconst collection = await this.db\n\t\t\t.selectFrom(\"_emdash_collections\")\n\t\t\t.select(\"id\")\n\t\t\t.where(\"slug\", \"=\", collectionSlug)\n\t\t\t.executeTakeFirst();\n\n\t\tif (!collection) {\n\t\t\treturn [];\n\t\t}\n\n\t\tconst fields = await this.db\n\t\t\t.selectFrom(\"_emdash_fields\")\n\t\t\t.select(\"slug\")\n\t\t\t.where(\"collection_id\", \"=\", collection.id)\n\t\t\t.where(\"searchable\", \"=\", 1)\n\t\t\t.execute();\n\n\t\treturn fields.map((f) => f.slug);\n\t}\n\n\t/**\n\t * Enable search for a collection\n\t *\n\t * Creates the FTS table and triggers, and populates from existing content.\n\t */\n\tasync enableSearch(\n\t\tcollectionSlug: string,\n\t\toptions?: { weights?: Record<string, number> },\n\t): Promise<void> {\n\t\tif (!isSqlite(this.db)) {\n\t\t\tthrow new Error(\"Full-text search is only available with SQLite databases\");\n\t\t}\n\t\t// Get searchable fields\n\t\tconst searchableFields = await this.getSearchableFields(collectionSlug);\n\n\t\tif (searchableFields.length === 0) {\n\t\t\tthrow new Error(\n\t\t\t\t`No searchable fields defined for collection \"${collectionSlug}\". ` +\n\t\t\t\t\t`Mark at least one field as searchable before enabling search.`,\n\t\t\t);\n\t\t}\n\n\t\t// Create FTS table\n\t\tawait this.createFtsTable(collectionSlug, searchableFields, options?.weights);\n\n\t\t// Populate from existing content\n\t\tawait this.populateFromContent(collectionSlug, searchableFields);\n\n\t\t// Update search config\n\t\tawait this.setSearchConfig(collectionSlug, {\n\t\t\tenabled: true,\n\t\t\tweights: options?.weights,\n\t\t});\n\t}\n\n\t/**\n\t * Disable search for a collection\n\t *\n\t * Drops the FTS table and triggers.\n\t */\n\tasync disableSearch(collectionSlug: string): Promise<void> {\n\t\tif (!isSqlite(this.db)) return;\n\t\tawait this.dropFtsTable(collectionSlug);\n\t\tawait this.setSearchConfig(collectionSlug, { enabled: false });\n\t}\n\n\t/**\n\t * Get index statistics for a collection\n\t */\n\tasync getIndexStats(\n\t\tcollectionSlug: string,\n\t): Promise<{ indexed: number; lastRebuilt?: string } | null> {\n\t\tif (!isSqlite(this.db)) return null;\n\t\tthis.validateInputs(collectionSlug);\n\t\tconst ftsTable = this.getFtsTableName(collectionSlug);\n\n\t\t// Check if table exists\n\t\tif (!(await this.ftsTableExists(collectionSlug))) {\n\t\t\treturn null;\n\t\t}\n\n\t\t// Count indexed rows\n\t\tconst result = await sql<{ count: number }>`\n\t\t\tSELECT COUNT(*) as count FROM \"${sql.raw(ftsTable)}\"\n\t\t`.execute(this.db);\n\n\t\treturn {\n\t\t\tindexed: result.rows[0]?.count ?? 0,\n\t\t};\n\t}\n\n\t/**\n\t * Verify FTS index integrity and rebuild if corrupted.\n\t *\n\t * Checks for two corruption indicators:\n\t * 1. Row count mismatch between content table and FTS table\n\t * 2. FTS5 integrity-check failure (catches shadow table inconsistencies)\n\t *\n\t * Returns true if the index was rebuilt, false if it was healthy.\n\t */\n\tasync verifyAndRepairIndex(collectionSlug: string): Promise<boolean> {\n\t\tif (!isSqlite(this.db)) return false;\n\t\tthis.validateInputs(collectionSlug);\n\t\tconst ftsTable = this.getFtsTableName(collectionSlug);\n\t\tconst contentTable = this.getContentTableName(collectionSlug);\n\n\t\tif (!(await this.ftsTableExists(collectionSlug))) {\n\t\t\treturn false;\n\t\t}\n\n\t\t// Check 1: Row count mismatch\n\t\tconst contentCount = await sql<{ count: number }>`\n\t\t\tSELECT COUNT(*) as count FROM ${sql.ref(contentTable)}\n\t\t\tWHERE deleted_at IS NULL\n\t\t`.execute(this.db);\n\n\t\tconst ftsCount = await sql<{ count: number }>`\n\t\t\tSELECT COUNT(*) as count FROM \"${sql.raw(ftsTable)}\"\n\t\t`.execute(this.db);\n\n\t\tconst contentRows = contentCount.rows[0]?.count ?? 0;\n\t\tconst ftsRows = ftsCount.rows[0]?.count ?? 0;\n\n\t\tif (contentRows !== ftsRows) {\n\t\t\tconsole.warn(\n\t\t\t\t`FTS index for \"${collectionSlug}\" has ${ftsRows} rows but content table has ${contentRows}. Rebuilding.`,\n\t\t\t);\n\t\t\tconst fields = await this.getSearchableFields(collectionSlug);\n\t\t\tconst config = await this.getSearchConfig(collectionSlug);\n\t\t\tif (fields.length > 0) {\n\t\t\t\tawait this.rebuildIndex(collectionSlug, fields, config?.weights);\n\t\t\t}\n\t\t\treturn true;\n\t\t}\n\n\t\t// Check 2: FTS5 integrity check (catches shadow table corruption)\n\t\ttry {\n\t\t\tawait sql\n\t\t\t\t.raw(`INSERT INTO \"${ftsTable}\"(\"${ftsTable}\") VALUES('integrity-check')`)\n\t\t\t\t.execute(this.db);\n\t\t} catch {\n\t\t\tconsole.warn(`FTS integrity check failed for \"${collectionSlug}\". Rebuilding index.`);\n\t\t\tconst fields = await this.getSearchableFields(collectionSlug);\n\t\t\tconst config = await this.getSearchConfig(collectionSlug);\n\t\t\tif (fields.length > 0) {\n\t\t\t\tawait this.rebuildIndex(collectionSlug, fields, config?.weights);\n\t\t\t}\n\t\t\treturn true;\n\t\t}\n\n\t\treturn false;\n\t}\n\n\t/**\n\t * Verify and repair FTS indexes for all search-enabled collections.\n\t *\n\t * Intended to run at startup to auto-heal any corruption from\n\t * previous process crashes.\n\t */\n\tasync verifyAndRepairAll(): Promise<number> {\n\t\tif (!isSqlite(this.db)) return 0;\n\n\t\tconst collections = await this.db\n\t\t\t.selectFrom(\"_emdash_collections\")\n\t\t\t.select(\"slug\")\n\t\t\t.where(\"search_config\", \"is not\", null)\n\t\t\t.execute();\n\n\t\tlet repaired = 0;\n\t\tfor (const { slug } of collections) {\n\t\t\tconst config = await this.getSearchConfig(slug);\n\t\t\tif (!config?.enabled) continue;\n\n\t\t\ttry {\n\t\t\t\tconst wasRepaired = await this.verifyAndRepairIndex(slug);\n\t\t\t\tif (wasRepaired) repaired++;\n\t\t\t} catch (error) {\n\t\t\t\tconsole.error(`Failed to verify/repair FTS index for \"${slug}\":`, error);\n\t\t\t}\n\t\t}\n\n\t\treturn repaired;\n\t}\n}\n","import type { Kysely } from \"kysely\";\nimport type { Selectable } 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 { FTSManager } from \"../search/fts-manager.js\";\nimport {\n\ttype Collection,\n\ttype CollectionSource,\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\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;\n\n/** Valid column types for runtime validation */\nconst COLUMN_TYPES: ReadonlySet<string> = new Set([\"TEXT\", \"REAL\", \"INTEGER\", \"JSON\"]);\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\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(\"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 * 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\n\t\t// Check if collection already exists\n\t\tconst existing = await this.getCollection(input.slug);\n\t\tif (existing) {\n\t\t\tthrow new SchemaError(`Collection \"${input.slug}\" already exists`, \"COLLECTION_EXISTS\");\n\t\t}\n\n\t\tconst id = ulid();\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 ?? input.supports?.includes(\"seo\") ?? false;\n\n\t\tawait withTransaction(this.db, async (trx) => {\n\t\t\tawait trx\n\t\t\t\t.insertInto(\"_emdash_collections\")\n\t\t\t\t.values({\n\t\t\t\t\tid,\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\tsupports: input.supports ? JSON.stringify(input.supports) : null,\n\t\t\t\t\tsource: input.source ?? \"manual\",\n\t\t\t\t\thas_seo: hasSeo ? 1 : 0,\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\t.execute();\n\n\t\t\t// Create the content table for this collection\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 * Update a collection\n\t */\n\tasync updateCollection(slug: string, input: UpdateCollectionInput): Promise<Collection> {\n\t\tconst existing = await this.getCollection(slug);\n\t\tif (!existing) {\n\t\t\tthrow new SchemaError(`Collection \"${slug}\" not found`, \"COLLECTION_NOT_FOUND\");\n\t\t}\n\n\t\tconst now = new Date().toISOString();\n\n\t\t// Derive hasSeo from supports array if supports is being updated and hasSeo not explicitly set\n\t\tconst supportsArray = input.supports ?? existing.supports;\n\t\tconst hasSeo =\n\t\t\tinput.hasSeo !== undefined\n\t\t\t\t? input.hasSeo\n\t\t\t\t: input.supports !== undefined\n\t\t\t\t\t? supportsArray.includes(\"seo\")\n\t\t\t\t\t: existing.hasSeo;\n\n\t\tawait this.db\n\t\t\t.updateTable(\"_emdash_collections\")\n\t\t\t.set({\n\t\t\t\tlabel: input.label ?? existing.label,\n\t\t\t\tlabel_singular: input.labelSingular ?? existing.labelSingular ?? null,\n\t\t\t\tdescription: input.description ?? existing.description ?? null,\n\t\t\t\ticon: input.icon ?? existing.icon ?? null,\n\t\t\t\tsupports: input.supports\n\t\t\t\t\t? JSON.stringify(input.supports)\n\t\t\t\t\t: JSON.stringify(existing.supports),\n\t\t\t\turl_pattern:\n\t\t\t\t\tinput.urlPattern !== undefined\n\t\t\t\t\t\t? (input.urlPattern ?? null)\n\t\t\t\t\t\t: (existing.urlPattern ?? null),\n\t\t\t\thas_seo: hasSeo ? 1 : 0,\n\t\t\t\tcomments_enabled:\n\t\t\t\t\tinput.commentsEnabled !== undefined\n\t\t\t\t\t\t? input.commentsEnabled\n\t\t\t\t\t\t\t? 1\n\t\t\t\t\t\t\t: 0\n\t\t\t\t\t\t: existing.commentsEnabled\n\t\t\t\t\t\t\t? 1\n\t\t\t\t\t\t\t: 0,\n\t\t\t\tcomments_moderation: input.commentsModeration ?? existing.commentsModeration,\n\t\t\t\tcomments_closed_after_days:\n\t\t\t\t\tinput.commentsClosedAfterDays !== undefined\n\t\t\t\t\t\t? input.commentsClosedAfterDays\n\t\t\t\t\t\t: existing.commentsClosedAfterDays,\n\t\t\t\tcomments_auto_approve_users:\n\t\t\t\t\tinput.commentsAutoApproveUsers !== undefined\n\t\t\t\t\t\t? input.commentsAutoApproveUsers\n\t\t\t\t\t\t\t? 1\n\t\t\t\t\t\t\t: 0\n\t\t\t\t\t\t: existing.commentsAutoApproveUsers\n\t\t\t\t\t\t\t? 1\n\t\t\t\t\t\t\t: 0,\n\t\t\t\tupdated_at: now,\n\t\t\t})\n\t\t\t.where(\"slug\", \"=\", slug)\n\t\t\t.execute();\n\n\t\tconst updated = await this.getCollection(slug);\n\t\tif (!updated) {\n\t\t\tthrow new SchemaError(\"Failed to update collection\", \"UPDATE_FAILED\");\n\t\t}\n\n\t\treturn updated;\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) {\n\t\t\tthrow new SchemaError(`Collection \"${slug}\" not found`, \"COLLECTION_NOT_FOUND\");\n\t\t}\n\n\t\t// Check if collection has content\n\t\tif (!options?.force) {\n\t\t\tconst hasContent = await this.collectionHasContent(slug);\n\t\t\tif (hasContent) {\n\t\t\t\tthrow new SchemaError(\n\t\t\t\t\t`Collection \"${slug}\" has content. Use force: true to delete.`,\n\t\t\t\t\t\"COLLECTION_HAS_CONTENT\",\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\t// Drop the content table\n\t\tawait this.dropContentTable(slug);\n\n\t\t// Delete the collection record (fields will cascade)\n\t\tawait this.db.deleteFrom(\"_emdash_collections\").where(\"id\", \"=\", existing.id).execute();\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\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\n\t\t// Insert field record\n\t\tawait this.db\n\t\t\t.insertInto(\"_emdash_fields\")\n\t\t\t.values({\n\t\t\t\tid,\n\t\t\t\tcollection_id: collection.id,\n\t\t\t\tslug: input.slug,\n\t\t\t\tlabel: input.label,\n\t\t\t\ttype: input.type,\n\t\t\t\tcolumn_type: columnType,\n\t\t\t\trequired: input.required ? 1 : 0,\n\t\t\t\tunique: input.unique ? 1 : 0,\n\t\t\t\tdefault_value: input.defaultValue !== undefined ? JSON.stringify(input.defaultValue) : null,\n\t\t\t\tvalidation: input.validation ? JSON.stringify(input.validation) : null,\n\t\t\t\twidget: input.widget ?? null,\n\t\t\t\toptions: input.options ? JSON.stringify(input.options) : null,\n\t\t\t\tsort_order: sortOrder,\n\t\t\t\tsearchable: input.searchable ? 1 : 0,\n\t\t\t\ttranslatable: input.translatable === false ? 0 : 1,\n\t\t\t})\n\t\t\t.execute();\n\n\t\t// Add column to content table\n\t\tawait this.addColumn(collectionSlug, input.slug, input.type, {\n\t\t\trequired: input.required,\n\t\t\tdefaultValue: input.defaultValue,\n\t\t});\n\n\t\tconst field = await this.getField(collectionSlug, input.slug);\n\t\tif (!field) {\n\t\t\tthrow new SchemaError(\"Failed to create field\", \"CREATE_FAILED\");\n\t\t}\n\n\t\treturn field;\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\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\n\t\tawait this.db\n\t\t\t.updateTable(\"_emdash_fields\")\n\t\t\t.set({\n\t\t\t\tlabel: input.label ?? field.label,\n\t\t\t\trequired: input.required !== undefined ? (input.required ? 1 : 0) : field.required ? 1 : 0,\n\t\t\t\tunique: input.unique !== undefined ? (input.unique ? 1 : 0) : field.unique ? 1 : 0,\n\t\t\t\tsearchable:\n\t\t\t\t\tinput.searchable !== undefined ? (input.searchable ? 1 : 0) : field.searchable ? 1 : 0,\n\t\t\t\ttranslatable:\n\t\t\t\t\tinput.translatable !== undefined\n\t\t\t\t\t\t? input.translatable\n\t\t\t\t\t\t\t? 1\n\t\t\t\t\t\t\t: 0\n\t\t\t\t\t\t: field.translatable\n\t\t\t\t\t\t\t? 1\n\t\t\t\t\t\t\t: 0,\n\t\t\t\tdefault_value:\n\t\t\t\t\tinput.defaultValue !== undefined\n\t\t\t\t\t\t? JSON.stringify(input.defaultValue)\n\t\t\t\t\t\t: field.defaultValue !== undefined\n\t\t\t\t\t\t\t? JSON.stringify(field.defaultValue)\n\t\t\t\t\t\t\t: null,\n\t\t\t\tvalidation: input.validation\n\t\t\t\t\t? JSON.stringify(input.validation)\n\t\t\t\t\t: field.validation\n\t\t\t\t\t\t? JSON.stringify(field.validation)\n\t\t\t\t\t\t: null,\n\t\t\t\twidget: input.widget ?? field.widget ?? null,\n\t\t\t\toptions: input.options\n\t\t\t\t\t? JSON.stringify(input.options)\n\t\t\t\t\t: field.options\n\t\t\t\t\t\t? JSON.stringify(field.options)\n\t\t\t\t\t\t: null,\n\t\t\t\tsort_order: input.sortOrder ?? field.sortOrder,\n\t\t\t})\n\t\t\t.where(\"id\", \"=\", field.id)\n\t\t\t.execute();\n\n\t\tconst updated = await this.getField(collectionSlug, fieldSlug);\n\t\tif (!updated) {\n\t\t\tthrow new SchemaError(\"Failed to update field\", \"UPDATE_FAILED\");\n\t\t}\n\n\t\t// If searchable changed, rebuild the FTS index for this collection\n\t\tconst searchableChanged =\n\t\t\tinput.searchable !== undefined && input.searchable !== field.searchable;\n\t\tif (searchableChanged) {\n\t\t\tawait this.rebuildSearchIndex(collectionSlug);\n\t\t}\n\n\t\treturn updated;\n\t}\n\n\t/**\n\t * Rebuild the search index for a collection\n\t *\n\t * Called when searchable fields change. If search is enabled for the collection,\n\t * this will rebuild the FTS table with the updated field list.\n\t */\n\tprivate async rebuildSearchIndex(collectionSlug: string): Promise<void> {\n\t\tconst ftsManager = new FTSManager(this.db);\n\n\t\t// Check if search is enabled for this collection\n\t\tconst config = await ftsManager.getSearchConfig(collectionSlug);\n\t\tif (!config?.enabled) {\n\t\t\t// Search not enabled, nothing to do\n\t\t\treturn;\n\t\t}\n\n\t\t// Get current searchable fields\n\t\tconst searchableFields = await ftsManager.getSearchableFields(collectionSlug);\n\n\t\tif (searchableFields.length === 0) {\n\t\t\t// No searchable fields left, disable search\n\t\t\tawait ftsManager.disableSearch(collectionSlug);\n\t\t} else {\n\t\t\t// Rebuild the index with updated fields\n\t\t\tawait ftsManager.rebuildIndex(collectionSlug, searchableFields, config.weights);\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\n\t\t// Drop column from content table\n\t\tawait this.dropColumn(collectionSlug, fieldSlug);\n\n\t\t// Delete field record\n\t\tawait this.db.deleteFrom(\"_emdash_fields\").where(\"id\", \"=\", field.id).execute();\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(slug: string, db?: Kysely<Database>): Promise<void> {\n\t\tconst conn = db ?? this.db;\n\t\tconst tableName = this.getTableName(slug);\n\n\t\tawait 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\t\t.addUniqueConstraint(`${tableName}_slug_locale_unique`, [\"slug\", \"locale\"])\n\t\t\t.execute();\n\n\t\t// Create standard indexes\n\t\tawait sql`\n\t\t\tCREATE INDEX ${sql.ref(`idx_${tableName}_status`)} \n\t\t\tON ${sql.ref(tableName)} (status)\n\t\t`.execute(conn);\n\n\t\tawait sql`\n\t\t\tCREATE INDEX ${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\tCREATE INDEX ${sql.ref(`idx_${tableName}_created`)} \n\t\t\tON ${sql.ref(tableName)} (created_at)\n\t\t`.execute(conn);\n\n\t\tawait sql`\n\t\t\tCREATE INDEX ${sql.ref(`idx_${tableName}_deleted`)} \n\t\t\tON ${sql.ref(tableName)} (deleted_at)\n\t\t`.execute(conn);\n\n\t\tawait sql`\n\t\t\tCREATE INDEX ${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\tCREATE INDEX ${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\tCREATE INDEX ${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\tCREATE INDEX ${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\tCREATE INDEX ${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\tCREATE INDEX ${sql.ref(`idx_${tableName}_updated`)} \n\t\t\tON ${sql.ref(tableName)} (updated_at)\n\t\t`.execute(conn);\n\n\t\tawait sql`\n\t\t\tCREATE INDEX ${sql.ref(`idx_${tableName}_locale`)} \n\t\t\tON ${sql.ref(tableName)} (locale)\n\t\t`.execute(conn);\n\n\t\tawait sql`\n\t\t\tCREATE INDEX ${sql.ref(`idx_${tableName}_translation_group`)} \n\t\t\tON ${sql.ref(tableName)} (translation_group)\n\t\t`.execute(conn);\n\t}\n\n\t/**\n\t * Drop a content table\n\t */\n\tprivate async dropContentTable(slug: string): Promise<void> {\n\t\tconst tableName = this.getTableName(slug);\n\t\tawait sql`DROP TABLE IF EXISTS ${sql.ref(tableName)}`.execute(this.db);\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): Promise<void> {\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(this.db);\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(this.db);\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(this.db);\n\t\t}\n\t}\n\n\t/**\n\t * Drop a column from a content table\n\t */\n\tprivate async dropColumn(collectionSlug: string, fieldSlug: string): 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(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\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\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\tsupports: row.supports ? JSON.parse(row.supports) : [],\n\t\t\tsource: row.source && isCollectionSource(row.source) ? row.source : undefined,\n\t\t\thasSeo: row.has_seo === 1,\n\t\t\turlPattern: row.url_pattern ?? 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\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\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 (existing) {\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 id = ulid();\n\t\tconst label = options?.label || this.slugToLabel(slug);\n\n\t\tawait this.db\n\t\t\t.insertInto(\"_emdash_collections\")\n\t\t\t.values({\n\t\t\t\tid,\n\t\t\t\tslug,\n\t\t\t\tlabel,\n\t\t\t\tlabel_singular: options?.labelSingular ?? null,\n\t\t\t\tdescription: options?.description ?? null,\n\t\t\t\ticon: null,\n\t\t\t\tsupports: JSON.stringify([]),\n\t\t\t\tsource: \"discovered\",\n\t\t\t\thas_seo: 0,\n\t\t\t\turl_pattern: null,\n\t\t\t})\n\t\t\t.execute();\n\n\t\tconst collection = await this.getCollection(slug);\n\t\tif (!collection) {\n\t\t\tthrow new SchemaError(\"Failed to register orphaned table\", \"REGISTER_FAILED\");\n\t\t}\n\n\t\treturn collection;\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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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"runner-C0hCbYnD.mjs","names":["up","down","up","down","up","down","up","down","up","down","up","down","up","down","up","down","up","down","up","down","up","down","up","down","up","down","up","down","up","down","up","down","up","down","up","down","up","down","up","down","up","down","up","down","up","down","up","down","up","down","up","down","up","down","up","down","up","down","up","down","m001","m002","m003","m004","m005","m006","m007","m008","m009","m011","m012","m013","m014","m015","m016","m017","m018","m019","m020","m021","m022","m023","m024","m025","m026","m027","m028","m029","m030","m031","m032"],"sources":["../src/database/migrations/001_initial.ts","../src/database/migrations/002_media_status.ts","../src/database/migrations/003_schema_registry.ts","../src/database/migrations/004_plugins.ts","../src/database/migrations/005_menus.ts","../src/database/migrations/006_taxonomy_defs.ts","../src/database/migrations/007_widgets.ts","../src/database/migrations/008_auth.ts","../src/database/migrations/009_user_disabled.ts","../src/database/migrations/011_sections.ts","../src/database/migrations/012_search.ts","../src/database/migrations/013_scheduled_publishing.ts","../src/database/migrations/014_draft_revisions.ts","../src/database/migrations/015_indexes.ts","../src/database/migrations/016_api_tokens.ts","../src/database/migrations/017_authorization_codes.ts","../src/database/migrations/018_seo.ts","../src/database/migrations/019_i18n.ts","../src/database/migrations/020_collection_url_pattern.ts","../src/database/migrations/021_remove_section_categories.ts","../src/database/migrations/022_marketplace_plugin_state.ts","../src/database/migrations/023_plugin_metadata.ts","../src/database/migrations/024_media_placeholders.ts","../src/database/migrations/025_oauth_clients.ts","../src/database/migrations/026_cron_tasks.ts","../src/database/migrations/027_comments.ts","../src/database/migrations/028_drop_author_url.ts","../src/database/migrations/029_redirects.ts","../src/database/migrations/030_widen_scheduled_index.ts","../src/database/migrations/031_bylines.ts","../src/database/migrations/032_rate_limits.ts","../src/database/migrations/runner.ts"],"sourcesContent":["import type { Kysely } from \"kysely\";\n\nimport { currentTimestamp } from \"../dialect-helpers.js\";\n\n/**\n * Initial schema migration\n *\n * Note: Content tables (ec_posts, ec_pages, etc.) are created dynamically\n * by the SchemaRegistry when collections are added via the admin UI.\n * This migration only creates system tables.\n */\nexport async function up(db: Kysely<unknown>): Promise<void> {\n\t// Revisions - stores snapshots of content entries\n\t// References entries in ec_* tables by collection + entry_id\n\tawait db.schema\n\t\t.createTable(\"revisions\")\n\t\t.ifNotExists()\n\t\t.addColumn(\"id\", \"text\", (col) => col.primaryKey())\n\t\t.addColumn(\"collection\", \"text\", (col) => col.notNull()) // e.g., 'posts'\n\t\t.addColumn(\"entry_id\", \"text\", (col) => col.notNull()) // ID in the ec_* table\n\t\t.addColumn(\"data\", \"text\", (col) => col.notNull()) // JSON snapshot of all fields\n\t\t.addColumn(\"author_id\", \"text\")\n\t\t.addColumn(\"created_at\", \"text\", (col) => col.defaultTo(currentTimestamp(db)))\n\t\t.execute();\n\n\tawait db.schema\n\t\t.createIndex(\"idx_revisions_entry\")\n\t\t.ifNotExists()\n\t\t.on(\"revisions\")\n\t\t.columns([\"collection\", \"entry_id\"])\n\t\t.execute();\n\n\t// Taxonomies\n\tawait db.schema\n\t\t.createTable(\"taxonomies\")\n\t\t.ifNotExists()\n\t\t.addColumn(\"id\", \"text\", (col) => col.primaryKey())\n\t\t.addColumn(\"name\", \"text\", (col) => col.notNull())\n\t\t.addColumn(\"slug\", \"text\", (col) => col.notNull())\n\t\t.addColumn(\"label\", \"text\", (col) => col.notNull())\n\t\t.addColumn(\"parent_id\", \"text\")\n\t\t.addColumn(\"data\", \"text\")\n\t\t.addUniqueConstraint(\"taxonomies_name_slug_unique\", [\"name\", \"slug\"])\n\t\t.addForeignKeyConstraint(\"taxonomies_parent_fk\", [\"parent_id\"], \"taxonomies\", [\"id\"], (cb) =>\n\t\t\tcb.onDelete(\"set null\"),\n\t\t)\n\t\t.execute();\n\n\tawait db.schema\n\t\t.createIndex(\"idx_taxonomies_name\")\n\t\t.ifNotExists()\n\t\t.on(\"taxonomies\")\n\t\t.column(\"name\")\n\t\t.execute();\n\n\t// Content-Taxonomy junction - references entries in ec_* tables\n\tawait db.schema\n\t\t.createTable(\"content_taxonomies\")\n\t\t.ifNotExists()\n\t\t.addColumn(\"collection\", \"text\", (col) => col.notNull()) // e.g., 'posts'\n\t\t.addColumn(\"entry_id\", \"text\", (col) => col.notNull()) // ID in the ec_* table\n\t\t.addColumn(\"taxonomy_id\", \"text\", (col) => col.notNull())\n\t\t.addPrimaryKeyConstraint(\"content_taxonomies_pk\", [\"collection\", \"entry_id\", \"taxonomy_id\"])\n\t\t.addForeignKeyConstraint(\n\t\t\t\"content_taxonomies_taxonomy_fk\",\n\t\t\t[\"taxonomy_id\"],\n\t\t\t\"taxonomies\",\n\t\t\t[\"id\"],\n\t\t\t(cb) => cb.onDelete(\"cascade\"),\n\t\t)\n\t\t.execute();\n\n\t// Media\n\tawait db.schema\n\t\t.createTable(\"media\")\n\t\t.ifNotExists()\n\t\t.addColumn(\"id\", \"text\", (col) => col.primaryKey())\n\t\t.addColumn(\"filename\", \"text\", (col) => col.notNull())\n\t\t.addColumn(\"mime_type\", \"text\", (col) => col.notNull())\n\t\t.addColumn(\"size\", \"integer\")\n\t\t.addColumn(\"width\", \"integer\")\n\t\t.addColumn(\"height\", \"integer\")\n\t\t.addColumn(\"alt\", \"text\")\n\t\t.addColumn(\"caption\", \"text\")\n\t\t.addColumn(\"storage_key\", \"text\", (col) => col.notNull())\n\t\t.addColumn(\"content_hash\", \"text\") // xxHash64 for deduplication\n\t\t.addColumn(\"created_at\", \"text\", (col) => col.defaultTo(currentTimestamp(db)))\n\t\t.addColumn(\"author_id\", \"text\")\n\t\t.execute();\n\n\tawait db.schema\n\t\t.createIndex(\"idx_media_content_hash\")\n\t\t.ifNotExists()\n\t\t.on(\"media\")\n\t\t.column(\"content_hash\")\n\t\t.execute();\n\n\t// Users\n\tawait db.schema\n\t\t.createTable(\"users\")\n\t\t.ifNotExists()\n\t\t.addColumn(\"id\", \"text\", (col) => col.primaryKey())\n\t\t.addColumn(\"email\", \"text\", (col) => col.notNull().unique())\n\t\t.addColumn(\"password_hash\", \"text\", (col) => col.notNull())\n\t\t.addColumn(\"name\", \"text\")\n\t\t.addColumn(\"role\", \"text\", (col) => col.defaultTo(\"subscriber\"))\n\t\t.addColumn(\"avatar_id\", \"text\")\n\t\t.addColumn(\"data\", \"text\")\n\t\t.addColumn(\"created_at\", \"text\", (col) => col.defaultTo(currentTimestamp(db)))\n\t\t.execute();\n\n\tawait db.schema\n\t\t.createIndex(\"idx_users_email\")\n\t\t.ifNotExists()\n\t\t.on(\"users\")\n\t\t.column(\"email\")\n\t\t.execute();\n\n\t// Options (key-value store)\n\tawait db.schema\n\t\t.createTable(\"options\")\n\t\t.ifNotExists()\n\t\t.addColumn(\"name\", \"text\", (col) => col.primaryKey())\n\t\t.addColumn(\"value\", \"text\", (col) => col.notNull())\n\t\t.execute();\n\n\t// Audit logs (security events)\n\tawait db.schema\n\t\t.createTable(\"audit_logs\")\n\t\t.ifNotExists()\n\t\t.addColumn(\"id\", \"text\", (col) => col.primaryKey())\n\t\t.addColumn(\"timestamp\", \"text\", (col) => col.defaultTo(currentTimestamp(db)))\n\t\t.addColumn(\"actor_id\", \"text\")\n\t\t.addColumn(\"actor_ip\", \"text\")\n\t\t.addColumn(\"action\", \"text\", (col) => col.notNull())\n\t\t.addColumn(\"resource_type\", \"text\")\n\t\t.addColumn(\"resource_id\", \"text\")\n\t\t.addColumn(\"details\", \"text\")\n\t\t.addColumn(\"status\", \"text\")\n\t\t.execute();\n\n\tawait db.schema\n\t\t.createIndex(\"idx_audit_actor\")\n\t\t.ifNotExists()\n\t\t.on(\"audit_logs\")\n\t\t.column(\"actor_id\")\n\t\t.execute();\n\tawait db.schema\n\t\t.createIndex(\"idx_audit_action\")\n\t\t.ifNotExists()\n\t\t.on(\"audit_logs\")\n\t\t.column(\"action\")\n\t\t.execute();\n\tawait db.schema\n\t\t.createIndex(\"idx_audit_timestamp\")\n\t\t.ifNotExists()\n\t\t.on(\"audit_logs\")\n\t\t.column(\"timestamp\")\n\t\t.execute();\n}\n\nexport async function down(db: Kysely<unknown>): Promise<void> {\n\tawait db.schema.dropTable(\"audit_logs\").execute();\n\tawait db.schema.dropTable(\"options\").execute();\n\tawait db.schema.dropTable(\"users\").execute();\n\tawait db.schema.dropTable(\"media\").execute();\n\tawait db.schema.dropTable(\"content_taxonomies\").execute();\n\tawait db.schema.dropTable(\"taxonomies\").execute();\n\tawait db.schema.dropTable(\"revisions\").execute();\n}\n","import type { Kysely } from \"kysely\";\n\n/**\n * Add status column to media table for tracking upload state.\n * Status values: 'pending' | 'ready' | 'failed'\n */\nexport async function up(db: Kysely<unknown>): Promise<void> {\n\tawait db.schema\n\t\t.alterTable(\"media\")\n\t\t.addColumn(\"status\", \"text\", (col) => col.notNull().defaultTo(\"ready\"))\n\t\t.execute();\n\n\tawait db.schema.createIndex(\"idx_media_status\").on(\"media\").column(\"status\").execute();\n}\n\nexport async function down(db: Kysely<unknown>): Promise<void> {\n\tawait db.schema.dropIndex(\"idx_media_status\").execute();\n\n\t// Note: SQLite doesn't support DROP COLUMN directly\n\t// For a proper down migration in SQLite, you'd need to:\n\t// 1. Create a new table without the column\n\t// 2. Copy data over\n\t// 3. Drop old table\n\t// 4. Rename new table\n\t// For simplicity, we'll leave this as a no-op since down migrations are rarely used\n}\n","import type { Kysely } from \"kysely\";\n\nimport { currentTimestamp } from \"../dialect-helpers.js\";\n\n/**\n * Migration: Schema Registry Tables\n *\n * Creates the schema registry tables that store collection and field definitions.\n * This enables dynamic schema management where D1 is the source of truth.\n */\nexport async function up(db: Kysely<unknown>): Promise<void> {\n\t// Collection definitions (like WordPress post types, but stored in DB)\n\tawait db.schema\n\t\t.createTable(\"_emdash_collections\")\n\t\t.addColumn(\"id\", \"text\", (col) => col.primaryKey())\n\t\t.addColumn(\"slug\", \"text\", (col) => col.notNull().unique())\n\t\t.addColumn(\"label\", \"text\", (col) => col.notNull())\n\t\t.addColumn(\"label_singular\", \"text\")\n\t\t.addColumn(\"description\", \"text\")\n\t\t.addColumn(\"icon\", \"text\")\n\t\t.addColumn(\"supports\", \"text\") // JSON array: [\"revisions\", \"drafts\", \"preview\"]\n\t\t.addColumn(\"source\", \"text\") // 'template:blog', 'import:wordpress', 'manual'\n\t\t.addColumn(\"created_at\", \"text\", (col) => col.defaultTo(currentTimestamp(db)))\n\t\t.addColumn(\"updated_at\", \"text\", (col) => col.defaultTo(currentTimestamp(db)))\n\t\t.execute();\n\n\t// Field definitions for each collection\n\tawait db.schema\n\t\t.createTable(\"_emdash_fields\")\n\t\t.addColumn(\"id\", \"text\", (col) => col.primaryKey())\n\t\t.addColumn(\"collection_id\", \"text\", (col) => col.notNull())\n\t\t.addColumn(\"slug\", \"text\", (col) => col.notNull())\n\t\t.addColumn(\"label\", \"text\", (col) => col.notNull())\n\t\t.addColumn(\"type\", \"text\", (col) => col.notNull()) // 'string', 'number', 'boolean', 'portableText', etc.\n\t\t.addColumn(\"column_type\", \"text\", (col) => col.notNull()) // 'TEXT', 'REAL', 'INTEGER', 'JSON'\n\t\t.addColumn(\"required\", \"integer\", (col) => col.defaultTo(0)) // boolean as 0/1\n\t\t.addColumn(\"unique\", \"integer\", (col) => col.defaultTo(0)) // boolean as 0/1\n\t\t.addColumn(\"default_value\", \"text\") // JSON-encoded default\n\t\t.addColumn(\"validation\", \"text\") // JSON: { min: 0, max: 100, pattern: \"^[a-z]+$\" }\n\t\t.addColumn(\"widget\", \"text\") // UI widget hint: 'textarea', 'richtext', 'select'\n\t\t.addColumn(\"options\", \"text\") // JSON: widget-specific config\n\t\t.addColumn(\"sort_order\", \"integer\", (col) => col.defaultTo(0))\n\t\t.addColumn(\"created_at\", \"text\", (col) => col.defaultTo(currentTimestamp(db)))\n\t\t.addForeignKeyConstraint(\n\t\t\t\"fields_collection_fk\",\n\t\t\t[\"collection_id\"],\n\t\t\t\"_emdash_collections\",\n\t\t\t[\"id\"],\n\t\t\t(cb) => cb.onDelete(\"cascade\"),\n\t\t)\n\t\t.execute();\n\n\t// Unique constraint on collection + field slug\n\tawait db.schema\n\t\t.createIndex(\"idx_fields_collection_slug\")\n\t\t.on(\"_emdash_fields\")\n\t\t.columns([\"collection_id\", \"slug\"])\n\t\t.unique()\n\t\t.execute();\n\n\t// Index for faster field lookups\n\tawait db.schema\n\t\t.createIndex(\"idx_fields_collection\")\n\t\t.on(\"_emdash_fields\")\n\t\t.column(\"collection_id\")\n\t\t.execute();\n\n\t// Index for sorting\n\tawait db.schema\n\t\t.createIndex(\"idx_fields_sort\")\n\t\t.on(\"_emdash_fields\")\n\t\t.columns([\"collection_id\", \"sort_order\"])\n\t\t.execute();\n}\n\nexport async function down(db: Kysely<unknown>): Promise<void> {\n\tawait db.schema.dropTable(\"_emdash_fields\").execute();\n\tawait db.schema.dropTable(\"_emdash_collections\").execute();\n}\n","import type { Kysely } from \"kysely\";\n\nimport { currentTimestamp } from \"../dialect-helpers.js\";\n\n/**\n * Migration: Plugin System Tables\n *\n * Creates the plugin storage table and plugin state tracking.\n * Plugin storage uses a document store with declared indexes.\n *\n * @see PLUGIN-SYSTEM.md § Plugin Storage\n */\nexport async function up(db: Kysely<unknown>): Promise<void> {\n\t// Core storage table for plugin documents\n\tawait db.schema\n\t\t.createTable(\"_plugin_storage\")\n\t\t.addColumn(\"plugin_id\", \"text\", (col) => col.notNull())\n\t\t.addColumn(\"collection\", \"text\", (col) => col.notNull())\n\t\t.addColumn(\"id\", \"text\", (col) => col.notNull())\n\t\t.addColumn(\"data\", \"text\", (col) => col.notNull()) // JSON\n\t\t.addColumn(\"created_at\", \"text\", (col) => col.defaultTo(currentTimestamp(db)))\n\t\t.addColumn(\"updated_at\", \"text\", (col) => col.defaultTo(currentTimestamp(db)))\n\t\t.addPrimaryKeyConstraint(\"pk_plugin_storage\", [\"plugin_id\", \"collection\", \"id\"])\n\t\t.execute();\n\n\t// Base index for listing\n\tawait db.schema\n\t\t.createIndex(\"idx_plugin_storage_list\")\n\t\t.on(\"_plugin_storage\")\n\t\t.columns([\"plugin_id\", \"collection\", \"created_at\"])\n\t\t.execute();\n\n\t// Plugin state tracking for lifecycle hooks\n\tawait db.schema\n\t\t.createTable(\"_plugin_state\")\n\t\t.addColumn(\"plugin_id\", \"text\", (col) => col.primaryKey())\n\t\t.addColumn(\"version\", \"text\", (col) => col.notNull())\n\t\t.addColumn(\"status\", \"text\", (col) => col.notNull().defaultTo(\"installed\")) // installed, active, inactive\n\t\t.addColumn(\"installed_at\", \"text\", (col) => col.defaultTo(currentTimestamp(db)))\n\t\t.addColumn(\"activated_at\", \"text\")\n\t\t.addColumn(\"deactivated_at\", \"text\")\n\t\t.addColumn(\"data\", \"text\") // JSON for plugin-specific state\n\t\t.execute();\n\n\t// Index tracking for dynamic expression indexes on plugin storage\n\t// This tracks which indexes have been created so we can manage them\n\tawait db.schema\n\t\t.createTable(\"_plugin_indexes\")\n\t\t.addColumn(\"plugin_id\", \"text\", (col) => col.notNull())\n\t\t.addColumn(\"collection\", \"text\", (col) => col.notNull())\n\t\t.addColumn(\"index_name\", \"text\", (col) => col.notNull())\n\t\t.addColumn(\"fields\", \"text\", (col) => col.notNull()) // JSON array of field paths\n\t\t.addColumn(\"created_at\", \"text\", (col) => col.defaultTo(currentTimestamp(db)))\n\t\t.addPrimaryKeyConstraint(\"pk_plugin_indexes\", [\"plugin_id\", \"collection\", \"index_name\"])\n\t\t.execute();\n}\n\nexport async function down(db: Kysely<unknown>): Promise<void> {\n\tawait db.schema.dropTable(\"_plugin_indexes\").execute();\n\tawait db.schema.dropTable(\"_plugin_state\").execute();\n\tawait db.schema.dropTable(\"_plugin_storage\").execute();\n}\n","import type { Kysely } from \"kysely\";\n\nimport { currentTimestamp } from \"../dialect-helpers.js\";\n\n/**\n * Navigation Menus migration\n *\n * Creates tables for admin-editable navigation menus.\n * Menu items can reference content entries, taxonomy terms, or custom URLs.\n */\nexport async function up(db: Kysely<unknown>): Promise<void> {\n\t// Menu definitions\n\tawait db.schema\n\t\t.createTable(\"_emdash_menus\")\n\t\t.addColumn(\"id\", \"text\", (col) => col.primaryKey())\n\t\t.addColumn(\"name\", \"text\", (col) => col.notNull().unique())\n\t\t.addColumn(\"label\", \"text\", (col) => col.notNull())\n\t\t.addColumn(\"created_at\", \"text\", (col) => col.defaultTo(currentTimestamp(db)))\n\t\t.addColumn(\"updated_at\", \"text\", (col) => col.defaultTo(currentTimestamp(db)))\n\t\t.execute();\n\n\t// Menu items (ordered, hierarchical)\n\tawait db.schema\n\t\t.createTable(\"_emdash_menu_items\")\n\t\t.addColumn(\"id\", \"text\", (col) => col.primaryKey())\n\t\t.addColumn(\"menu_id\", \"text\", (col) => col.notNull())\n\t\t.addColumn(\"parent_id\", \"text\")\n\t\t.addColumn(\"sort_order\", \"integer\", (col) => col.notNull().defaultTo(0))\n\t\t.addColumn(\"type\", \"text\", (col) => col.notNull()) // 'page', 'post', 'custom', 'taxonomy', 'collection'\n\t\t.addColumn(\"reference_collection\", \"text\")\n\t\t.addColumn(\"reference_id\", \"text\")\n\t\t.addColumn(\"custom_url\", \"text\")\n\t\t.addColumn(\"label\", \"text\", (col) => col.notNull())\n\t\t.addColumn(\"title_attr\", \"text\")\n\t\t.addColumn(\"target\", \"text\")\n\t\t.addColumn(\"css_classes\", \"text\")\n\t\t.addColumn(\"created_at\", \"text\", (col) => col.defaultTo(currentTimestamp(db)))\n\t\t.addForeignKeyConstraint(\"menu_items_menu_fk\", [\"menu_id\"], \"_emdash_menus\", [\"id\"], (cb) =>\n\t\t\tcb.onDelete(\"cascade\"),\n\t\t)\n\t\t.addForeignKeyConstraint(\n\t\t\t\"menu_items_parent_fk\",\n\t\t\t[\"parent_id\"],\n\t\t\t\"_emdash_menu_items\",\n\t\t\t[\"id\"],\n\t\t\t(cb) => cb.onDelete(\"cascade\"),\n\t\t)\n\t\t.execute();\n\n\t// Index for efficient menu item queries\n\tawait db.schema\n\t\t.createIndex(\"idx_menu_items_menu\")\n\t\t.on(\"_emdash_menu_items\")\n\t\t.columns([\"menu_id\", \"sort_order\"])\n\t\t.execute();\n\n\tawait db.schema\n\t\t.createIndex(\"idx_menu_items_parent\")\n\t\t.on(\"_emdash_menu_items\")\n\t\t.column(\"parent_id\")\n\t\t.execute();\n}\n\nexport async function down(db: Kysely<unknown>): Promise<void> {\n\tawait db.schema.dropTable(\"_emdash_menu_items\").execute();\n\tawait db.schema.dropTable(\"_emdash_menus\").execute();\n}\n","import type { Kysely } from \"kysely\";\n\nimport { currentTimestamp } from \"../dialect-helpers.js\";\n\n/**\n * Taxonomy definitions migration\n *\n * Adds _emdash_taxonomy_defs table to store taxonomy definitions (category, tag, custom)\n * and seeds default category and tag taxonomies.\n */\nexport async function up(db: Kysely<unknown>): Promise<void> {\n\t// Taxonomy definitions (what taxonomies exist)\n\tawait db.schema\n\t\t.createTable(\"_emdash_taxonomy_defs\")\n\t\t.addColumn(\"id\", \"text\", (col) => col.primaryKey())\n\t\t.addColumn(\"name\", \"text\", (col) => col.notNull().unique()) // 'category', 'tag'\n\t\t.addColumn(\"label\", \"text\", (col) => col.notNull()) // 'Categories'\n\t\t.addColumn(\"label_singular\", \"text\") // 'Category'\n\t\t.addColumn(\"hierarchical\", \"integer\", (col) => col.defaultTo(0)) // 0 or 1\n\t\t.addColumn(\"collections\", \"text\") // JSON array: [\"posts\"]\n\t\t.addColumn(\"created_at\", \"text\", (col) => col.defaultTo(currentTimestamp(db)))\n\t\t.execute();\n\n\t// Seed default taxonomies\n\tawait db\n\t\t// eslint-disable-next-line typescript-eslint(no-unsafe-type-assertion) -- Kysely migration runs against unknown schema\n\t\t.insertInto(\"_emdash_taxonomy_defs\" as never)\n\t\t.values([\n\t\t\t{\n\t\t\t\tid: \"taxdef_category\",\n\t\t\t\tname: \"category\",\n\t\t\t\tlabel: \"Categories\",\n\t\t\t\tlabel_singular: \"Category\",\n\t\t\t\thierarchical: 1,\n\t\t\t\tcollections: JSON.stringify([\"posts\"]),\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: \"taxdef_tag\",\n\t\t\t\tname: \"tag\",\n\t\t\t\tlabel: \"Tags\",\n\t\t\t\tlabel_singular: \"Tag\",\n\t\t\t\thierarchical: 0,\n\t\t\t\tcollections: JSON.stringify([\"posts\"]),\n\t\t\t},\n\t\t])\n\t\t.execute();\n}\n\nexport async function down(db: Kysely<unknown>): Promise<void> {\n\tawait db.schema.dropTable(\"_emdash_taxonomy_defs\").execute();\n}\n","import { type Kysely, sql } from \"kysely\";\n\nexport async function up(db: Kysely<unknown>): Promise<void> {\n\t// Widget areas table\n\tawait db.schema\n\t\t.createTable(\"_emdash_widget_areas\")\n\t\t.addColumn(\"id\", \"text\", (col) => col.primaryKey())\n\t\t.addColumn(\"name\", \"text\", (col) => col.notNull().unique())\n\t\t.addColumn(\"label\", \"text\", (col) => col.notNull())\n\t\t.addColumn(\"description\", \"text\")\n\t\t.addColumn(\"created_at\", \"text\", (col) => col.defaultTo(sql`CURRENT_TIMESTAMP`))\n\t\t.execute();\n\n\t// Widgets table\n\tawait db.schema\n\t\t.createTable(\"_emdash_widgets\")\n\t\t.addColumn(\"id\", \"text\", (col) => col.primaryKey())\n\t\t.addColumn(\"area_id\", \"text\", (col) =>\n\t\t\tcol.notNull().references(\"_emdash_widget_areas.id\").onDelete(\"cascade\"),\n\t\t)\n\t\t.addColumn(\"sort_order\", \"integer\", (col) => col.notNull().defaultTo(0))\n\t\t.addColumn(\"type\", \"text\", (col) => col.notNull()) // 'content', 'menu', 'component'\n\t\t.addColumn(\"title\", \"text\")\n\t\t.addColumn(\"content\", \"text\") // JSON: Portable Text for content type\n\t\t.addColumn(\"menu_name\", \"text\") // For menu type\n\t\t.addColumn(\"component_id\", \"text\") // For component type: 'core:recent-posts'\n\t\t.addColumn(\"component_props\", \"text\") // JSON: props for component\n\t\t.addColumn(\"created_at\", \"text\", (col) => col.defaultTo(sql`CURRENT_TIMESTAMP`))\n\t\t.execute();\n\n\t// Index for efficient lookups by area and order\n\tawait db.schema\n\t\t.createIndex(\"idx_widgets_area\")\n\t\t.on(\"_emdash_widgets\")\n\t\t.columns([\"area_id\", \"sort_order\"])\n\t\t.execute();\n}\n\nexport async function down(db: Kysely<unknown>): Promise<void> {\n\tawait db.schema.dropTable(\"_emdash_widgets\").execute();\n\tawait db.schema.dropTable(\"_emdash_widget_areas\").execute();\n}\n","import type { Kysely } from \"kysely\";\nimport { sql } from \"kysely\";\n\nimport { binaryType, currentTimestamp, currentTimestampValue } from \"../dialect-helpers.js\";\n\n/**\n * Auth migration - passkey-first authentication\n *\n * Changes:\n * - Removes password_hash from users (no passwords)\n * - Adds role as integer (RBAC levels)\n * - Adds email_verified, avatar_url, updated_at to users\n * - Creates credentials table (passkeys)\n * - Creates auth_tokens table (magic links, invites)\n * - Creates oauth_accounts table (external provider links)\n * - Creates allowed_domains table (self-signup)\n */\nexport async function up(db: Kysely<unknown>): Promise<void> {\n\t// SQLite doesn't support DROP COLUMN directly, so we need to recreate the table\n\t// Create new users table with updated schema\n\tawait db.schema\n\t\t.createTable(\"users_new\")\n\t\t.addColumn(\"id\", \"text\", (col) => col.primaryKey())\n\t\t.addColumn(\"email\", \"text\", (col) => col.notNull().unique())\n\t\t.addColumn(\"name\", \"text\")\n\t\t.addColumn(\"avatar_url\", \"text\")\n\t\t.addColumn(\"role\", \"integer\", (col) => col.notNull().defaultTo(10)) // SUBSCRIBER\n\t\t.addColumn(\"email_verified\", \"integer\", (col) => col.notNull().defaultTo(0))\n\t\t.addColumn(\"data\", \"text\")\n\t\t.addColumn(\"created_at\", \"text\", (col) => col.defaultTo(currentTimestamp(db)))\n\t\t.addColumn(\"updated_at\", \"text\", (col) => col.defaultTo(currentTimestamp(db)))\n\t\t.execute();\n\n\t// Migrate existing data (map old role strings to new integer levels)\n\tawait sql`\n\t\tINSERT INTO users_new (id, email, name, role, data, created_at, updated_at)\n\t\tSELECT\n\t\t\tid,\n\t\t\temail,\n\t\t\tname,\n\t\t\tCASE role\n\t\t\t\tWHEN 'admin' THEN 50\n\t\t\t\tWHEN 'editor' THEN 40\n\t\t\t\tWHEN 'author' THEN 30\n\t\t\t\tWHEN 'contributor' THEN 20\n\t\t\t\tELSE 10\n\t\t\tEND,\n\t\t\tdata,\n\t\t\tcreated_at,\n\t\t\t${currentTimestampValue(db)}\n\t\tFROM users\n\t`.execute(db);\n\n\t// Drop old table and rename new one\n\tawait db.schema.dropTable(\"users\").execute();\n\tawait sql`ALTER TABLE users_new RENAME TO users`.execute(db);\n\n\t// Recreate index\n\tawait db.schema.createIndex(\"idx_users_email\").on(\"users\").column(\"email\").execute();\n\n\t// Passkey credentials\n\tawait db.schema\n\t\t.createTable(\"credentials\")\n\t\t.addColumn(\"id\", \"text\", (col) => col.primaryKey()) // Base64url credential ID\n\t\t.addColumn(\"user_id\", \"text\", (col) => col.notNull())\n\t\t.addColumn(\"public_key\", binaryType(db), (col) => col.notNull()) // COSE public key\n\t\t.addColumn(\"counter\", \"integer\", (col) => col.notNull().defaultTo(0))\n\t\t.addColumn(\"device_type\", \"text\", (col) => col.notNull()) // 'singleDevice' | 'multiDevice'\n\t\t.addColumn(\"backed_up\", \"integer\", (col) => col.notNull().defaultTo(0))\n\t\t.addColumn(\"transports\", \"text\") // JSON array\n\t\t.addColumn(\"name\", \"text\") // User-friendly name\n\t\t.addColumn(\"created_at\", \"text\", (col) => col.defaultTo(currentTimestamp(db)))\n\t\t.addColumn(\"last_used_at\", \"text\", (col) => col.defaultTo(currentTimestamp(db)))\n\t\t.addForeignKeyConstraint(\"credentials_user_fk\", [\"user_id\"], \"users\", [\"id\"], (cb) =>\n\t\t\tcb.onDelete(\"cascade\"),\n\t\t)\n\t\t.execute();\n\n\tawait db.schema.createIndex(\"idx_credentials_user\").on(\"credentials\").column(\"user_id\").execute();\n\n\t// Auth tokens (magic links, email verification, invites, recovery)\n\tawait db.schema\n\t\t.createTable(\"auth_tokens\")\n\t\t.addColumn(\"hash\", \"text\", (col) => col.primaryKey()) // SHA-256 hash of token\n\t\t.addColumn(\"user_id\", \"text\")\n\t\t.addColumn(\"email\", \"text\") // For pre-user tokens\n\t\t.addColumn(\"type\", \"text\", (col) => col.notNull()) // 'magic_link' | 'email_verify' | 'invite' | 'recovery'\n\t\t.addColumn(\"role\", \"integer\") // For invites\n\t\t.addColumn(\"invited_by\", \"text\")\n\t\t.addColumn(\"expires_at\", \"text\", (col) => col.notNull())\n\t\t.addColumn(\"created_at\", \"text\", (col) => col.defaultTo(currentTimestamp(db)))\n\t\t.addForeignKeyConstraint(\"auth_tokens_user_fk\", [\"user_id\"], \"users\", [\"id\"], (cb) =>\n\t\t\tcb.onDelete(\"cascade\"),\n\t\t)\n\t\t.addForeignKeyConstraint(\"auth_tokens_invited_by_fk\", [\"invited_by\"], \"users\", [\"id\"], (cb) =>\n\t\t\tcb.onDelete(\"set null\"),\n\t\t)\n\t\t.execute();\n\n\tawait db.schema.createIndex(\"idx_auth_tokens_email\").on(\"auth_tokens\").column(\"email\").execute();\n\n\t// OAuth accounts (external provider links)\n\tawait db.schema\n\t\t.createTable(\"oauth_accounts\")\n\t\t.addColumn(\"provider\", \"text\", (col) => col.notNull())\n\t\t.addColumn(\"provider_account_id\", \"text\", (col) => col.notNull())\n\t\t.addColumn(\"user_id\", \"text\", (col) => col.notNull())\n\t\t.addColumn(\"created_at\", \"text\", (col) => col.defaultTo(currentTimestamp(db)))\n\t\t.addPrimaryKeyConstraint(\"oauth_accounts_pk\", [\"provider\", \"provider_account_id\"])\n\t\t.addForeignKeyConstraint(\"oauth_accounts_user_fk\", [\"user_id\"], \"users\", [\"id\"], (cb) =>\n\t\t\tcb.onDelete(\"cascade\"),\n\t\t)\n\t\t.execute();\n\n\tawait db.schema\n\t\t.createIndex(\"idx_oauth_accounts_user\")\n\t\t.on(\"oauth_accounts\")\n\t\t.column(\"user_id\")\n\t\t.execute();\n\n\t// Allowed domains for self-signup\n\tawait db.schema\n\t\t.createTable(\"allowed_domains\")\n\t\t.addColumn(\"domain\", \"text\", (col) => col.primaryKey())\n\t\t.addColumn(\"default_role\", \"integer\", (col) => col.notNull().defaultTo(20)) // CONTRIBUTOR\n\t\t.addColumn(\"enabled\", \"integer\", (col) => col.notNull().defaultTo(1))\n\t\t.addColumn(\"created_at\", \"text\", (col) => col.defaultTo(currentTimestamp(db)))\n\t\t.execute();\n\n\t// WebAuthn challenges (ephemeral, with TTL)\n\tawait db.schema\n\t\t.createTable(\"auth_challenges\")\n\t\t.addColumn(\"challenge\", \"text\", (col) => col.primaryKey()) // Base64url challenge\n\t\t.addColumn(\"type\", \"text\", (col) => col.notNull()) // 'registration' | 'authentication'\n\t\t.addColumn(\"user_id\", \"text\") // For registration, the user being registered\n\t\t.addColumn(\"data\", \"text\") // JSON for additional context\n\t\t.addColumn(\"expires_at\", \"text\", (col) => col.notNull())\n\t\t.addColumn(\"created_at\", \"text\", (col) => col.defaultTo(currentTimestamp(db)))\n\t\t.execute();\n\n\t// Index for efficient cleanup of expired challenges\n\tawait db.schema\n\t\t.createIndex(\"idx_auth_challenges_expires\")\n\t\t.on(\"auth_challenges\")\n\t\t.column(\"expires_at\")\n\t\t.execute();\n}\n\nexport async function down(db: Kysely<unknown>): Promise<void> {\n\t// Drop new tables\n\tawait db.schema.dropTable(\"auth_challenges\").execute();\n\tawait db.schema.dropTable(\"allowed_domains\").execute();\n\tawait db.schema.dropTable(\"oauth_accounts\").execute();\n\tawait db.schema.dropTable(\"auth_tokens\").execute();\n\tawait db.schema.dropTable(\"credentials\").execute();\n\n\t// Recreate old users table with password_hash\n\tawait db.schema\n\t\t.createTable(\"users_old\")\n\t\t.addColumn(\"id\", \"text\", (col) => col.primaryKey())\n\t\t.addColumn(\"email\", \"text\", (col) => col.notNull().unique())\n\t\t.addColumn(\"password_hash\", \"text\", (col) => col.notNull())\n\t\t.addColumn(\"name\", \"text\")\n\t\t.addColumn(\"role\", \"text\", (col) => col.defaultTo(\"subscriber\"))\n\t\t.addColumn(\"avatar_id\", \"text\")\n\t\t.addColumn(\"data\", \"text\")\n\t\t.addColumn(\"created_at\", \"text\", (col) => col.defaultTo(currentTimestamp(db)))\n\t\t.execute();\n\n\t// Migrate data back (users will have empty password_hash)\n\tawait sql`\n\t\tINSERT INTO users_old (id, email, password_hash, name, role, data, created_at)\n\t\tSELECT\n\t\t\tid,\n\t\t\temail,\n\t\t\t'', -- No way to restore password\n\t\t\tname,\n\t\t\tCASE role\n\t\t\t\tWHEN 50 THEN 'admin'\n\t\t\t\tWHEN 40 THEN 'editor'\n\t\t\t\tWHEN 30 THEN 'author'\n\t\t\t\tWHEN 20 THEN 'contributor'\n\t\t\t\tELSE 'subscriber'\n\t\t\tEND,\n\t\t\tdata,\n\t\t\tcreated_at\n\t\tFROM users\n\t`.execute(db);\n\n\tawait db.schema.dropTable(\"users\").execute();\n\tawait sql`ALTER TABLE users_old RENAME TO users`.execute(db);\n\n\tawait db.schema.createIndex(\"idx_users_email\").on(\"users\").column(\"email\").execute();\n}\n","import type { Kysely } from \"kysely\";\nimport { sql } from \"kysely\";\n\n/**\n * User disabled column - for soft-disabling users\n *\n * Changes:\n * - Adds disabled column to users table (INTEGER, default 0)\n * - Disabled users cannot log in\n */\nexport async function up(db: Kysely<unknown>): Promise<void> {\n\t// SQLite supports ADD COLUMN\n\tawait sql`ALTER TABLE users ADD COLUMN disabled INTEGER NOT NULL DEFAULT 0`.execute(db);\n\n\t// Create index for querying active users\n\tawait db.schema.createIndex(\"idx_users_disabled\").on(\"users\").column(\"disabled\").execute();\n}\n\nexport async function down(db: Kysely<unknown>): Promise<void> {\n\t// SQLite doesn't support DROP COLUMN directly, but we can drop the index\n\t// For full rollback, table would need to be recreated\n\tawait db.schema.dropIndex(\"idx_users_disabled\").execute();\n\n\t// SQLite 3.35.0+ supports DROP COLUMN, but for compatibility:\n\t// We'll leave the column but document that it's deprecated\n\t// In production, you'd recreate the table without the column\n}\n","import { type Kysely, sql } from \"kysely\";\n\n/**\n * Migration: Add sections tables and performance indexes\n *\n * Sections are reusable content blocks that can be inserted into any Portable Text field.\n * They provide a library of pre-built page sections (heroes, CTAs, testimonials, etc.)\n * that content authors can browse and insert with a single click.\n */\nexport async function up(db: Kysely<unknown>): Promise<void> {\n\t// Section categories table\n\tawait db.schema\n\t\t.createTable(\"_emdash_section_categories\")\n\t\t.addColumn(\"id\", \"text\", (col) => col.primaryKey())\n\t\t.addColumn(\"slug\", \"text\", (col) => col.notNull().unique())\n\t\t.addColumn(\"label\", \"text\", (col) => col.notNull())\n\t\t.addColumn(\"sort_order\", \"integer\", (col) => col.defaultTo(0))\n\t\t.addColumn(\"created_at\", \"text\", (col) => col.defaultTo(sql`CURRENT_TIMESTAMP`))\n\t\t.execute();\n\n\t// Sections table\n\tawait db.schema\n\t\t.createTable(\"_emdash_sections\")\n\t\t.addColumn(\"id\", \"text\", (col) => col.primaryKey())\n\t\t.addColumn(\"slug\", \"text\", (col) => col.notNull().unique())\n\t\t.addColumn(\"title\", \"text\", (col) => col.notNull())\n\t\t.addColumn(\"description\", \"text\")\n\t\t// Categorization\n\t\t.addColumn(\"category_id\", \"text\", (col) =>\n\t\t\tcol.references(\"_emdash_section_categories.id\").onDelete(\"set null\"),\n\t\t)\n\t\t.addColumn(\"keywords\", \"text\") // JSON array for search\n\t\t// Content (Portable Text array)\n\t\t.addColumn(\"content\", \"text\", (col) => col.notNull()) // JSON\n\t\t// Preview image (optional)\n\t\t.addColumn(\"preview_media_id\", \"text\")\n\t\t// Source tracking\n\t\t.addColumn(\"source\", \"text\", (col) => col.notNull().defaultTo(\"user\")) // 'theme', 'user', 'import'\n\t\t.addColumn(\"theme_id\", \"text\") // Which theme provided it (if source='theme')\n\t\t// Metadata\n\t\t.addColumn(\"created_at\", \"text\", (col) => col.defaultTo(sql`CURRENT_TIMESTAMP`))\n\t\t.addColumn(\"updated_at\", \"text\", (col) => col.defaultTo(sql`CURRENT_TIMESTAMP`))\n\t\t.execute();\n\n\t// Index for efficient category lookups\n\tawait db.schema\n\t\t.createIndex(\"idx_sections_category\")\n\t\t.on(\"_emdash_sections\")\n\t\t.columns([\"category_id\"])\n\t\t.execute();\n\n\t// Index for filtering by source\n\tawait db.schema\n\t\t.createIndex(\"idx_sections_source\")\n\t\t.on(\"_emdash_sections\")\n\t\t.columns([\"source\"])\n\t\t.execute();\n}\n\nexport async function down(db: Kysely<unknown>): Promise<void> {\n\tawait db.schema.dropIndex(\"idx_content_taxonomies_term\").execute();\n\tawait db.schema.dropIndex(\"idx_media_mime_type\").execute();\n\tawait db.schema.dropTable(\"_emdash_sections\").execute();\n\tawait db.schema.dropTable(\"_emdash_section_categories\").execute();\n}\n","import type { Kysely } from \"kysely\";\n\n/**\n * Migration: Search Support\n *\n * Adds search configuration to collections and searchable flag to fields.\n * FTS5 tables are created dynamically when search is enabled for a collection.\n */\nexport async function up(db: Kysely<unknown>): Promise<void> {\n\t// Add search_config to collections (JSON: { enabled, weights })\n\tawait db.schema.alterTable(\"_emdash_collections\").addColumn(\"search_config\", \"text\").execute();\n\n\t// Add searchable flag to fields\n\tawait db.schema\n\t\t.alterTable(\"_emdash_fields\")\n\t\t.addColumn(\"searchable\", \"integer\", (col) => col.defaultTo(0))\n\t\t.execute();\n}\n\nexport async function down(db: Kysely<unknown>): Promise<void> {\n\t// SQLite doesn't support DROP COLUMN in older versions, but modern SQLite does\n\t// These columns are safe to drop\n\tawait db.schema.alterTable(\"_emdash_fields\").dropColumn(\"searchable\").execute();\n\tawait db.schema.alterTable(\"_emdash_collections\").dropColumn(\"search_config\").execute();\n}\n","import type { Kysely } from \"kysely\";\nimport { sql } from \"kysely\";\n\nimport { listTablesLike } from \"../dialect-helpers.js\";\n\n/**\n * Migration: Add scheduled publishing support\n *\n * Adds scheduled_at column to all ec_* content tables.\n * When scheduled_at is set and status is 'scheduled', the content\n * will be auto-published when the scheduled time is reached.\n */\nexport async function up(db: Kysely<unknown>): Promise<void> {\n\t// Get all ec_* content tables\n\tconst tableNames = await listTablesLike(db, \"ec_%\");\n\n\t// Add scheduled_at column to each content table\n\tfor (const tableName of tableNames) {\n\t\tconst table = { name: tableName };\n\t\tawait sql`\n\t\t\tALTER TABLE ${sql.ref(table.name)} \n\t\t\tADD COLUMN scheduled_at TEXT\n\t\t`.execute(db);\n\n\t\t// Create index for efficient scheduled content queries\n\t\tawait sql`\n\t\t\tCREATE INDEX ${sql.ref(`idx_${table.name}_scheduled`)} \n\t\t\tON ${sql.ref(table.name)} (scheduled_at)\n\t\t\tWHERE scheduled_at IS NOT NULL AND status = 'scheduled'\n\t\t`.execute(db);\n\t}\n}\n\nexport async function down(db: Kysely<unknown>): Promise<void> {\n\t// Get all ec_* content tables\n\tconst tableNames = await listTablesLike(db, \"ec_%\");\n\n\t// Drop scheduled_at column from each content table\n\tfor (const tableName of tableNames) {\n\t\tconst table = { name: tableName };\n\t\t// Drop index first\n\t\tawait sql`\n\t\t\tDROP INDEX IF EXISTS ${sql.ref(`idx_${table.name}_scheduled`)}\n\t\t`.execute(db);\n\n\t\tawait sql`\n\t\t\tALTER TABLE ${sql.ref(table.name)} \n\t\t\tDROP COLUMN scheduled_at\n\t\t`.execute(db);\n\t}\n}\n","import type { Kysely } from \"kysely\";\nimport { sql } from \"kysely\";\n\nexport async function up(db: Kysely<unknown>): Promise<void> {\n\t// Get all content tables\n\tconst tables = await db\n\t\t// eslint-disable-next-line typescript-eslint(no-unsafe-type-assertion) -- Kysely migration runs against unknown schema\n\t\t.selectFrom(\"_emdash_collections\" as never)\n\t\t// eslint-disable-next-line typescript-eslint(no-unsafe-type-assertion) -- Kysely migration runs against unknown schema\n\t\t.select(\"slug\" as never)\n\t\t.execute();\n\n\t// eslint-disable-next-line typescript-eslint(no-unsafe-type-assertion) -- Kysely execute returns unknown[]; narrowing to known shape\n\tfor (const row of tables as Array<{ slug: string }>) {\n\t\tconst tableName = `ec_${row.slug}`;\n\n\t\t// Add live_revision_id column\n\t\tawait sql`\n\t\t\tALTER TABLE ${sql.ref(tableName)}\n\t\t\tADD COLUMN live_revision_id TEXT REFERENCES revisions(id)\n\t\t`.execute(db);\n\n\t\t// Add draft_revision_id column\n\t\tawait sql`\n\t\t\tALTER TABLE ${sql.ref(tableName)}\n\t\t\tADD COLUMN draft_revision_id TEXT REFERENCES revisions(id)\n\t\t`.execute(db);\n\n\t\t// Create indexes for the new columns\n\t\tawait sql`\n\t\t\tCREATE INDEX ${sql.ref(`idx_${row.slug}_live_revision`)}\n\t\t\tON ${sql.ref(tableName)} (live_revision_id)\n\t\t`.execute(db);\n\n\t\tawait sql`\n\t\t\tCREATE INDEX ${sql.ref(`idx_${row.slug}_draft_revision`)}\n\t\t\tON ${sql.ref(tableName)} (draft_revision_id)\n\t\t`.execute(db);\n\t}\n}\n\nexport async function down(db: Kysely<unknown>): Promise<void> {\n\tconst tables = await db\n\t\t// eslint-disable-next-line typescript-eslint(no-unsafe-type-assertion) -- Kysely migration runs against unknown schema\n\t\t.selectFrom(\"_emdash_collections\" as never)\n\t\t// eslint-disable-next-line typescript-eslint(no-unsafe-type-assertion) -- Kysely migration runs against unknown schema\n\t\t.select(\"slug\" as never)\n\t\t.execute();\n\n\t// eslint-disable-next-line typescript-eslint(no-unsafe-type-assertion) -- Kysely execute returns unknown[]; narrowing to known shape\n\tfor (const row of tables as Array<{ slug: string }>) {\n\t\tconst tableName = `ec_${row.slug}`;\n\n\t\tawait sql`\n\t\t\tDROP INDEX IF EXISTS ${sql.ref(`idx_${row.slug}_draft_revision`)}\n\t\t`.execute(db);\n\n\t\tawait sql`\n\t\t\tDROP INDEX IF EXISTS ${sql.ref(`idx_${row.slug}_live_revision`)}\n\t\t`.execute(db);\n\n\t\tawait sql`\n\t\t\tALTER TABLE ${sql.ref(tableName)}\n\t\t\tDROP COLUMN draft_revision_id\n\t\t`.execute(db);\n\n\t\tawait sql`\n\t\t\tALTER TABLE ${sql.ref(tableName)}\n\t\t\tDROP COLUMN live_revision_id\n\t\t`.execute(db);\n\t}\n}\n","import type { Kysely } from \"kysely\";\nimport { sql } from \"kysely\";\n\nimport { listTablesLike } from \"../dialect-helpers.js\";\n\n/**\n * Add performance indexes for common query patterns.\n *\n * Covers:\n * 1. Media table: mime_type, filename, created_at\n * 2. content_taxonomies: reverse lookup by taxonomy_id\n * 3. taxonomies: parent_id FK\n * 4. audit_logs: compound (resource_type, resource_id)\n * 5. Retroactive author_id + updated_at on existing ec_* content tables\n * (new tables get these from createContentTable() in registry.ts)\n */\nexport async function up(db: Kysely<unknown>): Promise<void> {\n\t// ── Media indexes ────────────────────────────────────────────────\n\tawait db.schema.createIndex(\"idx_media_mime_type\").on(\"media\").column(\"mime_type\").execute();\n\tawait db.schema.createIndex(\"idx_media_filename\").on(\"media\").column(\"filename\").execute();\n\tawait db.schema.createIndex(\"idx_media_created_at\").on(\"media\").column(\"created_at\").execute();\n\n\t// ── Taxonomy indexes ─────────────────────────────────────────────\n\t// Reverse lookup: find entries with a specific term\n\tawait db.schema\n\t\t.createIndex(\"idx_content_taxonomies_term\")\n\t\t.on(\"content_taxonomies\")\n\t\t.column(\"taxonomy_id\")\n\t\t.execute();\n\n\t// Hierarchical queries filter on parent_id FK\n\tawait db.schema\n\t\t.createIndex(\"idx_taxonomies_parent\")\n\t\t.on(\"taxonomies\")\n\t\t.column(\"parent_id\")\n\t\t.execute();\n\n\t// ── Audit log indexes ────────────────────────────────────────────\n\t// findByResource() compound query\n\tawait db.schema\n\t\t.createIndex(\"idx_audit_resource\")\n\t\t.on(\"audit_logs\")\n\t\t.columns([\"resource_type\", \"resource_id\"])\n\t\t.execute();\n\n\t// ── Retroactive content table indexes ────────────────────────────\n\t// Add author_id and updated_at indexes to all existing ec_* tables.\n\t// New tables created after this migration get these from createContentTable().\n\tconst tableNames = await listTablesLike(db, \"ec_%\");\n\n\tfor (const tableName of tableNames) {\n\t\tconst table = { name: tableName };\n\t\tawait sql`\n\t\t\tCREATE INDEX ${sql.ref(`idx_${table.name}_author`)} \n\t\t\tON ${sql.ref(table.name)} (author_id)\n\t\t`.execute(db);\n\n\t\tawait sql`\n\t\t\tCREATE INDEX ${sql.ref(`idx_${table.name}_updated`)} \n\t\t\tON ${sql.ref(table.name)} (updated_at)\n\t\t`.execute(db);\n\t}\n}\n\nexport async function down(db: Kysely<unknown>): Promise<void> {\n\t// Drop retroactive content table indexes\n\tconst tableNames = await listTablesLike(db, \"ec_%\");\n\n\tfor (const tableName of tableNames) {\n\t\tconst table = { name: tableName };\n\t\tawait sql`DROP INDEX IF EXISTS ${sql.ref(`idx_${table.name}_updated`)}`.execute(db);\n\t\tawait sql`DROP INDEX IF EXISTS ${sql.ref(`idx_${table.name}_author`)}`.execute(db);\n\t}\n\n\t// Drop system table indexes\n\tawait db.schema.dropIndex(\"idx_audit_resource\").execute();\n\tawait db.schema.dropIndex(\"idx_taxonomies_parent\").execute();\n\tawait db.schema.dropIndex(\"idx_content_taxonomies_term\").execute();\n\tawait db.schema.dropIndex(\"idx_media_created_at\").execute();\n\tawait db.schema.dropIndex(\"idx_media_filename\").execute();\n\tawait db.schema.dropIndex(\"idx_media_mime_type\").execute();\n}\n","import type { Kysely } from \"kysely\";\n\nimport { currentTimestamp } from \"../dialect-helpers.js\";\n\n/**\n * API token tables for programmatic access.\n *\n * Three tables:\n * 1. _emdash_api_tokens — Personal Access Tokens (ec_pat_...)\n * 2. _emdash_oauth_tokens — OAuth access/refresh tokens (ec_oat_/ec_ort_...)\n * 3. _emdash_device_codes — OAuth Device Flow state (RFC 8628)\n */\nexport async function up(db: Kysely<unknown>): Promise<void> {\n\t// ── Personal Access Tokens ───────────────────────────────────────\n\tawait db.schema\n\t\t.createTable(\"_emdash_api_tokens\")\n\t\t.addColumn(\"id\", \"text\", (col) => col.primaryKey())\n\t\t.addColumn(\"name\", \"text\", (col) => col.notNull())\n\t\t.addColumn(\"token_hash\", \"text\", (col) => col.notNull().unique())\n\t\t.addColumn(\"prefix\", \"text\", (col) => col.notNull())\n\t\t.addColumn(\"user_id\", \"text\", (col) => col.notNull())\n\t\t.addColumn(\"scopes\", \"text\", (col) => col.notNull()) // JSON array\n\t\t.addColumn(\"expires_at\", \"text\") // null = no expiry\n\t\t.addColumn(\"last_used_at\", \"text\")\n\t\t.addColumn(\"created_at\", \"text\", (col) => col.defaultTo(currentTimestamp(db)))\n\t\t.addForeignKeyConstraint(\"api_tokens_user_fk\", [\"user_id\"], \"users\", [\"id\"], (cb) =>\n\t\t\tcb.onDelete(\"cascade\"),\n\t\t)\n\t\t.execute();\n\n\tawait db.schema\n\t\t.createIndex(\"idx_api_tokens_token_hash\")\n\t\t.on(\"_emdash_api_tokens\")\n\t\t.column(\"token_hash\")\n\t\t.execute();\n\n\tawait db.schema\n\t\t.createIndex(\"idx_api_tokens_user_id\")\n\t\t.on(\"_emdash_api_tokens\")\n\t\t.column(\"user_id\")\n\t\t.execute();\n\n\t// ── OAuth Tokens ─────────────────────────────────────────────────\n\tawait db.schema\n\t\t.createTable(\"_emdash_oauth_tokens\")\n\t\t.addColumn(\"token_hash\", \"text\", (col) => col.primaryKey())\n\t\t.addColumn(\"token_type\", \"text\", (col) => col.notNull()) // 'access' | 'refresh'\n\t\t.addColumn(\"user_id\", \"text\", (col) => col.notNull())\n\t\t.addColumn(\"scopes\", \"text\", (col) => col.notNull()) // JSON array\n\t\t.addColumn(\"client_type\", \"text\", (col) => col.notNull().defaultTo(\"cli\"))\n\t\t.addColumn(\"expires_at\", \"text\", (col) => col.notNull())\n\t\t.addColumn(\"refresh_token_hash\", \"text\") // links access → refresh\n\t\t.addColumn(\"created_at\", \"text\", (col) => col.defaultTo(currentTimestamp(db)))\n\t\t.addForeignKeyConstraint(\"oauth_tokens_user_fk\", [\"user_id\"], \"users\", [\"id\"], (cb) =>\n\t\t\tcb.onDelete(\"cascade\"),\n\t\t)\n\t\t.execute();\n\n\tawait db.schema\n\t\t.createIndex(\"idx_oauth_tokens_user_id\")\n\t\t.on(\"_emdash_oauth_tokens\")\n\t\t.column(\"user_id\")\n\t\t.execute();\n\n\tawait db.schema\n\t\t.createIndex(\"idx_oauth_tokens_expires\")\n\t\t.on(\"_emdash_oauth_tokens\")\n\t\t.column(\"expires_at\")\n\t\t.execute();\n\n\t// ── Device Codes (OAuth Device Flow, RFC 8628) ───────────────────\n\tawait db.schema\n\t\t.createTable(\"_emdash_device_codes\")\n\t\t.addColumn(\"device_code\", \"text\", (col) => col.primaryKey())\n\t\t.addColumn(\"user_code\", \"text\", (col) => col.notNull().unique())\n\t\t.addColumn(\"scopes\", \"text\", (col) => col.notNull()) // JSON array\n\t\t.addColumn(\"user_id\", \"text\") // set when user authorizes\n\t\t.addColumn(\"status\", \"text\", (col) => col.notNull().defaultTo(\"pending\"))\n\t\t.addColumn(\"expires_at\", \"text\", (col) => col.notNull())\n\t\t.addColumn(\"interval\", \"integer\", (col) => col.notNull().defaultTo(5))\n\t\t.addColumn(\"created_at\", \"text\", (col) => col.defaultTo(currentTimestamp(db)))\n\t\t.execute();\n}\n\nexport async function down(db: Kysely<unknown>): Promise<void> {\n\tawait db.schema.dropTable(\"_emdash_device_codes\").execute();\n\tawait db.schema.dropTable(\"_emdash_oauth_tokens\").execute();\n\tawait db.schema.dropTable(\"_emdash_api_tokens\").execute();\n}\n","import type { Kysely } from \"kysely\";\nimport { sql } from \"kysely\";\n\nimport { currentTimestamp } from \"../dialect-helpers.js\";\n\n/**\n * Authorization codes for OAuth 2.1 Authorization Code + PKCE flow.\n *\n * Used by MCP clients (Claude Desktop, VS Code, etc.) to authenticate\n * via the standard OAuth authorization code grant.\n *\n * Also adds client_id tracking to oauth_tokens for per-client revocation.\n */\nexport async function up(db: Kysely<unknown>): Promise<void> {\n\tawait db.schema\n\t\t.createTable(\"_emdash_authorization_codes\")\n\t\t.addColumn(\"code_hash\", \"text\", (col) => col.primaryKey()) // SHA-256 hash of authorization code\n\t\t.addColumn(\"client_id\", \"text\", (col) => col.notNull()) // CIMD URL or opaque string\n\t\t.addColumn(\"redirect_uri\", \"text\", (col) => col.notNull()) // Must match exactly on exchange\n\t\t.addColumn(\"user_id\", \"text\", (col) => col.notNull())\n\t\t.addColumn(\"scopes\", \"text\", (col) => col.notNull()) // JSON array\n\t\t.addColumn(\"code_challenge\", \"text\", (col) => col.notNull()) // S256 challenge\n\t\t.addColumn(\"code_challenge_method\", \"text\", (col) => col.notNull().defaultTo(\"S256\"))\n\t\t.addColumn(\"resource\", \"text\") // RFC 8707 resource indicator\n\t\t.addColumn(\"expires_at\", \"text\", (col) => col.notNull())\n\t\t.addColumn(\"created_at\", \"text\", (col) => col.defaultTo(currentTimestamp(db)))\n\t\t.addForeignKeyConstraint(\"auth_codes_user_fk\", [\"user_id\"], \"users\", [\"id\"], (cb) =>\n\t\t\tcb.onDelete(\"cascade\"),\n\t\t)\n\t\t.execute();\n\n\tawait db.schema\n\t\t.createIndex(\"idx_auth_codes_expires\")\n\t\t.on(\"_emdash_authorization_codes\")\n\t\t.column(\"expires_at\")\n\t\t.execute();\n\n\t// Track which client obtained a token (for per-client revocation)\n\tawait sql`ALTER TABLE _emdash_oauth_tokens ADD COLUMN client_id TEXT`.execute(db);\n}\n\nexport async function down(db: Kysely<unknown>): Promise<void> {\n\tawait db.schema.dropTable(\"_emdash_authorization_codes\").execute();\n\t// SQLite doesn't support DROP COLUMN, but this is only for dev rollback\n}\n","import type { Kysely } from \"kysely\";\nimport { sql } from \"kysely\";\n\nimport { currentTimestamp } from \"../dialect-helpers.js\";\n\n/**\n * Migration: SEO support\n *\n * Creates:\n * - `_emdash_seo` table: per-content SEO metadata (separate from content tables)\n * - `has_seo` column on `_emdash_collections`: opt-in flag per collection\n *\n * SEO is not a universal concern — only collections representing web pages\n * need it. The `has_seo` flag controls whether the admin shows SEO fields\n * and whether the collection's content appears in sitemaps.\n */\nexport async function up(db: Kysely<unknown>): Promise<void> {\n\t// Create the SEO table\n\tawait db.schema\n\t\t.createTable(\"_emdash_seo\")\n\t\t.addColumn(\"collection\", \"text\", (col) => col.notNull())\n\t\t.addColumn(\"content_id\", \"text\", (col) => col.notNull())\n\t\t.addColumn(\"seo_title\", \"text\")\n\t\t.addColumn(\"seo_description\", \"text\")\n\t\t.addColumn(\"seo_image\", \"text\")\n\t\t.addColumn(\"seo_canonical\", \"text\")\n\t\t.addColumn(\"seo_no_index\", \"integer\", (col) => col.notNull().defaultTo(0))\n\t\t.addColumn(\"created_at\", \"text\", (col) => col.notNull().defaultTo(currentTimestamp(db)))\n\t\t.addColumn(\"updated_at\", \"text\", (col) => col.notNull().defaultTo(currentTimestamp(db)))\n\t\t.addPrimaryKeyConstraint(\"_emdash_seo_pk\", [\"collection\", \"content_id\"])\n\t\t.execute();\n\n\t// Index for batch lookups by collection (PK covers point lookups).\n\t// Sitemap queries join on (collection, content_id) which the PK covers,\n\t// and filter seo_no_index. This index supports getMany() batch queries.\n\tawait sql`\n\t\tCREATE INDEX idx_emdash_seo_collection\n\t\tON _emdash_seo (collection)\n\t`.execute(db);\n\n\t// Add has_seo flag to collections\n\tawait sql`\n\t\tALTER TABLE _emdash_collections\n\t\tADD COLUMN has_seo INTEGER NOT NULL DEFAULT 0\n\t`.execute(db);\n}\n\nexport async function down(db: Kysely<unknown>): Promise<void> {\n\tawait sql`DROP TABLE IF EXISTS _emdash_seo`.execute(db);\n\n\t// SQLite doesn't support DROP COLUMN before 3.35.0, but D1 does\n\tawait sql`\n\t\tALTER TABLE _emdash_collections\n\t\tDROP COLUMN has_seo\n\t`.execute(db);\n}\n","import type { Kysely } from \"kysely\";\nimport { sql } from \"kysely\";\n\nimport { isSqlite, listTablesLike } from \"../dialect-helpers.js\";\nimport { validateIdentifier } from \"../validate.js\";\n\n/**\n * Migration: i18n support (row-per-locale model)\n *\n * Each piece of content can exist in multiple locales. Translations of the\n * same content share a `translation_group` ULID while each row carries its\n * own `locale` code. Slugs are unique per-locale, not globally.\n *\n * Changes:\n * 1. For every ec_* content table:\n * - Rebuild the table to replace inline `slug TEXT UNIQUE` with\n * `slug TEXT` + a compound `UNIQUE(slug, locale)` constraint.\n * - Add `locale TEXT NOT NULL DEFAULT 'en'`\n * - Add `translation_group TEXT`\n * - Backfill `translation_group = id` for existing rows\n * - Recreate all standard indexes plus new locale/translation_group indexes\n *\n * 2. Add `translatable` column to `_emdash_fields`\n *\n * The table-rebuild approach is required because SQLite cannot drop an inline\n * UNIQUE constraint via ALTER TABLE. We use PRAGMA table_info to discover all\n * columns (including dynamic user-defined fields) and rebuild dynamically.\n */\n\n// Column info returned by PRAGMA table_info\ninterface ColumnInfo {\n\tcid: number;\n\tname: string;\n\ttype: string;\n\tnotnull: number;\n\tdflt_value: string | null;\n\tpk: number;\n}\n\n// Index info returned by PRAGMA index_list\ninterface IndexInfo {\n\tseq: number;\n\tname: string;\n\tunique: number;\n\torigin: string;\n\tpartial: number;\n}\n\n// Index column info returned by PRAGMA index_info\ninterface IndexColumnInfo {\n\tseqno: number;\n\tcid: number;\n\tname: string;\n}\n\n/**\n * Quote an identifier for use in raw SQL. Escapes embedded double-quotes\n * per SQL standard (double them). The name should first pass\n * validateIdentifier() or validateTableName() for defense-in-depth.\n */\nconst DOUBLE_QUOTE_RE = /\"/g;\nfunction quoteIdent(name: string): string {\n\treturn `\"${name.replace(DOUBLE_QUOTE_RE, '\"\"')}\"`;\n}\n\n/** Suffix added to tmp tables during i18n migration rebuild. */\nconst I18N_TMP_SUFFIX = /_i18n_tmp$/;\n\n/** Table names from sqlite_master are ec_{slug} — validate the pattern. */\nconst TABLE_NAME_PATTERN = /^ec_[a-z][a-z0-9_]*$/;\nfunction validateTableName(name: string): void {\n\tif (!TABLE_NAME_PATTERN.test(name)) {\n\t\tthrow new Error(`Invalid content table name: \"${name}\"`);\n\t}\n}\n\n/** SQLite column types produced by EmDash's schema registry. */\nconst ALLOWED_COLUMN_TYPES = new Set([\"TEXT\", \"INTEGER\", \"REAL\", \"BLOB\", \"JSON\", \"NUMERIC\", \"\"]);\nfunction validateColumnType(type: string, colName: string): void {\n\tif (!ALLOWED_COLUMN_TYPES.has(type.toUpperCase())) {\n\t\tthrow new Error(`Unexpected column type \"${type}\" for column \"${colName}\"`);\n\t}\n}\n\n/**\n * Validate that a default value expression from PRAGMA table_info is safe\n * to interpolate into DDL. Allows: string literals, numeric literals,\n * NULL, and known function calls like datetime('now').\n *\n * Note: PRAGMA table_info strips the outer parens from expression defaults,\n * so `DEFAULT (datetime('now'))` is reported as `datetime('now')`.\n * We accept both forms and re-wrap in parens via normalizeDdlDefault().\n */\nconst SAFE_DEFAULT_PATTERN =\n\t/^(?:'[^']*'|NULL|-?\\d+(?:\\.\\d+)?|\\(?datetime\\('now'\\)\\)?|\\(?json\\('[^']*'\\)\\)?|0|1)$/i;\nfunction validateDefaultValue(value: string, colName: string): void {\n\tif (!SAFE_DEFAULT_PATTERN.test(value)) {\n\t\tthrow new Error(`Unexpected default value \"${value}\" for column \"${colName}\"`);\n\t}\n}\n\n/**\n * Normalize a PRAGMA table_info default value for use in DDL.\n * Function-call defaults (e.g. `datetime('now')`) must be wrapped in parens\n * to form valid expression defaults: `DEFAULT (datetime('now'))`.\n * PRAGMA strips the outer parens, so we re-add them here.\n */\nconst FUNCTION_DEFAULT_PATTERN = /^(?:datetime|json)\\(/i;\nfunction normalizeDdlDefault(value: string): string {\n\t// Already wrapped in parens — return as-is\n\tif (value.startsWith(\"(\")) return value;\n\tif (FUNCTION_DEFAULT_PATTERN.test(value)) return `(${value})`;\n\treturn value;\n}\n\n/**\n * Validate that a CREATE INDEX statement from sqlite_master is safe to replay.\n * Must start with CREATE [UNIQUE] INDEX and not contain suspicious patterns.\n */\nconst CREATE_INDEX_PATTERN = /^CREATE\\s+(UNIQUE\\s+)?INDEX\\s+/i;\nfunction validateCreateIndexSql(sqlStr: string, idxName: string): void {\n\tif (!CREATE_INDEX_PATTERN.test(sqlStr)) {\n\t\tthrow new Error(`Unexpected index SQL for \"${idxName}\": does not match CREATE INDEX pattern`);\n\t}\n\t// Reject semicolons which could allow statement injection\n\tif (sqlStr.includes(\";\")) {\n\t\tthrow new Error(`Unexpected index SQL for \"${idxName}\": contains semicolon`);\n\t}\n}\n\n/**\n * PostgreSQL path: ALTER TABLE supports ADD COLUMN and DROP CONSTRAINT directly.\n * No table rebuild needed.\n */\nasync function upPostgres(db: Kysely<unknown>): Promise<void> {\n\tconst tableNames = await listTablesLike(db, \"ec_%\");\n\n\tfor (const t of tableNames) {\n\t\tvalidateTableName(t);\n\n\t\t// Check if already migrated (idempotency)\n\t\tconst hasLocale = await sql<{ exists: boolean }>`\n\t\t\tSELECT EXISTS(\n\t\t\t\tSELECT 1 FROM information_schema.columns\n\t\t\t\tWHERE table_schema = 'public' AND table_name = ${t} AND column_name = 'locale'\n\t\t\t) as exists\n\t\t`.execute(db);\n\t\tif (hasLocale.rows[0]?.exists === true) continue;\n\n\t\t// Add i18n columns\n\t\tawait sql`ALTER TABLE ${sql.ref(t)} ADD COLUMN locale TEXT NOT NULL DEFAULT 'en'`.execute(db);\n\t\tawait sql`ALTER TABLE ${sql.ref(t)} ADD COLUMN translation_group TEXT`.execute(db);\n\n\t\t// Drop existing unique constraint on slug (Postgres auto-names these)\n\t\t// Find the constraint name first\n\t\tconst constraints = await sql<{ conname: string }>`\n\t\t\tSELECT conname FROM pg_constraint\n\t\t\tWHERE conrelid = ${t}::regclass\n\t\t\tAND contype = 'u'\n\t\t\tAND array_length(conkey, 1) = 1\n\t\t\tAND conkey[1] = (\n\t\t\t\tSELECT attnum FROM pg_attribute\n\t\t\t\tWHERE attrelid = ${t}::regclass AND attname = 'slug'\n\t\t\t)\n\t\t`.execute(db);\n\n\t\tfor (const c of constraints.rows) {\n\t\t\tawait sql`ALTER TABLE ${sql.ref(t)} DROP CONSTRAINT ${sql.ref(c.conname)}`.execute(db);\n\t\t}\n\n\t\t// Add compound unique constraint\n\t\tawait sql`\n\t\t\tALTER TABLE ${sql.ref(t)}\n\t\t\tADD CONSTRAINT ${sql.ref(`${t}_slug_locale_unique`)} UNIQUE (slug, locale)\n\t\t`.execute(db);\n\n\t\t// Backfill translation_group\n\t\tawait sql`UPDATE ${sql.ref(t)} SET translation_group = id`.execute(db);\n\n\t\t// Create indexes\n\t\tawait sql`CREATE INDEX ${sql.ref(`idx_${t}_locale`)} ON ${sql.ref(t)} (locale)`.execute(db);\n\t\tawait sql`\n\t\t\tCREATE INDEX ${sql.ref(`idx_${t}_translation_group`)}\n\t\t\tON ${sql.ref(t)} (translation_group)\n\t\t`.execute(db);\n\t}\n\n\t// Add translatable flag to fields table\n\tconst hasTranslatable = await sql<{ exists: boolean }>`\n\t\tSELECT EXISTS(\n\t\t\tSELECT 1 FROM information_schema.columns\n\t\t\tWHERE table_schema = 'public' AND table_name = '_emdash_fields' AND column_name = 'translatable'\n\t\t) as exists\n\t`.execute(db);\n\tif (hasTranslatable.rows[0]?.exists !== true) {\n\t\tawait sql`\n\t\t\tALTER TABLE _emdash_fields\n\t\t\tADD COLUMN translatable INTEGER NOT NULL DEFAULT 1\n\t\t`.execute(db);\n\t}\n}\n\nexport async function up(db: Kysely<unknown>): Promise<void> {\n\tif (!isSqlite(db)) {\n\t\treturn upPostgres(db);\n\t}\n\n\t// Clean up orphaned tmp tables from a previous partial run.\n\t// On D1 (no transactions), a crash mid-migration can leave these behind.\n\tconst orphanedTmps = await listTablesLike(db, \"ec_%_i18n_tmp\");\n\tfor (const tmpName of orphanedTmps) {\n\t\tvalidateTableName(tmpName.replace(I18N_TMP_SUFFIX, \"\"));\n\t\tawait sql`DROP TABLE IF EXISTS ${sql.ref(tmpName)}`.execute(db);\n\t}\n\n\t// Discover all ec_* content tables\n\tconst tableNames = await listTablesLike(db, \"ec_%\");\n\tconst tables = { rows: tableNames.map((name) => ({ name })) };\n\n\tfor (const table of tables.rows) {\n\t\tconst t = table.name;\n\t\tvalidateTableName(t);\n\t\tconst tmp = `${t}_i18n_tmp`;\n\n\t\t// Note: no transaction wrapper — D1 doesn't support transactions,\n\t\t// and SQLite/better-sqlite3 is single-writer so crash-safety is\n\t\t// handled by the journal mode. The tmp table approach is already\n\t\t// crash-recoverable (orphaned tmp tables are harmless).\n\t\t{\n\t\t\tconst trx = db;\n\t\t\t// ── 1. Read existing column definitions ──────────────────────\n\t\t\tconst colResult = await sql<ColumnInfo>`\n\t\t\t\tPRAGMA table_info(${sql.ref(t)})\n\t\t\t`.execute(trx);\n\t\t\tconst columns = colResult.rows;\n\n\t\t\t// ── Idempotency: skip tables already migrated ───────────────\n\t\t\t// On D1, the migrator can't use transactions, so a partially-\n\t\t\t// applied migration may not be recorded. If the table already\n\t\t\t// has a `locale` column, it was already rebuilt — skip it.\n\t\t\tif (columns.some((col) => col.name === \"locale\")) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// ── 2. Read existing indexes (to recreate after rebuild) ─────\n\t\t\tconst idxResult = await sql<IndexInfo>`\n\t\t\t\tPRAGMA index_list(${sql.ref(t)})\n\t\t\t`.execute(trx);\n\n\t\t\t// Collect non-autoindex, non-primary-key indexes for recreation\n\t\t\tconst indexDefs: { name: string; unique: boolean; columns: string[]; partial: number }[] = [];\n\t\t\tfor (const idx of idxResult.rows) {\n\t\t\t\t// Skip autoindexes (created by inline UNIQUE) — we're removing them\n\t\t\t\tif (idx.origin === \"pk\" || idx.name.startsWith(\"sqlite_autoindex_\")) continue;\n\n\t\t\t\tconst idxColResult = await sql<IndexColumnInfo>`\n\t\t\t\t\tPRAGMA index_info(${sql.ref(idx.name)})\n\t\t\t\t`.execute(trx);\n\n\t\t\t\tindexDefs.push({\n\t\t\t\t\tname: idx.name,\n\t\t\t\t\tunique: idx.unique === 1,\n\t\t\t\t\tcolumns: idxColResult.rows.map((c) => c.name),\n\t\t\t\t\tpartial: idx.partial,\n\t\t\t\t});\n\t\t\t}\n\n\t\t\t// For partial indexes we need the original CREATE statement\n\t\t\tconst partialSqls = new Map<string, string>();\n\t\t\tfor (const idx of indexDefs) {\n\t\t\t\tif (idx.partial) {\n\t\t\t\t\tconst createResult = await sql<{ sql: string }>`\n\t\t\t\t\t\tSELECT sql FROM sqlite_master \n\t\t\t\t\t\tWHERE type = 'index' AND name = ${idx.name}\n\t\t\t\t\t`.execute(trx);\n\t\t\t\t\tif (createResult.rows[0]?.sql) {\n\t\t\t\t\t\tpartialSqls.set(idx.name, createResult.rows[0].sql);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// ── 3. Build column defs for the new table ──────────────────\n\t\t\t// Validate all column names from PRAGMA before using them in raw SQL.\n\t\t\t// These originate from our own schema, but defense-in-depth matters.\n\t\t\tfor (const col of columns) {\n\t\t\t\tvalidateIdentifier(col.name, \"column name\");\n\t\t\t}\n\n\t\t\t// Replace slug's inline UNIQUE with a table-level UNIQUE(slug, locale)\n\t\t\tconst colDefs: string[] = [];\n\t\t\tconst colNames: string[] = [];\n\n\t\t\tfor (const col of columns) {\n\t\t\t\tvalidateColumnType(col.type || \"TEXT\", col.name);\n\t\t\t\tcolNames.push(quoteIdent(col.name));\n\t\t\t\tlet def = `${quoteIdent(col.name)} ${col.type || \"TEXT\"}`;\n\n\t\t\t\tif (col.pk) {\n\t\t\t\t\tdef += \" PRIMARY KEY\";\n\t\t\t\t} else if (col.name === \"slug\") {\n\t\t\t\t\t// Intentionally omit UNIQUE — compound unique below\n\t\t\t\t} else {\n\t\t\t\t\tif (col.notnull) def += \" NOT NULL\";\n\t\t\t\t}\n\n\t\t\t\tif (col.dflt_value !== null) {\n\t\t\t\t\tvalidateDefaultValue(col.dflt_value, col.name);\n\t\t\t\t\tdef += ` DEFAULT ${normalizeDdlDefault(col.dflt_value)}`;\n\t\t\t\t}\n\n\t\t\t\tcolDefs.push(def);\n\t\t\t}\n\n\t\t\t// Append new i18n columns\n\t\t\tcolDefs.push(\"\\\"locale\\\" TEXT NOT NULL DEFAULT 'en'\");\n\t\t\tcolDefs.push('\"translation_group\" TEXT');\n\n\t\t\t// Compound unique: same slug + locale must be unique\n\t\t\tcolDefs.push('UNIQUE(\"slug\", \"locale\")');\n\n\t\t\tconst createColsSql = colDefs.join(\",\\n\\t\\t\\t\\t\");\n\t\t\tconst selectColsSql = colNames.join(\", \");\n\n\t\t\t// ── 4. Rebuild the table ────────────────────────────────────\n\t\t\t// Drop all existing indexes first (they reference the old table)\n\t\t\tfor (const idx of indexDefs) {\n\t\t\t\tawait sql`DROP INDEX IF EXISTS ${sql.ref(idx.name)}`.execute(trx);\n\t\t\t}\n\n\t\t\t// Create new table with updated schema\n\t\t\tawait sql\n\t\t\t\t.raw(`CREATE TABLE ${quoteIdent(tmp)} (\\n\\t\\t\\t\\t${createColsSql}\\n\\t\\t\\t)`)\n\t\t\t\t.execute(trx);\n\n\t\t\t// Copy existing data, backfilling locale='en' and translation_group=id\n\t\t\tawait sql\n\t\t\t\t.raw(\n\t\t\t\t\t`INSERT INTO ${quoteIdent(tmp)} (${selectColsSql}, \"locale\", \"translation_group\")\\n\\t\\t\\t SELECT ${selectColsSql}, 'en', \"id\" FROM ${quoteIdent(t)}`,\n\t\t\t\t)\n\t\t\t\t.execute(trx);\n\n\t\t\t// Swap tables\n\t\t\tawait sql`DROP TABLE ${sql.ref(t)}`.execute(trx);\n\t\t\tawait sql.raw(`ALTER TABLE ${quoteIdent(tmp)} RENAME TO ${quoteIdent(t)}`).execute(trx);\n\n\t\t\t// ── 5. Recreate all original indexes ────────────────────────\n\t\t\tfor (const idx of indexDefs) {\n\t\t\t\t// Skip the old slug-only index — replaced by slug_locale below\n\t\t\t\tif (idx.name === `idx_${t}_slug`) continue;\n\n\t\t\t\tif (idx.partial && partialSqls.has(idx.name)) {\n\t\t\t\t\t// Partial indexes — validate the SQL before replaying\n\t\t\t\t\tconst idxSql = partialSqls.get(idx.name)!;\n\t\t\t\t\tvalidateCreateIndexSql(idxSql, idx.name);\n\t\t\t\t\tawait sql.raw(idxSql).execute(trx);\n\t\t\t\t} else {\n\t\t\t\t\t// Validate index column names before interpolation\n\t\t\t\t\tfor (const c of idx.columns) {\n\t\t\t\t\t\tvalidateIdentifier(c, \"index column name\");\n\t\t\t\t\t}\n\t\t\t\t\tconst cols = idx.columns.map((c) => quoteIdent(c)).join(\", \");\n\t\t\t\t\tconst unique = idx.unique ? \"UNIQUE \" : \"\";\n\t\t\t\t\tawait sql\n\t\t\t\t\t\t.raw(`CREATE ${unique}INDEX ${quoteIdent(idx.name)} ON ${quoteIdent(t)} (${cols})`)\n\t\t\t\t\t\t.execute(trx);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// ── 6. Create new i18n indexes ──────────────────────────────\n\t\t\t// slug_locale unique is handled by the table constraint above,\n\t\t\t// but we still want a regular slug index for non-locale-aware queries\n\t\t\tawait sql`\n\t\t\t\tCREATE INDEX ${sql.ref(`idx_${t}_slug`)} \n\t\t\t\tON ${sql.ref(t)} (slug)\n\t\t\t`.execute(trx);\n\n\t\t\tawait sql`\n\t\t\t\tCREATE INDEX ${sql.ref(`idx_${t}_locale`)} \n\t\t\t\tON ${sql.ref(t)} (locale)\n\t\t\t`.execute(trx);\n\n\t\t\tawait sql`\n\t\t\t\tCREATE INDEX ${sql.ref(`idx_${t}_translation_group`)} \n\t\t\t\tON ${sql.ref(t)} (translation_group)\n\t\t\t`.execute(trx);\n\t\t}\n\t}\n\n\t// ── 7. Add translatable flag to fields table ────────────────────\n\t// Guard against duplicate column — on D1 the migration may have\n\t// partially applied without being recorded (no transaction support).\n\tconst fieldCols = await sql<ColumnInfo>`\n\t\tPRAGMA table_info(_emdash_fields)\n\t`.execute(db);\n\tif (!fieldCols.rows.some((col) => col.name === \"translatable\")) {\n\t\tawait sql`\n\t\t\tALTER TABLE _emdash_fields \n\t\t\tADD COLUMN translatable INTEGER NOT NULL DEFAULT 1\n\t\t`.execute(db);\n\t}\n}\n\n/**\n * PostgreSQL down path: straightforward ALTER TABLE operations.\n */\nasync function downPostgres(db: Kysely<unknown>): Promise<void> {\n\tawait sql`ALTER TABLE _emdash_fields DROP COLUMN translatable`.execute(db);\n\n\tconst tableNames = await listTablesLike(db, \"ec_%\");\n\tfor (const t of tableNames) {\n\t\tvalidateTableName(t);\n\n\t\t// Drop i18n indexes\n\t\tawait sql`DROP INDEX IF EXISTS ${sql.ref(`idx_${t}_locale`)}`.execute(db);\n\t\tawait sql`DROP INDEX IF EXISTS ${sql.ref(`idx_${t}_translation_group`)}`.execute(db);\n\n\t\t// Drop compound unique constraint\n\t\tawait sql`ALTER TABLE ${sql.ref(t)} DROP CONSTRAINT IF EXISTS ${sql.ref(`${t}_slug_locale_unique`)}`.execute(\n\t\t\tdb,\n\t\t);\n\n\t\t// Restore simple unique constraint on slug\n\t\tawait sql`ALTER TABLE ${sql.ref(t)} ADD CONSTRAINT ${sql.ref(`${t}_slug_unique`)} UNIQUE (slug)`.execute(\n\t\t\tdb,\n\t\t);\n\n\t\t// Drop i18n columns\n\t\tawait sql`ALTER TABLE ${sql.ref(t)} DROP COLUMN locale`.execute(db);\n\t\tawait sql`ALTER TABLE ${sql.ref(t)} DROP COLUMN translation_group`.execute(db);\n\t}\n}\n\nexport async function down(db: Kysely<unknown>): Promise<void> {\n\tif (!isSqlite(db)) {\n\t\treturn downPostgres(db);\n\t}\n\n\t// Remove translatable column from fields table\n\tawait sql`\n\t\tALTER TABLE _emdash_fields\n\t\tDROP COLUMN translatable\n\t`.execute(db);\n\n\t// Discover all ec_* content tables\n\tconst tableNames = await listTablesLike(db, \"ec_%\");\n\n\tfor (const tableName of tableNames) {\n\t\tconst t = tableName;\n\t\tvalidateTableName(t);\n\t\tconst tmp = `${t}_i18n_tmp`;\n\n\t\t// No transaction — see comment in up() above.\n\t\t{\n\t\t\tconst trx = db;\n\t\t\t// ── 1. Read current column definitions ──────────────────────\n\t\t\tconst colResult = await sql<ColumnInfo>`\n\t\t\t\tPRAGMA table_info(${sql.ref(t)})\n\t\t\t`.execute(trx);\n\t\t\tconst columns = colResult.rows;\n\n\t\t\t// ── 2. Read current indexes ─────────────────────────────────\n\t\t\tconst idxResult = await sql<IndexInfo>`\n\t\t\t\tPRAGMA index_list(${sql.ref(t)})\n\t\t\t`.execute(trx);\n\n\t\t\tconst indexDefs: { name: string; unique: boolean; columns: string[]; partial: number }[] = [];\n\t\t\tfor (const idx of idxResult.rows) {\n\t\t\t\tif (idx.origin === \"pk\" || idx.name.startsWith(\"sqlite_autoindex_\")) continue;\n\n\t\t\t\tconst idxColResult = await sql<IndexColumnInfo>`\n\t\t\t\t\tPRAGMA index_info(${sql.ref(idx.name)})\n\t\t\t\t`.execute(trx);\n\n\t\t\t\tindexDefs.push({\n\t\t\t\t\tname: idx.name,\n\t\t\t\t\tunique: idx.unique === 1,\n\t\t\t\t\tcolumns: idxColResult.rows.map((c) => c.name),\n\t\t\t\t\tpartial: idx.partial,\n\t\t\t\t});\n\t\t\t}\n\n\t\t\t// Save partial index SQL\n\t\t\tconst partialSqls = new Map<string, string>();\n\t\t\tfor (const idx of indexDefs) {\n\t\t\t\tif (idx.partial) {\n\t\t\t\t\tconst createResult = await sql<{ sql: string }>`\n\t\t\t\t\t\tSELECT sql FROM sqlite_master \n\t\t\t\t\t\tWHERE type = 'index' AND name = ${idx.name}\n\t\t\t\t\t`.execute(trx);\n\t\t\t\t\tif (createResult.rows[0]?.sql) {\n\t\t\t\t\t\tpartialSqls.set(idx.name, createResult.rows[0].sql);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// ── 3. Build column defs WITHOUT locale/translation_group ───\n\t\t\t// Validate all column names\n\t\t\tfor (const col of columns) {\n\t\t\t\tif (col.name === \"locale\" || col.name === \"translation_group\") continue;\n\t\t\t\tvalidateIdentifier(col.name, \"column name\");\n\t\t\t}\n\n\t\t\t// Restore slug's inline UNIQUE\n\t\t\tconst colDefs: string[] = [];\n\t\t\tconst colNames: string[] = [];\n\n\t\t\tfor (const col of columns) {\n\t\t\t\t// Skip i18n columns\n\t\t\t\tif (col.name === \"locale\" || col.name === \"translation_group\") continue;\n\n\t\t\t\tvalidateColumnType(col.type || \"TEXT\", col.name);\n\t\t\t\tcolNames.push(quoteIdent(col.name));\n\t\t\t\tlet def = `${quoteIdent(col.name)} ${col.type || \"TEXT\"}`;\n\n\t\t\t\tif (col.pk) {\n\t\t\t\t\tdef += \" PRIMARY KEY\";\n\t\t\t\t} else if (col.name === \"slug\") {\n\t\t\t\t\t// Restore inline UNIQUE\n\t\t\t\t\tdef += \" UNIQUE\";\n\t\t\t\t} else {\n\t\t\t\t\tif (col.notnull) def += \" NOT NULL\";\n\t\t\t\t}\n\n\t\t\t\tif (col.dflt_value !== null) {\n\t\t\t\t\tvalidateDefaultValue(col.dflt_value, col.name);\n\t\t\t\t\tdef += ` DEFAULT ${normalizeDdlDefault(col.dflt_value)}`;\n\t\t\t\t}\n\n\t\t\t\tcolDefs.push(def);\n\t\t\t}\n\n\t\t\tconst createColsSql = colDefs.join(\",\\n\\t\\t\\t\\t\");\n\t\t\tconst selectColsSql = colNames.join(\", \");\n\n\t\t\t// ── 4. Rebuild the table ────────────────────────────────────\n\t\t\t// Drop all existing indexes first\n\t\t\tfor (const idx of indexDefs) {\n\t\t\t\tawait sql`DROP INDEX IF EXISTS ${sql.ref(idx.name)}`.execute(trx);\n\t\t\t}\n\n\t\t\t// Create table with original schema (slug UNIQUE, no i18n columns)\n\t\t\tawait sql\n\t\t\t\t.raw(`CREATE TABLE ${quoteIdent(tmp)} (\\n\\t\\t\\t\\t${createColsSql}\\n\\t\\t\\t)`)\n\t\t\t\t.execute(trx);\n\n\t\t\t// Copy data — keep only one row per content item.\n\t\t\t// Prefer locale='en' rows. For items without an 'en' row, pick the\n\t\t\t// row with the smallest locale code (deterministic, unlike bare GROUP BY).\n\t\t\t// Handle NULL translation_group by treating each such row as its own group.\n\t\t\t// INSERT OR IGNORE skips any duplicate slugs from the fallback pass.\n\t\t\tawait sql\n\t\t\t\t.raw(\n\t\t\t\t\t`INSERT OR IGNORE INTO ${quoteIdent(tmp)} (${selectColsSql})\n\t\t\t SELECT ${selectColsSql} FROM ${quoteIdent(t)}\n\t\t\t WHERE \"locale\" = 'en'`,\n\t\t\t\t)\n\t\t\t\t.execute(trx);\n\n\t\t\tawait sql\n\t\t\t\t.raw(\n\t\t\t\t\t`INSERT OR IGNORE INTO ${quoteIdent(tmp)} (${selectColsSql})\n\t\t\t SELECT ${selectColsSql} FROM ${quoteIdent(t)}\n\t\t\t WHERE \"id\" NOT IN (SELECT \"id\" FROM ${quoteIdent(tmp)})\n\t\t\t AND \"id\" IN (\n\t\t\t\tSELECT \"id\" FROM ${quoteIdent(t)} AS t2\n\t\t\t\tWHERE t2.\"translation_group\" IS NOT NULL\n\t\t\t\tAND t2.\"locale\" = (\n\t\t\t\t\tSELECT MIN(t3.\"locale\") FROM ${quoteIdent(t)} AS t3\n\t\t\t\t\tWHERE t3.\"translation_group\" = t2.\"translation_group\"\n\t\t\t\t)\n\t\t\t )`,\n\t\t\t\t)\n\t\t\t\t.execute(trx);\n\n\t\t\t// Pick up any rows with NULL translation_group that weren't already copied\n\t\t\tawait sql\n\t\t\t\t.raw(\n\t\t\t\t\t`INSERT OR IGNORE INTO ${quoteIdent(tmp)} (${selectColsSql})\n\t\t\t SELECT ${selectColsSql} FROM ${quoteIdent(t)}\n\t\t\t WHERE \"id\" NOT IN (SELECT \"id\" FROM ${quoteIdent(tmp)})\n\t\t\t AND \"translation_group\" IS NULL`,\n\t\t\t\t)\n\t\t\t\t.execute(trx);\n\n\t\t\t// Swap tables\n\t\t\tawait sql`DROP TABLE ${sql.ref(t)}`.execute(trx);\n\t\t\tawait sql.raw(`ALTER TABLE ${quoteIdent(tmp)} RENAME TO ${quoteIdent(t)}`).execute(trx);\n\n\t\t\t// ── 5. Recreate indexes ─────────────────────────────────────\n\t\t\tfor (const idx of indexDefs) {\n\t\t\t\t// Skip i18n-specific indexes — they don't exist in the old schema\n\t\t\t\tif (idx.name === `idx_${t}_locale`) continue;\n\t\t\t\tif (idx.name === `idx_${t}_translation_group`) continue;\n\n\t\t\t\tif (idx.partial && partialSqls.has(idx.name)) {\n\t\t\t\t\t// Partial indexes — validate the SQL before replaying\n\t\t\t\t\tconst idxSql = partialSqls.get(idx.name)!;\n\t\t\t\t\tvalidateCreateIndexSql(idxSql, idx.name);\n\t\t\t\t\tawait sql.raw(idxSql).execute(trx);\n\t\t\t\t} else {\n\t\t\t\t\t// Filter out i18n columns from any index that might reference them\n\t\t\t\t\tconst cols = idx.columns.filter((c) => c !== \"locale\" && c !== \"translation_group\");\n\t\t\t\t\tif (cols.length === 0) continue;\n\n\t\t\t\t\t// Validate column names\n\t\t\t\t\tfor (const c of cols) {\n\t\t\t\t\t\tvalidateIdentifier(c, \"index column name\");\n\t\t\t\t\t}\n\t\t\t\t\tconst colsSql = cols.map((c) => quoteIdent(c)).join(\", \");\n\t\t\t\t\tconst unique = idx.unique ? \"UNIQUE \" : \"\";\n\t\t\t\t\tawait sql\n\t\t\t\t\t\t.raw(`CREATE ${unique}INDEX ${quoteIdent(idx.name)} ON ${quoteIdent(t)} (${colsSql})`)\n\t\t\t\t\t\t.execute(trx);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n","import type { Kysely } from \"kysely\";\nimport { sql } from \"kysely\";\n\n/**\n * Migration: URL pattern for collections\n *\n * Adds `url_pattern` column to `_emdash_collections` so each collection\n * can declare its own URL structure (e.g. \"/{slug}\" for pages, \"/blog/{slug}\"\n * for posts). Used for menu URL resolution, sitemaps, and path-based lookups.\n */\nexport async function up(db: Kysely<unknown>): Promise<void> {\n\tawait sql`\n\t\tALTER TABLE _emdash_collections\n\t\tADD COLUMN url_pattern TEXT\n\t`.execute(db);\n}\n\nexport async function down(db: Kysely<unknown>): Promise<void> {\n\tawait sql`\n\t\tALTER TABLE _emdash_collections\n\t\tDROP COLUMN url_pattern\n\t`.execute(db);\n}\n","import { type Kysely, sql } from \"kysely\";\n\n/**\n * Migration: Remove section categories\n *\n * Section categories had a complete backend but no UI to create or manage them.\n * Rather than building the missing UI for a feature with very little need at this stage,\n * we're removing the feature entirely.\n */\nexport async function up(db: Kysely<unknown>): Promise<void> {\n\t// Drop index before column — SQLite requires this order\n\tawait db.schema.dropIndex(\"idx_sections_category\").ifExists().execute();\n\n\tawait db.schema.alterTable(\"_emdash_sections\").dropColumn(\"category_id\").execute();\n\n\tawait db.schema.dropTable(\"_emdash_section_categories\").execute();\n}\n\nexport async function down(db: Kysely<unknown>): Promise<void> {\n\t// Recreate section categories table\n\tawait db.schema\n\t\t.createTable(\"_emdash_section_categories\")\n\t\t.addColumn(\"id\", \"text\", (col) => col.primaryKey())\n\t\t.addColumn(\"slug\", \"text\", (col) => col.notNull().unique())\n\t\t.addColumn(\"label\", \"text\", (col) => col.notNull())\n\t\t.addColumn(\"sort_order\", \"integer\", (col) => col.defaultTo(0))\n\t\t.addColumn(\"created_at\", \"text\", (col) => col.defaultTo(sql`CURRENT_TIMESTAMP`))\n\t\t.execute();\n\n\t// Re-add category_id column to sections\n\tawait db.schema\n\t\t.alterTable(\"_emdash_sections\")\n\t\t.addColumn(\"category_id\", \"text\", (col) =>\n\t\t\tcol.references(\"_emdash_section_categories.id\").onDelete(\"set null\"),\n\t\t)\n\t\t.execute();\n\n\tawait db.schema\n\t\t.createIndex(\"idx_sections_category\")\n\t\t.on(\"_emdash_sections\")\n\t\t.columns([\"category_id\"])\n\t\t.execute();\n}\n","import type { Kysely } from \"kysely\";\nimport { sql } from \"kysely\";\n\n/**\n * Migration: Add marketplace fields to _plugin_state\n *\n * Adds `source` and `marketplace_version` columns to track\n * whether a plugin was installed from config or marketplace,\n * and which marketplace version is installed.\n */\nexport async function up(db: Kysely<unknown>): Promise<void> {\n\t// source: 'config' (declared in astro.config) or 'marketplace' (installed from registry)\n\tawait sql`\n\t\tALTER TABLE _plugin_state\n\t\tADD COLUMN source TEXT NOT NULL DEFAULT 'config'\n\t`.execute(db);\n\n\t// marketplace_version: tracks installed version for update checking\n\tawait sql`\n\t\tALTER TABLE _plugin_state\n\t\tADD COLUMN marketplace_version TEXT\n\t`.execute(db);\n\n\t// Index for efficient marketplace plugin queries on cold start\n\tawait sql`\n\t\tCREATE INDEX idx_plugin_state_source\n\t\tON _plugin_state (source)\n\t\tWHERE source = 'marketplace'\n\t`.execute(db);\n}\n\nexport async function down(db: Kysely<unknown>): Promise<void> {\n\tawait sql`\n\t\tDROP INDEX IF EXISTS idx_plugin_state_source\n\t`.execute(db);\n\n\tawait sql`\n\t\tALTER TABLE _plugin_state\n\t\tDROP COLUMN marketplace_version\n\t`.execute(db);\n\n\tawait sql`\n\t\tALTER TABLE _plugin_state\n\t\tDROP COLUMN source\n\t`.execute(db);\n}\n","import type { Kysely } from \"kysely\";\nimport { sql } from \"kysely\";\n\n/**\n * Migration: Add display metadata to _plugin_state\n *\n * Stores display_name and description for marketplace plugins\n * so the admin UI can show meaningful info without re-fetching\n * from the marketplace on every page load.\n */\nexport async function up(db: Kysely<unknown>): Promise<void> {\n\tawait sql`\n\t\tALTER TABLE _plugin_state\n\t\tADD COLUMN display_name TEXT\n\t`.execute(db);\n\n\tawait sql`\n\t\tALTER TABLE _plugin_state\n\t\tADD COLUMN description TEXT\n\t`.execute(db);\n}\n\nexport async function down(db: Kysely<unknown>): Promise<void> {\n\tawait sql`\n\t\tALTER TABLE _plugin_state\n\t\tDROP COLUMN description\n\t`.execute(db);\n\n\tawait sql`\n\t\tALTER TABLE _plugin_state\n\t\tDROP COLUMN display_name\n\t`.execute(db);\n}\n","import type { Kysely } from \"kysely\";\nimport { sql } from \"kysely\";\n\n/**\n * Migration: Add placeholder columns to media table\n *\n * Stores blurhash and dominant_color for LQIP (Low Quality Image Placeholder)\n * support. Generated at upload time from image pixel data.\n */\nexport async function up(db: Kysely<unknown>): Promise<void> {\n\tawait sql`\n\t\tALTER TABLE media\n\t\tADD COLUMN blurhash TEXT\n\t`.execute(db);\n\n\tawait sql`\n\t\tALTER TABLE media\n\t\tADD COLUMN dominant_color TEXT\n\t`.execute(db);\n}\n\nexport async function down(db: Kysely<unknown>): Promise<void> {\n\tawait sql`\n\t\tALTER TABLE media\n\t\tDROP COLUMN dominant_color\n\t`.execute(db);\n\n\tawait sql`\n\t\tALTER TABLE media\n\t\tDROP COLUMN blurhash\n\t`.execute(db);\n}\n","import type { Kysely } from \"kysely\";\n\nimport { currentTimestamp } from \"../dialect-helpers.js\";\n\n/**\n * Migration: Create OAuth clients table\n *\n * Implements the oauth_clients registry so that the authorization endpoint\n * can validate client_id and enforce a per-client redirect URI allowlist.\n *\n * Each client has a set of pre-registered redirect URIs (JSON array).\n * The authorize endpoint rejects any redirect_uri not in the client's list.\n */\nexport async function up(db: Kysely<unknown>): Promise<void> {\n\tawait db.schema\n\t\t.createTable(\"_emdash_oauth_clients\")\n\t\t.addColumn(\"id\", \"text\", (col) => col.primaryKey()) // Client ID (e.g. URL or opaque string)\n\t\t.addColumn(\"name\", \"text\", (col) => col.notNull()) // Human-readable name\n\t\t.addColumn(\"redirect_uris\", \"text\", (col) => col.notNull()) // JSON array of allowed redirect URIs\n\t\t.addColumn(\"scopes\", \"text\") // JSON array of allowed scopes (null = all)\n\t\t.addColumn(\"created_at\", \"text\", (col) => col.defaultTo(currentTimestamp(db)))\n\t\t.addColumn(\"updated_at\", \"text\", (col) => col.defaultTo(currentTimestamp(db)))\n\t\t.execute();\n}\n\nexport async function down(db: Kysely<unknown>): Promise<void> {\n\tawait db.schema.dropTable(\"_emdash_oauth_clients\").execute();\n}\n","import type { Kysely } from \"kysely\";\n\nimport { currentTimestamp } from \"../dialect-helpers.js\";\n\n/**\n * Migration: Create cron tasks table for plugin scheduled tasks.\n *\n * Each plugin can register cron tasks (recurring or one-shot) which are\n * stored here and executed by the platform-specific scheduler.\n *\n * The `next_run_at` + `status` + `enabled` index drives the \"find overdue\n * tasks\" query used by CronExecutor.tick().\n */\nexport async function up(db: Kysely<unknown>): Promise<void> {\n\tawait db.schema\n\t\t.createTable(\"_emdash_cron_tasks\")\n\t\t.addColumn(\"id\", \"text\", (col) => col.primaryKey())\n\t\t.addColumn(\"plugin_id\", \"text\", (col) => col.notNull())\n\t\t.addColumn(\"task_name\", \"text\", (col) => col.notNull())\n\t\t.addColumn(\"schedule\", \"text\", (col) => col.notNull())\n\t\t.addColumn(\"is_oneshot\", \"integer\", (col) => col.notNull().defaultTo(0))\n\t\t.addColumn(\"data\", \"text\") // JSON\n\t\t.addColumn(\"next_run_at\", \"text\", (col) => col.notNull())\n\t\t.addColumn(\"last_run_at\", \"text\")\n\t\t.addColumn(\"status\", \"text\", (col) => col.notNull().defaultTo(\"idle\"))\n\t\t.addColumn(\"locked_at\", \"text\")\n\t\t.addColumn(\"enabled\", \"integer\", (col) => col.notNull().defaultTo(1))\n\t\t.addColumn(\"created_at\", \"text\", (col) => col.defaultTo(currentTimestamp(db)))\n\t\t.addUniqueConstraint(\"uq_cron_tasks_plugin_task\", [\"plugin_id\", \"task_name\"])\n\t\t.execute();\n\n\t// Equality columns first (enabled, status), then range column (next_run_at)\n\t// for optimal B-tree index usage in the tick query.\n\tawait db.schema\n\t\t.createIndex(\"idx_cron_tasks_due\")\n\t\t.on(\"_emdash_cron_tasks\")\n\t\t.columns([\"enabled\", \"status\", \"next_run_at\"])\n\t\t.execute();\n\n\tawait db.schema\n\t\t.createIndex(\"idx_cron_tasks_plugin\")\n\t\t.on(\"_emdash_cron_tasks\")\n\t\t.column(\"plugin_id\")\n\t\t.execute();\n}\n\nexport async function down(db: Kysely<unknown>): Promise<void> {\n\tawait db.schema.dropTable(\"_emdash_cron_tasks\").execute();\n}\n","import type { Kysely } from \"kysely\";\n\nimport { currentTimestamp } from \"../dialect-helpers.js\";\n\nexport async function up(db: Kysely<unknown>): Promise<void> {\n\t// Create the comments table\n\tawait db.schema\n\t\t.createTable(\"_emdash_comments\")\n\t\t.addColumn(\"id\", \"text\", (col) => col.primaryKey())\n\t\t.addColumn(\"collection\", \"text\", (col) => col.notNull())\n\t\t.addColumn(\"content_id\", \"text\", (col) => col.notNull())\n\t\t.addColumn(\"parent_id\", \"text\", (col) =>\n\t\t\tcol.references(\"_emdash_comments.id\").onDelete(\"cascade\"),\n\t\t)\n\t\t.addColumn(\"author_name\", \"text\", (col) => col.notNull())\n\t\t.addColumn(\"author_email\", \"text\", (col) => col.notNull())\n\t\t.addColumn(\"author_url\", \"text\")\n\t\t.addColumn(\"author_user_id\", \"text\", (col) => col.references(\"users.id\").onDelete(\"set null\"))\n\t\t.addColumn(\"body\", \"text\", (col) => col.notNull())\n\t\t.addColumn(\"status\", \"text\", (col) => col.notNull().defaultTo(\"pending\"))\n\t\t.addColumn(\"ip_hash\", \"text\")\n\t\t.addColumn(\"user_agent\", \"text\")\n\t\t.addColumn(\"moderation_metadata\", \"text\") // JSON\n\t\t.addColumn(\"created_at\", \"text\", (col) => col.defaultTo(currentTimestamp(db)))\n\t\t.addColumn(\"updated_at\", \"text\", (col) => col.defaultTo(currentTimestamp(db)))\n\t\t.execute();\n\n\t// Indexes\n\tawait db.schema\n\t\t.createIndex(\"idx_comments_content\")\n\t\t.on(\"_emdash_comments\")\n\t\t.columns([\"collection\", \"content_id\", \"status\"])\n\t\t.execute();\n\n\tawait db.schema\n\t\t.createIndex(\"idx_comments_parent\")\n\t\t.on(\"_emdash_comments\")\n\t\t.column(\"parent_id\")\n\t\t.execute();\n\n\tawait db.schema\n\t\t.createIndex(\"idx_comments_status\")\n\t\t.on(\"_emdash_comments\")\n\t\t.columns([\"status\", \"created_at\"])\n\t\t.execute();\n\n\tawait db.schema\n\t\t.createIndex(\"idx_comments_author_email\")\n\t\t.on(\"_emdash_comments\")\n\t\t.column(\"author_email\")\n\t\t.execute();\n\n\tawait db.schema\n\t\t.createIndex(\"idx_comments_author_user\")\n\t\t.on(\"_emdash_comments\")\n\t\t.column(\"author_user_id\")\n\t\t.execute();\n\n\t// Add collection-level comment settings columns\n\tawait db.schema\n\t\t.alterTable(\"_emdash_collections\")\n\t\t.addColumn(\"comments_enabled\", \"integer\", (col) => col.defaultTo(0))\n\t\t.execute();\n\n\tawait db.schema\n\t\t.alterTable(\"_emdash_collections\")\n\t\t.addColumn(\"comments_moderation\", \"text\", (col) => col.defaultTo(\"first_time\"))\n\t\t.execute();\n\n\tawait db.schema\n\t\t.alterTable(\"_emdash_collections\")\n\t\t.addColumn(\"comments_closed_after_days\", \"integer\", (col) => col.defaultTo(90))\n\t\t.execute();\n\n\tawait db.schema\n\t\t.alterTable(\"_emdash_collections\")\n\t\t.addColumn(\"comments_auto_approve_users\", \"integer\", (col) => col.defaultTo(1))\n\t\t.execute();\n}\n\nexport async function down(db: Kysely<unknown>): Promise<void> {\n\tawait db.schema.dropTable(\"_emdash_comments\").execute();\n\n\t// Note: SQLite doesn't support DROP COLUMN before 3.35.0.\n\t// For down migrations on the collection settings columns, the table\n\t// would need to be rebuilt. Skipping for simplicity in v0.\n}\n","import { sql, type Kysely } from \"kysely\";\n\nexport async function up(db: Kysely<unknown>): Promise<void> {\n\tawait sql`ALTER TABLE _emdash_comments DROP COLUMN author_url`.execute(db);\n}\n\nexport async function down(db: Kysely<unknown>): Promise<void> {\n\tawait db.schema.alterTable(\"_emdash_comments\").addColumn(\"author_url\", \"text\").execute();\n}\n","import type { Kysely } from \"kysely\";\n\nimport { currentTimestamp } from \"../dialect-helpers.js\";\n\nexport async function up(db: Kysely<unknown>): Promise<void> {\n\t// Redirect rules table\n\tawait db.schema\n\t\t.createTable(\"_emdash_redirects\")\n\t\t.addColumn(\"id\", \"text\", (col) => col.primaryKey())\n\t\t.addColumn(\"source\", \"text\", (col) => col.notNull())\n\t\t.addColumn(\"destination\", \"text\", (col) => col.notNull())\n\t\t.addColumn(\"type\", \"integer\", (col) => col.notNull().defaultTo(301))\n\t\t.addColumn(\"is_pattern\", \"integer\", (col) => col.notNull().defaultTo(0))\n\t\t.addColumn(\"enabled\", \"integer\", (col) => col.notNull().defaultTo(1))\n\t\t.addColumn(\"hits\", \"integer\", (col) => col.notNull().defaultTo(0))\n\t\t.addColumn(\"last_hit_at\", \"text\")\n\t\t.addColumn(\"group_name\", \"text\")\n\t\t.addColumn(\"auto\", \"integer\", (col) => col.notNull().defaultTo(0))\n\t\t.addColumn(\"created_at\", \"text\", (col) => col.defaultTo(currentTimestamp(db)))\n\t\t.addColumn(\"updated_at\", \"text\", (col) => col.defaultTo(currentTimestamp(db)))\n\t\t.execute();\n\n\t// Unique source for exact (non-pattern) rules\n\t// SQLite doesn't support partial indexes with WHERE on all versions,\n\t// so we use a regular index and enforce uniqueness in the application layer\n\tawait db.schema\n\t\t.createIndex(\"idx_redirects_source\")\n\t\t.on(\"_emdash_redirects\")\n\t\t.column(\"source\")\n\t\t.execute();\n\n\tawait db.schema\n\t\t.createIndex(\"idx_redirects_enabled\")\n\t\t.on(\"_emdash_redirects\")\n\t\t.column(\"enabled\")\n\t\t.execute();\n\n\tawait db.schema\n\t\t.createIndex(\"idx_redirects_group\")\n\t\t.on(\"_emdash_redirects\")\n\t\t.column(\"group_name\")\n\t\t.execute();\n\n\t// 404 log table\n\tawait db.schema\n\t\t.createTable(\"_emdash_404_log\")\n\t\t.addColumn(\"id\", \"text\", (col) => col.primaryKey())\n\t\t.addColumn(\"path\", \"text\", (col) => col.notNull())\n\t\t.addColumn(\"referrer\", \"text\")\n\t\t.addColumn(\"user_agent\", \"text\")\n\t\t.addColumn(\"ip\", \"text\")\n\t\t.addColumn(\"created_at\", \"text\", (col) => col.defaultTo(currentTimestamp(db)))\n\t\t.execute();\n\n\tawait db.schema.createIndex(\"idx_404_log_path\").on(\"_emdash_404_log\").column(\"path\").execute();\n\n\tawait db.schema\n\t\t.createIndex(\"idx_404_log_created\")\n\t\t.on(\"_emdash_404_log\")\n\t\t.column(\"created_at\")\n\t\t.execute();\n}\n\nexport async function down(db: Kysely<unknown>): Promise<void> {\n\tawait db.schema.dropTable(\"_emdash_404_log\").execute();\n\tawait db.schema.dropTable(\"_emdash_redirects\").execute();\n}\n","import type { Kysely } from \"kysely\";\nimport { sql } from \"kysely\";\n\nimport { listTablesLike } from \"../dialect-helpers.js\";\n\n/**\n * Migration: Widen scheduled publishing index\n *\n * The original partial index (013) only covered status='scheduled'.\n * Published posts can now have scheduled draft changes, so widen the\n * index to cover all rows where scheduled_at IS NOT NULL.\n */\nexport async function up(db: Kysely<unknown>): Promise<void> {\n\tconst tableNames = await listTablesLike(db, \"ec_%\");\n\n\tfor (const tableName of tableNames) {\n\t\tconst table = { name: tableName };\n\n\t\tawait sql`\n\t\t\tDROP INDEX IF EXISTS ${sql.ref(`idx_${table.name}_scheduled`)}\n\t\t`.execute(db);\n\n\t\tawait sql`\n\t\t\tCREATE INDEX ${sql.ref(`idx_${table.name}_scheduled`)}\n\t\t\tON ${sql.ref(table.name)} (scheduled_at)\n\t\t\tWHERE scheduled_at IS NOT NULL\n\t\t`.execute(db);\n\t}\n}\n\nexport async function down(db: Kysely<unknown>): Promise<void> {\n\tconst tableNames = await listTablesLike(db, \"ec_%\");\n\n\tfor (const tableName of tableNames) {\n\t\tconst table = { name: tableName };\n\n\t\tawait sql`\n\t\t\tDROP INDEX IF EXISTS ${sql.ref(`idx_${table.name}_scheduled`)}\n\t\t`.execute(db);\n\n\t\t// Restore the original narrower index\n\t\tawait sql`\n\t\t\tCREATE INDEX ${sql.ref(`idx_${table.name}_scheduled`)}\n\t\t\tON ${sql.ref(table.name)} (scheduled_at)\n\t\t\tWHERE scheduled_at IS NOT NULL AND status = 'scheduled'\n\t\t`.execute(db);\n\t}\n}\n","import type { Kysely } from \"kysely\";\nimport { sql } from \"kysely\";\n\nimport { currentTimestamp, listTablesLike } from \"../dialect-helpers.js\";\n\nexport async function up(db: Kysely<unknown>): Promise<void> {\n\tawait db.schema\n\t\t.createTable(\"_emdash_bylines\")\n\t\t.addColumn(\"id\", \"text\", (col) => col.primaryKey())\n\t\t.addColumn(\"slug\", \"text\", (col) => col.notNull().unique())\n\t\t.addColumn(\"display_name\", \"text\", (col) => col.notNull())\n\t\t.addColumn(\"bio\", \"text\")\n\t\t.addColumn(\"avatar_media_id\", \"text\", (col) => col.references(\"media.id\").onDelete(\"set null\"))\n\t\t.addColumn(\"website_url\", \"text\")\n\t\t.addColumn(\"user_id\", \"text\", (col) => col.references(\"users.id\").onDelete(\"set null\"))\n\t\t.addColumn(\"is_guest\", \"integer\", (col) => col.notNull().defaultTo(0))\n\t\t.addColumn(\"created_at\", \"text\", (col) => col.defaultTo(currentTimestamp(db)))\n\t\t.addColumn(\"updated_at\", \"text\", (col) => col.defaultTo(currentTimestamp(db)))\n\t\t.execute();\n\n\tawait sql`\n\t\tCREATE UNIQUE INDEX ${sql.ref(\"idx_bylines_user_id_unique\")}\n\t\tON ${sql.ref(\"_emdash_bylines\")} (user_id)\n\t\tWHERE user_id IS NOT NULL\n\t`.execute(db);\n\n\tawait db.schema.createIndex(\"idx_bylines_slug\").on(\"_emdash_bylines\").column(\"slug\").execute();\n\n\tawait db.schema\n\t\t.createIndex(\"idx_bylines_display_name\")\n\t\t.on(\"_emdash_bylines\")\n\t\t.column(\"display_name\")\n\t\t.execute();\n\n\tawait db.schema\n\t\t.createTable(\"_emdash_content_bylines\")\n\t\t.addColumn(\"id\", \"text\", (col) => col.primaryKey())\n\t\t.addColumn(\"collection_slug\", \"text\", (col) => col.notNull())\n\t\t.addColumn(\"content_id\", \"text\", (col) => col.notNull())\n\t\t.addColumn(\"byline_id\", \"text\", (col) =>\n\t\t\tcol.notNull().references(\"_emdash_bylines.id\").onDelete(\"cascade\"),\n\t\t)\n\t\t.addColumn(\"sort_order\", \"integer\", (col) => col.notNull().defaultTo(0))\n\t\t.addColumn(\"role_label\", \"text\")\n\t\t.addColumn(\"created_at\", \"text\", (col) => col.defaultTo(currentTimestamp(db)))\n\t\t.addUniqueConstraint(\"content_bylines_unique\", [\"collection_slug\", \"content_id\", \"byline_id\"])\n\t\t.execute();\n\n\tawait db.schema\n\t\t.createIndex(\"idx_content_bylines_content\")\n\t\t.on(\"_emdash_content_bylines\")\n\t\t.columns([\"collection_slug\", \"content_id\", \"sort_order\"])\n\t\t.execute();\n\n\tawait db.schema\n\t\t.createIndex(\"idx_content_bylines_byline\")\n\t\t.on(\"_emdash_content_bylines\")\n\t\t.column(\"byline_id\")\n\t\t.execute();\n\n\tconst tableNames = await listTablesLike(db, \"ec_%\");\n\tfor (const tableName of tableNames) {\n\t\tawait sql`\n\t\t\tALTER TABLE ${sql.ref(tableName)}\n\t\t\tADD COLUMN primary_byline_id TEXT\n\t\t`.execute(db);\n\n\t\tawait sql`\n\t\t\tCREATE INDEX ${sql.ref(`idx_${tableName}_primary_byline`)}\n\t\t\tON ${sql.ref(tableName)} (primary_byline_id)\n\t\t`.execute(db);\n\t}\n}\n\nexport async function down(db: Kysely<unknown>): Promise<void> {\n\tconst tableNames = await listTablesLike(db, \"ec_%\");\n\tfor (const tableName of tableNames) {\n\t\tawait sql`\n\t\t\tDROP INDEX IF EXISTS ${sql.ref(`idx_${tableName}_primary_byline`)}\n\t\t`.execute(db);\n\n\t\tawait sql`\n\t\t\tALTER TABLE ${sql.ref(tableName)}\n\t\t\tDROP COLUMN primary_byline_id\n\t\t`.execute(db);\n\t}\n\n\tawait db.schema.dropTable(\"_emdash_content_bylines\").execute();\n\tawait db.schema.dropTable(\"_emdash_bylines\").execute();\n}\n","import type { Kysely } from \"kysely\";\n\n/**\n * Migration: Rate limits table + device code polling tracking.\n *\n * 1. Create _emdash_rate_limits for database-backed rate limiting\n * of unauthenticated endpoints (device code, magic link, passkey).\n *\n * 2. Add last_polled_at column to _emdash_device_codes for\n * RFC 8628 slow_down enforcement.\n */\nexport async function up(db: Kysely<unknown>): Promise<void> {\n\t// ── Rate limits table ────────────────────────────────────────────\n\tawait db.schema\n\t\t.createTable(\"_emdash_rate_limits\")\n\t\t.addColumn(\"key\", \"text\", (col) => col.notNull())\n\t\t.addColumn(\"window\", \"text\", (col) => col.notNull())\n\t\t.addColumn(\"count\", \"integer\", (col) => col.notNull().defaultTo(1))\n\t\t.addPrimaryKeyConstraint(\"pk_rate_limits\", [\"key\", \"window\"])\n\t\t.execute();\n\n\t// Index on window for efficient cleanup of expired entries\n\tawait db.schema\n\t\t.createIndex(\"idx_rate_limits_window\")\n\t\t.on(\"_emdash_rate_limits\")\n\t\t.column(\"window\")\n\t\t.execute();\n\n\t// ── Device code polling tracking ─────────────────────────────────\n\tawait db.schema.alterTable(\"_emdash_device_codes\").addColumn(\"last_polled_at\", \"text\").execute();\n}\n\nexport async function down(db: Kysely<unknown>): Promise<void> {\n\tawait db.schema.dropTable(\"_emdash_rate_limits\").execute();\n\n\t// SQLite doesn't support DROP COLUMN before 3.35.0, but since this is\n\t// dev-only (v0, no migrations in production), we accept the limitation.\n\tawait db.schema.alterTable(\"_emdash_device_codes\").dropColumn(\"last_polled_at\").execute();\n}\n","import { type Kysely, type Migration, type MigrationProvider, Migrator } from \"kysely\";\n\nimport type { Database } from \"../types.js\";\n// Import migrations statically for bundling\nimport * as m001 from \"./001_initial.js\";\nimport * as m002 from \"./002_media_status.js\";\nimport * as m003 from \"./003_schema_registry.js\";\nimport * as m004 from \"./004_plugins.js\";\nimport * as m005 from \"./005_menus.js\";\nimport * as m006 from \"./006_taxonomy_defs.js\";\nimport * as m007 from \"./007_widgets.js\";\nimport * as m008 from \"./008_auth.js\";\nimport * as m009 from \"./009_user_disabled.js\";\nimport * as m011 from \"./011_sections.js\";\nimport * as m012 from \"./012_search.js\";\nimport * as m013 from \"./013_scheduled_publishing.js\";\nimport * as m014 from \"./014_draft_revisions.js\";\nimport * as m015 from \"./015_indexes.js\";\nimport * as m016 from \"./016_api_tokens.js\";\nimport * as m017 from \"./017_authorization_codes.js\";\nimport * as m018 from \"./018_seo.js\";\nimport * as m019 from \"./019_i18n.js\";\nimport * as m020 from \"./020_collection_url_pattern.js\";\nimport * as m021 from \"./021_remove_section_categories.js\";\nimport * as m022 from \"./022_marketplace_plugin_state.js\";\nimport * as m023 from \"./023_plugin_metadata.js\";\nimport * as m024 from \"./024_media_placeholders.js\";\nimport * as m025 from \"./025_oauth_clients.js\";\nimport * as m026 from \"./026_cron_tasks.js\";\nimport * as m027 from \"./027_comments.js\";\nimport * as m028 from \"./028_drop_author_url.js\";\nimport * as m029 from \"./029_redirects.js\";\nimport * as m030 from \"./030_widen_scheduled_index.js\";\nimport * as m031 from \"./031_bylines.js\";\nimport * as m032 from \"./032_rate_limits.js\";\n\n/**\n * Migration provider that uses statically imported migrations.\n * This approach works well with bundlers and avoids filesystem access.\n */\nclass StaticMigrationProvider implements MigrationProvider {\n\tasync getMigrations(): Promise<Record<string, Migration>> {\n\t\treturn {\n\t\t\t\"001_initial\": m001,\n\t\t\t\"002_media_status\": m002,\n\t\t\t\"003_schema_registry\": m003,\n\t\t\t\"004_plugins\": m004,\n\t\t\t\"005_menus\": m005,\n\t\t\t\"006_taxonomy_defs\": m006,\n\t\t\t\"007_widgets\": m007,\n\t\t\t\"008_auth\": m008,\n\t\t\t\"009_user_disabled\": m009,\n\t\t\t\"011_sections\": m011,\n\t\t\t\"012_search\": m012,\n\t\t\t\"013_scheduled_publishing\": m013,\n\t\t\t\"014_draft_revisions\": m014,\n\t\t\t\"015_indexes\": m015,\n\t\t\t\"016_api_tokens\": m016,\n\t\t\t\"017_authorization_codes\": m017,\n\t\t\t\"018_seo\": m018,\n\t\t\t\"019_i18n\": m019,\n\t\t\t\"020_collection_url_pattern\": m020,\n\t\t\t\"021_remove_section_categories\": m021,\n\t\t\t\"022_marketplace_plugin_state\": m022,\n\t\t\t\"023_plugin_metadata\": m023,\n\t\t\t\"024_media_placeholders\": m024,\n\t\t\t\"025_oauth_clients\": m025,\n\t\t\t\"026_cron_tasks\": m026,\n\t\t\t\"027_comments\": m027,\n\t\t\t\"028_drop_author_url\": m028,\n\t\t\t\"029_redirects\": m029,\n\t\t\t\"030_widen_scheduled_index\": m030,\n\t\t\t\"031_bylines\": m031,\n\t\t\t\"032_rate_limits\": m032,\n\t\t};\n\t}\n}\n\nexport interface MigrationStatus {\n\tapplied: string[];\n\tpending: string[];\n}\n\n/** Custom migration table name */\nconst MIGRATION_TABLE = \"_emdash_migrations\";\nconst MIGRATION_LOCK_TABLE = \"_emdash_migrations_lock\";\n\n/**\n * Get migration status\n */\nexport async function getMigrationStatus(db: Kysely<Database>): Promise<MigrationStatus> {\n\tconst migrator = new Migrator({\n\t\tdb,\n\t\tprovider: new StaticMigrationProvider(),\n\t\tmigrationTableName: MIGRATION_TABLE,\n\t\tmigrationLockTableName: MIGRATION_LOCK_TABLE,\n\t});\n\n\tconst migrations = await migrator.getMigrations();\n\n\tconst applied: string[] = [];\n\tconst pending: string[] = [];\n\n\tfor (const migration of migrations) {\n\t\tif (migration.executedAt) {\n\t\t\tapplied.push(migration.name);\n\t\t} else {\n\t\t\tpending.push(migration.name);\n\t\t}\n\t}\n\n\treturn { applied, pending };\n}\n\n/**\n * Run all pending migrations\n */\nexport async function runMigrations(db: Kysely<Database>): Promise<{ applied: string[] }> {\n\tconst migrator = new Migrator({\n\t\tdb,\n\t\tprovider: new StaticMigrationProvider(),\n\t\tmigrationTableName: MIGRATION_TABLE,\n\t\tmigrationLockTableName: MIGRATION_LOCK_TABLE,\n\t});\n\n\tconst { error, results } = await migrator.migrateToLatest();\n\n\tconst applied = results?.filter((r) => r.status === \"Success\").map((r) => r.migrationName) ?? [];\n\n\tif (error) {\n\t\t// Kysely sometimes wraps errors with an empty message. Check cause and\n\t\t// failed migration results for the real error.\n\t\tlet msg = error instanceof Error ? error.message : JSON.stringify(error);\n\t\tif (!msg && error instanceof Error && error.cause) {\n\t\t\tmsg = error.cause instanceof Error ? error.cause.message : JSON.stringify(error.cause);\n\t\t}\n\t\tconst failedMigration = results?.find((r) => r.status === \"Error\");\n\t\tif (failedMigration) {\n\t\t\tmsg = `${msg || \"unknown error\"} (migration: ${failedMigration.migrationName})`;\n\t\t}\n\t\tthrow new Error(`Migration failed: ${msg}`);\n\t}\n\n\treturn { applied };\n}\n\n/**\n * Rollback the last migration\n */\nexport async function rollbackMigration(\n\tdb: Kysely<Database>,\n): Promise<{ rolledBack: string | null }> {\n\tconst migrator = new Migrator({\n\t\tdb,\n\t\tprovider: new StaticMigrationProvider(),\n\t\tmigrationTableName: MIGRATION_TABLE,\n\t\tmigrationLockTableName: MIGRATION_LOCK_TABLE,\n\t});\n\n\tconst { error, results } = await migrator.migrateDown();\n\n\tconst rolledBack = results?.[0]?.status === \"Success\" ? results[0].migrationName : null;\n\n\tif (error) {\n\t\tconst msg = error instanceof Error ? error.message : JSON.stringify(error);\n\t\tthrow new Error(`Rollback failed: ${msg}`);\n\t}\n\n\treturn { rolledBack };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AAWA,eAAsBA,MAAG,IAAoC;AAG5D,OAAM,GAAG,OACP,YAAY,YAAY,CACxB,aAAa,CACb,UAAU,MAAM,SAAS,QAAQ,IAAI,YAAY,CAAC,CAClD,UAAU,cAAc,SAAS,QAAQ,IAAI,SAAS,CAAC,CACvD,UAAU,YAAY,SAAS,QAAQ,IAAI,SAAS,CAAC,CACrD,UAAU,QAAQ,SAAS,QAAQ,IAAI,SAAS,CAAC,CACjD,UAAU,aAAa,OAAO,CAC9B,UAAU,cAAc,SAAS,QAAQ,IAAI,UAAU,iBAAiB,GAAG,CAAC,CAAC,CAC7E,SAAS;AAEX,OAAM,GAAG,OACP,YAAY,sBAAsB,CAClC,aAAa,CACb,GAAG,YAAY,CACf,QAAQ,CAAC,cAAc,WAAW,CAAC,CACnC,SAAS;AAGX,OAAM,GAAG,OACP,YAAY,aAAa,CACzB,aAAa,CACb,UAAU,MAAM,SAAS,QAAQ,IAAI,YAAY,CAAC,CAClD,UAAU,QAAQ,SAAS,QAAQ,IAAI,SAAS,CAAC,CACjD,UAAU,QAAQ,SAAS,QAAQ,IAAI,SAAS,CAAC,CACjD,UAAU,SAAS,SAAS,QAAQ,IAAI,SAAS,CAAC,CAClD,UAAU,aAAa,OAAO,CAC9B,UAAU,QAAQ,OAAO,CACzB,oBAAoB,+BAA+B,CAAC,QAAQ,OAAO,CAAC,CACpE,wBAAwB,wBAAwB,CAAC,YAAY,EAAE,cAAc,CAAC,KAAK,GAAG,OACtF,GAAG,SAAS,WAAW,CACvB,CACA,SAAS;AAEX,OAAM,GAAG,OACP,YAAY,sBAAsB,CAClC,aAAa,CACb,GAAG,aAAa,CAChB,OAAO,OAAO,CACd,SAAS;AAGX,OAAM,GAAG,OACP,YAAY,qBAAqB,CACjC,aAAa,CACb,UAAU,cAAc,SAAS,QAAQ,IAAI,SAAS,CAAC,CACvD,UAAU,YAAY,SAAS,QAAQ,IAAI,SAAS,CAAC,CACrD,UAAU,eAAe,SAAS,QAAQ,IAAI,SAAS,CAAC,CACxD,wBAAwB,yBAAyB;EAAC;EAAc;EAAY;EAAc,CAAC,CAC3F,wBACA,kCACA,CAAC,cAAc,EACf,cACA,CAAC,KAAK,GACL,OAAO,GAAG,SAAS,UAAU,CAC9B,CACA,SAAS;AAGX,OAAM,GAAG,OACP,YAAY,QAAQ,CACpB,aAAa,CACb,UAAU,MAAM,SAAS,QAAQ,IAAI,YAAY,CAAC,CAClD,UAAU,YAAY,SAAS,QAAQ,IAAI,SAAS,CAAC,CACrD,UAAU,aAAa,SAAS,QAAQ,IAAI,SAAS,CAAC,CACtD,UAAU,QAAQ,UAAU,CAC5B,UAAU,SAAS,UAAU,CAC7B,UAAU,UAAU,UAAU,CAC9B,UAAU,OAAO,OAAO,CACxB,UAAU,WAAW,OAAO,CAC5B,UAAU,eAAe,SAAS,QAAQ,IAAI,SAAS,CAAC,CACxD,UAAU,gBAAgB,OAAO,CACjC,UAAU,cAAc,SAAS,QAAQ,IAAI,UAAU,iBAAiB,GAAG,CAAC,CAAC,CAC7E,UAAU,aAAa,OAAO,CAC9B,SAAS;AAEX,OAAM,GAAG,OACP,YAAY,yBAAyB,CACrC,aAAa,CACb,GAAG,QAAQ,CACX,OAAO,eAAe,CACtB,SAAS;AAGX,OAAM,GAAG,OACP,YAAY,QAAQ,CACpB,aAAa,CACb,UAAU,MAAM,SAAS,QAAQ,IAAI,YAAY,CAAC,CAClD,UAAU,SAAS,SAAS,QAAQ,IAAI,SAAS,CAAC,QAAQ,CAAC,CAC3D,UAAU,iBAAiB,SAAS,QAAQ,IAAI,SAAS,CAAC,CAC1D,UAAU,QAAQ,OAAO,CACzB,UAAU,QAAQ,SAAS,QAAQ,IAAI,UAAU,aAAa,CAAC,CAC/D,UAAU,aAAa,OAAO,CAC9B,UAAU,QAAQ,OAAO,CACzB,UAAU,cAAc,SAAS,QAAQ,IAAI,UAAU,iBAAiB,GAAG,CAAC,CAAC,CAC7E,SAAS;AAEX,OAAM,GAAG,OACP,YAAY,kBAAkB,CAC9B,aAAa,CACb,GAAG,QAAQ,CACX,OAAO,QAAQ,CACf,SAAS;AAGX,OAAM,GAAG,OACP,YAAY,UAAU,CACtB,aAAa,CACb,UAAU,QAAQ,SAAS,QAAQ,IAAI,YAAY,CAAC,CACpD,UAAU,SAAS,SAAS,QAAQ,IAAI,SAAS,CAAC,CAClD,SAAS;AAGX,OAAM,GAAG,OACP,YAAY,aAAa,CACzB,aAAa,CACb,UAAU,MAAM,SAAS,QAAQ,IAAI,YAAY,CAAC,CAClD,UAAU,aAAa,SAAS,QAAQ,IAAI,UAAU,iBAAiB,GAAG,CAAC,CAAC,CAC5E,UAAU,YAAY,OAAO,CAC7B,UAAU,YAAY,OAAO,CAC7B,UAAU,UAAU,SAAS,QAAQ,IAAI,SAAS,CAAC,CACnD,UAAU,iBAAiB,OAAO,CAClC,UAAU,eAAe,OAAO,CAChC,UAAU,WAAW,OAAO,CAC5B,UAAU,UAAU,OAAO,CAC3B,SAAS;AAEX,OAAM,GAAG,OACP,YAAY,kBAAkB,CAC9B,aAAa,CACb,GAAG,aAAa,CAChB,OAAO,WAAW,CAClB,SAAS;AACX,OAAM,GAAG,OACP,YAAY,mBAAmB,CAC/B,aAAa,CACb,GAAG,aAAa,CAChB,OAAO,SAAS,CAChB,SAAS;AACX,OAAM,GAAG,OACP,YAAY,sBAAsB,CAClC,aAAa,CACb,GAAG,aAAa,CAChB,OAAO,YAAY,CACnB,SAAS;;AAGZ,eAAsBC,QAAK,IAAoC;AAC9D,OAAM,GAAG,OAAO,UAAU,aAAa,CAAC,SAAS;AACjD,OAAM,GAAG,OAAO,UAAU,UAAU,CAAC,SAAS;AAC9C,OAAM,GAAG,OAAO,UAAU,QAAQ,CAAC,SAAS;AAC5C,OAAM,GAAG,OAAO,UAAU,QAAQ,CAAC,SAAS;AAC5C,OAAM,GAAG,OAAO,UAAU,qBAAqB,CAAC,SAAS;AACzD,OAAM,GAAG,OAAO,UAAU,aAAa,CAAC,SAAS;AACjD,OAAM,GAAG,OAAO,UAAU,YAAY,CAAC,SAAS;;;;;;;;;;;;;AClKjD,eAAsBC,MAAG,IAAoC;AAC5D,OAAM,GAAG,OACP,WAAW,QAAQ,CACnB,UAAU,UAAU,SAAS,QAAQ,IAAI,SAAS,CAAC,UAAU,QAAQ,CAAC,CACtE,SAAS;AAEX,OAAM,GAAG,OAAO,YAAY,mBAAmB,CAAC,GAAG,QAAQ,CAAC,OAAO,SAAS,CAAC,SAAS;;AAGvF,eAAsBC,QAAK,IAAoC;AAC9D,OAAM,GAAG,OAAO,UAAU,mBAAmB,CAAC,SAAS;;;;;;;;;;;;;;;ACNxD,eAAsBC,MAAG,IAAoC;AAE5D,OAAM,GAAG,OACP,YAAY,sBAAsB,CAClC,UAAU,MAAM,SAAS,QAAQ,IAAI,YAAY,CAAC,CAClD,UAAU,QAAQ,SAAS,QAAQ,IAAI,SAAS,CAAC,QAAQ,CAAC,CAC1D,UAAU,SAAS,SAAS,QAAQ,IAAI,SAAS,CAAC,CAClD,UAAU,kBAAkB,OAAO,CACnC,UAAU,eAAe,OAAO,CAChC,UAAU,QAAQ,OAAO,CACzB,UAAU,YAAY,OAAO,CAC7B,UAAU,UAAU,OAAO,CAC3B,UAAU,cAAc,SAAS,QAAQ,IAAI,UAAU,iBAAiB,GAAG,CAAC,CAAC,CAC7E,UAAU,cAAc,SAAS,QAAQ,IAAI,UAAU,iBAAiB,GAAG,CAAC,CAAC,CAC7E,SAAS;AAGX,OAAM,GAAG,OACP,YAAY,iBAAiB,CAC7B,UAAU,MAAM,SAAS,QAAQ,IAAI,YAAY,CAAC,CAClD,UAAU,iBAAiB,SAAS,QAAQ,IAAI,SAAS,CAAC,CAC1D,UAAU,QAAQ,SAAS,QAAQ,IAAI,SAAS,CAAC,CACjD,UAAU,SAAS,SAAS,QAAQ,IAAI,SAAS,CAAC,CAClD,UAAU,QAAQ,SAAS,QAAQ,IAAI,SAAS,CAAC,CACjD,UAAU,eAAe,SAAS,QAAQ,IAAI,SAAS,CAAC,CACxD,UAAU,YAAY,YAAY,QAAQ,IAAI,UAAU,EAAE,CAAC,CAC3D,UAAU,UAAU,YAAY,QAAQ,IAAI,UAAU,EAAE,CAAC,CACzD,UAAU,iBAAiB,OAAO,CAClC,UAAU,cAAc,OAAO,CAC/B,UAAU,UAAU,OAAO,CAC3B,UAAU,WAAW,OAAO,CAC5B,UAAU,cAAc,YAAY,QAAQ,IAAI,UAAU,EAAE,CAAC,CAC7D,UAAU,cAAc,SAAS,QAAQ,IAAI,UAAU,iBAAiB,GAAG,CAAC,CAAC,CAC7E,wBACA,wBACA,CAAC,gBAAgB,EACjB,uBACA,CAAC,KAAK,GACL,OAAO,GAAG,SAAS,UAAU,CAC9B,CACA,SAAS;AAGX,OAAM,GAAG,OACP,YAAY,6BAA6B,CACzC,GAAG,iBAAiB,CACpB,QAAQ,CAAC,iBAAiB,OAAO,CAAC,CAClC,QAAQ,CACR,SAAS;AAGX,OAAM,GAAG,OACP,YAAY,wBAAwB,CACpC,GAAG,iBAAiB,CACpB,OAAO,gBAAgB,CACvB,SAAS;AAGX,OAAM,GAAG,OACP,YAAY,kBAAkB,CAC9B,GAAG,iBAAiB,CACpB,QAAQ,CAAC,iBAAiB,aAAa,CAAC,CACxC,SAAS;;AAGZ,eAAsBC,QAAK,IAAoC;AAC9D,OAAM,GAAG,OAAO,UAAU,iBAAiB,CAAC,SAAS;AACrD,OAAM,GAAG,OAAO,UAAU,sBAAsB,CAAC,SAAS;;;;;;;;;;;;;;;;;ACjE3D,eAAsBC,MAAG,IAAoC;AAE5D,OAAM,GAAG,OACP,YAAY,kBAAkB,CAC9B,UAAU,aAAa,SAAS,QAAQ,IAAI,SAAS,CAAC,CACtD,UAAU,cAAc,SAAS,QAAQ,IAAI,SAAS,CAAC,CACvD,UAAU,MAAM,SAAS,QAAQ,IAAI,SAAS,CAAC,CAC/C,UAAU,QAAQ,SAAS,QAAQ,IAAI,SAAS,CAAC,CACjD,UAAU,cAAc,SAAS,QAAQ,IAAI,UAAU,iBAAiB,GAAG,CAAC,CAAC,CAC7E,UAAU,cAAc,SAAS,QAAQ,IAAI,UAAU,iBAAiB,GAAG,CAAC,CAAC,CAC7E,wBAAwB,qBAAqB;EAAC;EAAa;EAAc;EAAK,CAAC,CAC/E,SAAS;AAGX,OAAM,GAAG,OACP,YAAY,0BAA0B,CACtC,GAAG,kBAAkB,CACrB,QAAQ;EAAC;EAAa;EAAc;EAAa,CAAC,CAClD,SAAS;AAGX,OAAM,GAAG,OACP,YAAY,gBAAgB,CAC5B,UAAU,aAAa,SAAS,QAAQ,IAAI,YAAY,CAAC,CACzD,UAAU,WAAW,SAAS,QAAQ,IAAI,SAAS,CAAC,CACpD,UAAU,UAAU,SAAS,QAAQ,IAAI,SAAS,CAAC,UAAU,YAAY,CAAC,CAC1E,UAAU,gBAAgB,SAAS,QAAQ,IAAI,UAAU,iBAAiB,GAAG,CAAC,CAAC,CAC/E,UAAU,gBAAgB,OAAO,CACjC,UAAU,kBAAkB,OAAO,CACnC,UAAU,QAAQ,OAAO,CACzB,SAAS;AAIX,OAAM,GAAG,OACP,YAAY,kBAAkB,CAC9B,UAAU,aAAa,SAAS,QAAQ,IAAI,SAAS,CAAC,CACtD,UAAU,cAAc,SAAS,QAAQ,IAAI,SAAS,CAAC,CACvD,UAAU,cAAc,SAAS,QAAQ,IAAI,SAAS,CAAC,CACvD,UAAU,UAAU,SAAS,QAAQ,IAAI,SAAS,CAAC,CACnD,UAAU,cAAc,SAAS,QAAQ,IAAI,UAAU,iBAAiB,GAAG,CAAC,CAAC,CAC7E,wBAAwB,qBAAqB;EAAC;EAAa;EAAc;EAAa,CAAC,CACvF,SAAS;;AAGZ,eAAsBC,QAAK,IAAoC;AAC9D,OAAM,GAAG,OAAO,UAAU,kBAAkB,CAAC,SAAS;AACtD,OAAM,GAAG,OAAO,UAAU,gBAAgB,CAAC,SAAS;AACpD,OAAM,GAAG,OAAO,UAAU,kBAAkB,CAAC,SAAS;;;;;;;;;;;;;;;AClDvD,eAAsBC,MAAG,IAAoC;AAE5D,OAAM,GAAG,OACP,YAAY,gBAAgB,CAC5B,UAAU,MAAM,SAAS,QAAQ,IAAI,YAAY,CAAC,CAClD,UAAU,QAAQ,SAAS,QAAQ,IAAI,SAAS,CAAC,QAAQ,CAAC,CAC1D,UAAU,SAAS,SAAS,QAAQ,IAAI,SAAS,CAAC,CAClD,UAAU,cAAc,SAAS,QAAQ,IAAI,UAAU,iBAAiB,GAAG,CAAC,CAAC,CAC7E,UAAU,cAAc,SAAS,QAAQ,IAAI,UAAU,iBAAiB,GAAG,CAAC,CAAC,CAC7E,SAAS;AAGX,OAAM,GAAG,OACP,YAAY,qBAAqB,CACjC,UAAU,MAAM,SAAS,QAAQ,IAAI,YAAY,CAAC,CAClD,UAAU,WAAW,SAAS,QAAQ,IAAI,SAAS,CAAC,CACpD,UAAU,aAAa,OAAO,CAC9B,UAAU,cAAc,YAAY,QAAQ,IAAI,SAAS,CAAC,UAAU,EAAE,CAAC,CACvE,UAAU,QAAQ,SAAS,QAAQ,IAAI,SAAS,CAAC,CACjD,UAAU,wBAAwB,OAAO,CACzC,UAAU,gBAAgB,OAAO,CACjC,UAAU,cAAc,OAAO,CAC/B,UAAU,SAAS,SAAS,QAAQ,IAAI,SAAS,CAAC,CAClD,UAAU,cAAc,OAAO,CAC/B,UAAU,UAAU,OAAO,CAC3B,UAAU,eAAe,OAAO,CAChC,UAAU,cAAc,SAAS,QAAQ,IAAI,UAAU,iBAAiB,GAAG,CAAC,CAAC,CAC7E,wBAAwB,sBAAsB,CAAC,UAAU,EAAE,iBAAiB,CAAC,KAAK,GAAG,OACrF,GAAG,SAAS,UAAU,CACtB,CACA,wBACA,wBACA,CAAC,YAAY,EACb,sBACA,CAAC,KAAK,GACL,OAAO,GAAG,SAAS,UAAU,CAC9B,CACA,SAAS;AAGX,OAAM,GAAG,OACP,YAAY,sBAAsB,CAClC,GAAG,qBAAqB,CACxB,QAAQ,CAAC,WAAW,aAAa,CAAC,CAClC,SAAS;AAEX,OAAM,GAAG,OACP,YAAY,wBAAwB,CACpC,GAAG,qBAAqB,CACxB,OAAO,YAAY,CACnB,SAAS;;AAGZ,eAAsBC,QAAK,IAAoC;AAC9D,OAAM,GAAG,OAAO,UAAU,qBAAqB,CAAC,SAAS;AACzD,OAAM,GAAG,OAAO,UAAU,gBAAgB,CAAC,SAAS;;;;;;;;;;;;;;;ACvDrD,eAAsBC,MAAG,IAAoC;AAE5D,OAAM,GAAG,OACP,YAAY,wBAAwB,CACpC,UAAU,MAAM,SAAS,QAAQ,IAAI,YAAY,CAAC,CAClD,UAAU,QAAQ,SAAS,QAAQ,IAAI,SAAS,CAAC,QAAQ,CAAC,CAC1D,UAAU,SAAS,SAAS,QAAQ,IAAI,SAAS,CAAC,CAClD,UAAU,kBAAkB,OAAO,CACnC,UAAU,gBAAgB,YAAY,QAAQ,IAAI,UAAU,EAAE,CAAC,CAC/D,UAAU,eAAe,OAAO,CAChC,UAAU,cAAc,SAAS,QAAQ,IAAI,UAAU,iBAAiB,GAAG,CAAC,CAAC,CAC7E,SAAS;AAGX,OAAM,GAEJ,WAAW,wBAAiC,CAC5C,OAAO,CACP;EACC,IAAI;EACJ,MAAM;EACN,OAAO;EACP,gBAAgB;EAChB,cAAc;EACd,aAAa,KAAK,UAAU,CAAC,QAAQ,CAAC;EACtC,EACD;EACC,IAAI;EACJ,MAAM;EACN,OAAO;EACP,gBAAgB;EAChB,cAAc;EACd,aAAa,KAAK,UAAU,CAAC,QAAQ,CAAC;EACtC,CACD,CAAC,CACD,SAAS;;AAGZ,eAAsBC,QAAK,IAAoC;AAC9D,OAAM,GAAG,OAAO,UAAU,wBAAwB,CAAC,SAAS;;;;;;;;;AC/C7D,eAAsBC,MAAG,IAAoC;AAE5D,OAAM,GAAG,OACP,YAAY,uBAAuB,CACnC,UAAU,MAAM,SAAS,QAAQ,IAAI,YAAY,CAAC,CAClD,UAAU,QAAQ,SAAS,QAAQ,IAAI,SAAS,CAAC,QAAQ,CAAC,CAC1D,UAAU,SAAS,SAAS,QAAQ,IAAI,SAAS,CAAC,CAClD,UAAU,eAAe,OAAO,CAChC,UAAU,cAAc,SAAS,QAAQ,IAAI,UAAU,GAAG,oBAAoB,CAAC,CAC/E,SAAS;AAGX,OAAM,GAAG,OACP,YAAY,kBAAkB,CAC9B,UAAU,MAAM,SAAS,QAAQ,IAAI,YAAY,CAAC,CAClD,UAAU,WAAW,SAAS,QAC9B,IAAI,SAAS,CAAC,WAAW,0BAA0B,CAAC,SAAS,UAAU,CACvE,CACA,UAAU,cAAc,YAAY,QAAQ,IAAI,SAAS,CAAC,UAAU,EAAE,CAAC,CACvE,UAAU,QAAQ,SAAS,QAAQ,IAAI,SAAS,CAAC,CACjD,UAAU,SAAS,OAAO,CAC1B,UAAU,WAAW,OAAO,CAC5B,UAAU,aAAa,OAAO,CAC9B,UAAU,gBAAgB,OAAO,CACjC,UAAU,mBAAmB,OAAO,CACpC,UAAU,cAAc,SAAS,QAAQ,IAAI,UAAU,GAAG,oBAAoB,CAAC,CAC/E,SAAS;AAGX,OAAM,GAAG,OACP,YAAY,mBAAmB,CAC/B,GAAG,kBAAkB,CACrB,QAAQ,CAAC,WAAW,aAAa,CAAC,CAClC,SAAS;;AAGZ,eAAsBC,QAAK,IAAoC;AAC9D,OAAM,GAAG,OAAO,UAAU,kBAAkB,CAAC,SAAS;AACtD,OAAM,GAAG,OAAO,UAAU,uBAAuB,CAAC,SAAS;;;;;;;;;;;;;;;;;;;;;ACvB5D,eAAsBC,MAAG,IAAoC;AAG5D,OAAM,GAAG,OACP,YAAY,YAAY,CACxB,UAAU,MAAM,SAAS,QAAQ,IAAI,YAAY,CAAC,CAClD,UAAU,SAAS,SAAS,QAAQ,IAAI,SAAS,CAAC,QAAQ,CAAC,CAC3D,UAAU,QAAQ,OAAO,CACzB,UAAU,cAAc,OAAO,CAC/B,UAAU,QAAQ,YAAY,QAAQ,IAAI,SAAS,CAAC,UAAU,GAAG,CAAC,CAClE,UAAU,kBAAkB,YAAY,QAAQ,IAAI,SAAS,CAAC,UAAU,EAAE,CAAC,CAC3E,UAAU,QAAQ,OAAO,CACzB,UAAU,cAAc,SAAS,QAAQ,IAAI,UAAU,iBAAiB,GAAG,CAAC,CAAC,CAC7E,UAAU,cAAc,SAAS,QAAQ,IAAI,UAAU,iBAAiB,GAAG,CAAC,CAAC,CAC7E,SAAS;AAGX,OAAM,GAAG;;;;;;;;;;;;;;;KAeL,sBAAsB,GAAG,CAAC;;GAE5B,QAAQ,GAAG;AAGb,OAAM,GAAG,OAAO,UAAU,QAAQ,CAAC,SAAS;AAC5C,OAAM,GAAG,wCAAwC,QAAQ,GAAG;AAG5D,OAAM,GAAG,OAAO,YAAY,kBAAkB,CAAC,GAAG,QAAQ,CAAC,OAAO,QAAQ,CAAC,SAAS;AAGpF,OAAM,GAAG,OACP,YAAY,cAAc,CAC1B,UAAU,MAAM,SAAS,QAAQ,IAAI,YAAY,CAAC,CAClD,UAAU,WAAW,SAAS,QAAQ,IAAI,SAAS,CAAC,CACpD,UAAU,cAAc,WAAW,GAAG,GAAG,QAAQ,IAAI,SAAS,CAAC,CAC/D,UAAU,WAAW,YAAY,QAAQ,IAAI,SAAS,CAAC,UAAU,EAAE,CAAC,CACpE,UAAU,eAAe,SAAS,QAAQ,IAAI,SAAS,CAAC,CACxD,UAAU,aAAa,YAAY,QAAQ,IAAI,SAAS,CAAC,UAAU,EAAE,CAAC,CACtE,UAAU,cAAc,OAAO,CAC/B,UAAU,QAAQ,OAAO,CACzB,UAAU,cAAc,SAAS,QAAQ,IAAI,UAAU,iBAAiB,GAAG,CAAC,CAAC,CAC7E,UAAU,gBAAgB,SAAS,QAAQ,IAAI,UAAU,iBAAiB,GAAG,CAAC,CAAC,CAC/E,wBAAwB,uBAAuB,CAAC,UAAU,EAAE,SAAS,CAAC,KAAK,GAAG,OAC9E,GAAG,SAAS,UAAU,CACtB,CACA,SAAS;AAEX,OAAM,GAAG,OAAO,YAAY,uBAAuB,CAAC,GAAG,cAAc,CAAC,OAAO,UAAU,CAAC,SAAS;AAGjG,OAAM,GAAG,OACP,YAAY,cAAc,CAC1B,UAAU,QAAQ,SAAS,QAAQ,IAAI,YAAY,CAAC,CACpD,UAAU,WAAW,OAAO,CAC5B,UAAU,SAAS,OAAO,CAC1B,UAAU,QAAQ,SAAS,QAAQ,IAAI,SAAS,CAAC,CACjD,UAAU,QAAQ,UAAU,CAC5B,UAAU,cAAc,OAAO,CAC/B,UAAU,cAAc,SAAS,QAAQ,IAAI,SAAS,CAAC,CACvD,UAAU,cAAc,SAAS,QAAQ,IAAI,UAAU,iBAAiB,GAAG,CAAC,CAAC,CAC7E,wBAAwB,uBAAuB,CAAC,UAAU,EAAE,SAAS,CAAC,KAAK,GAAG,OAC9E,GAAG,SAAS,UAAU,CACtB,CACA,wBAAwB,6BAA6B,CAAC,aAAa,EAAE,SAAS,CAAC,KAAK,GAAG,OACvF,GAAG,SAAS,WAAW,CACvB,CACA,SAAS;AAEX,OAAM,GAAG,OAAO,YAAY,wBAAwB,CAAC,GAAG,cAAc,CAAC,OAAO,QAAQ,CAAC,SAAS;AAGhG,OAAM,GAAG,OACP,YAAY,iBAAiB,CAC7B,UAAU,YAAY,SAAS,QAAQ,IAAI,SAAS,CAAC,CACrD,UAAU,uBAAuB,SAAS,QAAQ,IAAI,SAAS,CAAC,CAChE,UAAU,WAAW,SAAS,QAAQ,IAAI,SAAS,CAAC,CACpD,UAAU,cAAc,SAAS,QAAQ,IAAI,UAAU,iBAAiB,GAAG,CAAC,CAAC,CAC7E,wBAAwB,qBAAqB,CAAC,YAAY,sBAAsB,CAAC,CACjF,wBAAwB,0BAA0B,CAAC,UAAU,EAAE,SAAS,CAAC,KAAK,GAAG,OACjF,GAAG,SAAS,UAAU,CACtB,CACA,SAAS;AAEX,OAAM,GAAG,OACP,YAAY,0BAA0B,CACtC,GAAG,iBAAiB,CACpB,OAAO,UAAU,CACjB,SAAS;AAGX,OAAM,GAAG,OACP,YAAY,kBAAkB,CAC9B,UAAU,UAAU,SAAS,QAAQ,IAAI,YAAY,CAAC,CACtD,UAAU,gBAAgB,YAAY,QAAQ,IAAI,SAAS,CAAC,UAAU,GAAG,CAAC,CAC1E,UAAU,WAAW,YAAY,QAAQ,IAAI,SAAS,CAAC,UAAU,EAAE,CAAC,CACpE,UAAU,cAAc,SAAS,QAAQ,IAAI,UAAU,iBAAiB,GAAG,CAAC,CAAC,CAC7E,SAAS;AAGX,OAAM,GAAG,OACP,YAAY,kBAAkB,CAC9B,UAAU,aAAa,SAAS,QAAQ,IAAI,YAAY,CAAC,CACzD,UAAU,QAAQ,SAAS,QAAQ,IAAI,SAAS,CAAC,CACjD,UAAU,WAAW,OAAO,CAC5B,UAAU,QAAQ,OAAO,CACzB,UAAU,cAAc,SAAS,QAAQ,IAAI,SAAS,CAAC,CACvD,UAAU,cAAc,SAAS,QAAQ,IAAI,UAAU,iBAAiB,GAAG,CAAC,CAAC,CAC7E,SAAS;AAGX,OAAM,GAAG,OACP,YAAY,8BAA8B,CAC1C,GAAG,kBAAkB,CACrB,OAAO,aAAa,CACpB,SAAS;;AAGZ,eAAsBC,QAAK,IAAoC;AAE9D,OAAM,GAAG,OAAO,UAAU,kBAAkB,CAAC,SAAS;AACtD,OAAM,GAAG,OAAO,UAAU,kBAAkB,CAAC,SAAS;AACtD,OAAM,GAAG,OAAO,UAAU,iBAAiB,CAAC,SAAS;AACrD,OAAM,GAAG,OAAO,UAAU,cAAc,CAAC,SAAS;AAClD,OAAM,GAAG,OAAO,UAAU,cAAc,CAAC,SAAS;AAGlD,OAAM,GAAG,OACP,YAAY,YAAY,CACxB,UAAU,MAAM,SAAS,QAAQ,IAAI,YAAY,CAAC,CAClD,UAAU,SAAS,SAAS,QAAQ,IAAI,SAAS,CAAC,QAAQ,CAAC,CAC3D,UAAU,iBAAiB,SAAS,QAAQ,IAAI,SAAS,CAAC,CAC1D,UAAU,QAAQ,OAAO,CACzB,UAAU,QAAQ,SAAS,QAAQ,IAAI,UAAU,aAAa,CAAC,CAC/D,UAAU,aAAa,OAAO,CAC9B,UAAU,QAAQ,OAAO,CACzB,UAAU,cAAc,SAAS,QAAQ,IAAI,UAAU,iBAAiB,GAAG,CAAC,CAAC,CAC7E,SAAS;AAGX,OAAM,GAAG;;;;;;;;;;;;;;;;;GAiBP,QAAQ,GAAG;AAEb,OAAM,GAAG,OAAO,UAAU,QAAQ,CAAC,SAAS;AAC5C,OAAM,GAAG,wCAAwC,QAAQ,GAAG;AAE5D,OAAM,GAAG,OAAO,YAAY,kBAAkB,CAAC,GAAG,QAAQ,CAAC,OAAO,QAAQ,CAAC,SAAS;;;;;;;;;;;;;;;;ACtLrF,eAAsBC,MAAG,IAAoC;AAE5D,OAAM,GAAG,mEAAmE,QAAQ,GAAG;AAGvF,OAAM,GAAG,OAAO,YAAY,qBAAqB,CAAC,GAAG,QAAQ,CAAC,OAAO,WAAW,CAAC,SAAS;;AAG3F,eAAsBC,QAAK,IAAoC;AAG9D,OAAM,GAAG,OAAO,UAAU,qBAAqB,CAAC,SAAS;;;;;;;;;;;;;;;;ACZ1D,eAAsBC,MAAG,IAAoC;AAE5D,OAAM,GAAG,OACP,YAAY,6BAA6B,CACzC,UAAU,MAAM,SAAS,QAAQ,IAAI,YAAY,CAAC,CAClD,UAAU,QAAQ,SAAS,QAAQ,IAAI,SAAS,CAAC,QAAQ,CAAC,CAC1D,UAAU,SAAS,SAAS,QAAQ,IAAI,SAAS,CAAC,CAClD,UAAU,cAAc,YAAY,QAAQ,IAAI,UAAU,EAAE,CAAC,CAC7D,UAAU,cAAc,SAAS,QAAQ,IAAI,UAAU,GAAG,oBAAoB,CAAC,CAC/E,SAAS;AAGX,OAAM,GAAG,OACP,YAAY,mBAAmB,CAC/B,UAAU,MAAM,SAAS,QAAQ,IAAI,YAAY,CAAC,CAClD,UAAU,QAAQ,SAAS,QAAQ,IAAI,SAAS,CAAC,QAAQ,CAAC,CAC1D,UAAU,SAAS,SAAS,QAAQ,IAAI,SAAS,CAAC,CAClD,UAAU,eAAe,OAAO,CAEhC,UAAU,eAAe,SAAS,QAClC,IAAI,WAAW,gCAAgC,CAAC,SAAS,WAAW,CACpE,CACA,UAAU,YAAY,OAAO,CAE7B,UAAU,WAAW,SAAS,QAAQ,IAAI,SAAS,CAAC,CAEpD,UAAU,oBAAoB,OAAO,CAErC,UAAU,UAAU,SAAS,QAAQ,IAAI,SAAS,CAAC,UAAU,OAAO,CAAC,CACrE,UAAU,YAAY,OAAO,CAE7B,UAAU,cAAc,SAAS,QAAQ,IAAI,UAAU,GAAG,oBAAoB,CAAC,CAC/E,UAAU,cAAc,SAAS,QAAQ,IAAI,UAAU,GAAG,oBAAoB,CAAC,CAC/E,SAAS;AAGX,OAAM,GAAG,OACP,YAAY,wBAAwB,CACpC,GAAG,mBAAmB,CACtB,QAAQ,CAAC,cAAc,CAAC,CACxB,SAAS;AAGX,OAAM,GAAG,OACP,YAAY,sBAAsB,CAClC,GAAG,mBAAmB,CACtB,QAAQ,CAAC,SAAS,CAAC,CACnB,SAAS;;AAGZ,eAAsBC,QAAK,IAAoC;AAC9D,OAAM,GAAG,OAAO,UAAU,8BAA8B,CAAC,SAAS;AAClE,OAAM,GAAG,OAAO,UAAU,sBAAsB,CAAC,SAAS;AAC1D,OAAM,GAAG,OAAO,UAAU,mBAAmB,CAAC,SAAS;AACvD,OAAM,GAAG,OAAO,UAAU,6BAA6B,CAAC,SAAS;;;;;;;;;;;;;;;ACvDlE,eAAsBC,MAAG,IAAoC;AAE5D,OAAM,GAAG,OAAO,WAAW,sBAAsB,CAAC,UAAU,iBAAiB,OAAO,CAAC,SAAS;AAG9F,OAAM,GAAG,OACP,WAAW,iBAAiB,CAC5B,UAAU,cAAc,YAAY,QAAQ,IAAI,UAAU,EAAE,CAAC,CAC7D,SAAS;;AAGZ,eAAsBC,QAAK,IAAoC;AAG9D,OAAM,GAAG,OAAO,WAAW,iBAAiB,CAAC,WAAW,aAAa,CAAC,SAAS;AAC/E,OAAM,GAAG,OAAO,WAAW,sBAAsB,CAAC,WAAW,gBAAgB,CAAC,SAAS;;;;;;;;;;;;;;;;ACXxF,eAAsBC,MAAG,IAAoC;CAE5D,MAAM,aAAa,MAAM,eAAe,IAAI,OAAO;AAGnD,MAAK,MAAM,aAAa,YAAY;EACnC,MAAM,QAAQ,EAAE,MAAM,WAAW;AACjC,QAAM,GAAG;iBACM,IAAI,IAAI,MAAM,KAAK,CAAC;;IAEjC,QAAQ,GAAG;AAGb,QAAM,GAAG;kBACO,IAAI,IAAI,OAAO,MAAM,KAAK,YAAY,CAAC;QACjD,IAAI,IAAI,MAAM,KAAK,CAAC;;IAExB,QAAQ,GAAG;;;AAIf,eAAsBC,QAAK,IAAoC;CAE9D,MAAM,aAAa,MAAM,eAAe,IAAI,OAAO;AAGnD,MAAK,MAAM,aAAa,YAAY;EACnC,MAAM,QAAQ,EAAE,MAAM,WAAW;AAEjC,QAAM,GAAG;0BACe,IAAI,IAAI,OAAO,MAAM,KAAK,YAAY,CAAC;IAC7D,QAAQ,GAAG;AAEb,QAAM,GAAG;iBACM,IAAI,IAAI,MAAM,KAAK,CAAC;;IAEjC,QAAQ,GAAG;;;;;;;;;;AC7Cf,eAAsBC,MAAG,IAAoC;CAE5D,MAAM,SAAS,MAAM,GAEnB,WAAW,sBAA+B,CAE1C,OAAO,OAAgB,CACvB,SAAS;AAGX,MAAK,MAAM,OAAO,QAAmC;EACpD,MAAM,YAAY,MAAM,IAAI;AAG5B,QAAM,GAAG;iBACM,IAAI,IAAI,UAAU,CAAC;;IAEhC,QAAQ,GAAG;AAGb,QAAM,GAAG;iBACM,IAAI,IAAI,UAAU,CAAC;;IAEhC,QAAQ,GAAG;AAGb,QAAM,GAAG;kBACO,IAAI,IAAI,OAAO,IAAI,KAAK,gBAAgB,CAAC;QACnD,IAAI,IAAI,UAAU,CAAC;IACvB,QAAQ,GAAG;AAEb,QAAM,GAAG;kBACO,IAAI,IAAI,OAAO,IAAI,KAAK,iBAAiB,CAAC;QACpD,IAAI,IAAI,UAAU,CAAC;IACvB,QAAQ,GAAG;;;AAIf,eAAsBC,QAAK,IAAoC;CAC9D,MAAM,SAAS,MAAM,GAEnB,WAAW,sBAA+B,CAE1C,OAAO,OAAgB,CACvB,SAAS;AAGX,MAAK,MAAM,OAAO,QAAmC;EACpD,MAAM,YAAY,MAAM,IAAI;AAE5B,QAAM,GAAG;0BACe,IAAI,IAAI,OAAO,IAAI,KAAK,iBAAiB,CAAC;IAChE,QAAQ,GAAG;AAEb,QAAM,GAAG;0BACe,IAAI,IAAI,OAAO,IAAI,KAAK,gBAAgB,CAAC;IAC/D,QAAQ,GAAG;AAEb,QAAM,GAAG;iBACM,IAAI,IAAI,UAAU,CAAC;;IAEhC,QAAQ,GAAG;AAEb,QAAM,GAAG;iBACM,IAAI,IAAI,UAAU,CAAC;;IAEhC,QAAQ,GAAG;;;;;;;;;;;;;;;;;;;;;ACrDf,eAAsBC,MAAG,IAAoC;AAE5D,OAAM,GAAG,OAAO,YAAY,sBAAsB,CAAC,GAAG,QAAQ,CAAC,OAAO,YAAY,CAAC,SAAS;AAC5F,OAAM,GAAG,OAAO,YAAY,qBAAqB,CAAC,GAAG,QAAQ,CAAC,OAAO,WAAW,CAAC,SAAS;AAC1F,OAAM,GAAG,OAAO,YAAY,uBAAuB,CAAC,GAAG,QAAQ,CAAC,OAAO,aAAa,CAAC,SAAS;AAI9F,OAAM,GAAG,OACP,YAAY,8BAA8B,CAC1C,GAAG,qBAAqB,CACxB,OAAO,cAAc,CACrB,SAAS;AAGX,OAAM,GAAG,OACP,YAAY,wBAAwB,CACpC,GAAG,aAAa,CAChB,OAAO,YAAY,CACnB,SAAS;AAIX,OAAM,GAAG,OACP,YAAY,qBAAqB,CACjC,GAAG,aAAa,CAChB,QAAQ,CAAC,iBAAiB,cAAc,CAAC,CACzC,SAAS;CAKX,MAAM,aAAa,MAAM,eAAe,IAAI,OAAO;AAEnD,MAAK,MAAM,aAAa,YAAY;EACnC,MAAM,QAAQ,EAAE,MAAM,WAAW;AACjC,QAAM,GAAG;kBACO,IAAI,IAAI,OAAO,MAAM,KAAK,SAAS,CAAC;QAC9C,IAAI,IAAI,MAAM,KAAK,CAAC;IACxB,QAAQ,GAAG;AAEb,QAAM,GAAG;kBACO,IAAI,IAAI,OAAO,MAAM,KAAK,UAAU,CAAC;QAC/C,IAAI,IAAI,MAAM,KAAK,CAAC;IACxB,QAAQ,GAAG;;;AAIf,eAAsBC,QAAK,IAAoC;CAE9D,MAAM,aAAa,MAAM,eAAe,IAAI,OAAO;AAEnD,MAAK,MAAM,aAAa,YAAY;EACnC,MAAM,QAAQ,EAAE,MAAM,WAAW;AACjC,QAAM,GAAG,wBAAwB,IAAI,IAAI,OAAO,MAAM,KAAK,UAAU,GAAG,QAAQ,GAAG;AACnF,QAAM,GAAG,wBAAwB,IAAI,IAAI,OAAO,MAAM,KAAK,SAAS,GAAG,QAAQ,GAAG;;AAInF,OAAM,GAAG,OAAO,UAAU,qBAAqB,CAAC,SAAS;AACzD,OAAM,GAAG,OAAO,UAAU,wBAAwB,CAAC,SAAS;AAC5D,OAAM,GAAG,OAAO,UAAU,8BAA8B,CAAC,SAAS;AAClE,OAAM,GAAG,OAAO,UAAU,uBAAuB,CAAC,SAAS;AAC3D,OAAM,GAAG,OAAO,UAAU,qBAAqB,CAAC,SAAS;AACzD,OAAM,GAAG,OAAO,UAAU,sBAAsB,CAAC,SAAS;;;;;;;;;;;;;;;;;ACpE3D,eAAsBC,MAAG,IAAoC;AAE5D,OAAM,GAAG,OACP,YAAY,qBAAqB,CACjC,UAAU,MAAM,SAAS,QAAQ,IAAI,YAAY,CAAC,CAClD,UAAU,QAAQ,SAAS,QAAQ,IAAI,SAAS,CAAC,CACjD,UAAU,cAAc,SAAS,QAAQ,IAAI,SAAS,CAAC,QAAQ,CAAC,CAChE,UAAU,UAAU,SAAS,QAAQ,IAAI,SAAS,CAAC,CACnD,UAAU,WAAW,SAAS,QAAQ,IAAI,SAAS,CAAC,CACpD,UAAU,UAAU,SAAS,QAAQ,IAAI,SAAS,CAAC,CACnD,UAAU,cAAc,OAAO,CAC/B,UAAU,gBAAgB,OAAO,CACjC,UAAU,cAAc,SAAS,QAAQ,IAAI,UAAU,iBAAiB,GAAG,CAAC,CAAC,CAC7E,wBAAwB,sBAAsB,CAAC,UAAU,EAAE,SAAS,CAAC,KAAK,GAAG,OAC7E,GAAG,SAAS,UAAU,CACtB,CACA,SAAS;AAEX,OAAM,GAAG,OACP,YAAY,4BAA4B,CACxC,GAAG,qBAAqB,CACxB,OAAO,aAAa,CACpB,SAAS;AAEX,OAAM,GAAG,OACP,YAAY,yBAAyB,CACrC,GAAG,qBAAqB,CACxB,OAAO,UAAU,CACjB,SAAS;AAGX,OAAM,GAAG,OACP,YAAY,uBAAuB,CACnC,UAAU,cAAc,SAAS,QAAQ,IAAI,YAAY,CAAC,CAC1D,UAAU,cAAc,SAAS,QAAQ,IAAI,SAAS,CAAC,CACvD,UAAU,WAAW,SAAS,QAAQ,IAAI,SAAS,CAAC,CACpD,UAAU,UAAU,SAAS,QAAQ,IAAI,SAAS,CAAC,CACnD,UAAU,eAAe,SAAS,QAAQ,IAAI,SAAS,CAAC,UAAU,MAAM,CAAC,CACzE,UAAU,cAAc,SAAS,QAAQ,IAAI,SAAS,CAAC,CACvD,UAAU,sBAAsB,OAAO,CACvC,UAAU,cAAc,SAAS,QAAQ,IAAI,UAAU,iBAAiB,GAAG,CAAC,CAAC,CAC7E,wBAAwB,wBAAwB,CAAC,UAAU,EAAE,SAAS,CAAC,KAAK,GAAG,OAC/E,GAAG,SAAS,UAAU,CACtB,CACA,SAAS;AAEX,OAAM,GAAG,OACP,YAAY,2BAA2B,CACvC,GAAG,uBAAuB,CAC1B,OAAO,UAAU,CACjB,SAAS;AAEX,OAAM,GAAG,OACP,YAAY,2BAA2B,CACvC,GAAG,uBAAuB,CAC1B,OAAO,aAAa,CACpB,SAAS;AAGX,OAAM,GAAG,OACP,YAAY,uBAAuB,CACnC,UAAU,eAAe,SAAS,QAAQ,IAAI,YAAY,CAAC,CAC3D,UAAU,aAAa,SAAS,QAAQ,IAAI,SAAS,CAAC,QAAQ,CAAC,CAC/D,UAAU,UAAU,SAAS,QAAQ,IAAI,SAAS,CAAC,CACnD,UAAU,WAAW,OAAO,CAC5B,UAAU,UAAU,SAAS,QAAQ,IAAI,SAAS,CAAC,UAAU,UAAU,CAAC,CACxE,UAAU,cAAc,SAAS,QAAQ,IAAI,SAAS,CAAC,CACvD,UAAU,YAAY,YAAY,QAAQ,IAAI,SAAS,CAAC,UAAU,EAAE,CAAC,CACrE,UAAU,cAAc,SAAS,QAAQ,IAAI,UAAU,iBAAiB,GAAG,CAAC,CAAC,CAC7E,SAAS;;AAGZ,eAAsBC,QAAK,IAAoC;AAC9D,OAAM,GAAG,OAAO,UAAU,uBAAuB,CAAC,SAAS;AAC3D,OAAM,GAAG,OAAO,UAAU,uBAAuB,CAAC,SAAS;AAC3D,OAAM,GAAG,OAAO,UAAU,qBAAqB,CAAC,SAAS;;;;;;;;;;;;;;;;;AC1E1D,eAAsBC,MAAG,IAAoC;AAC5D,OAAM,GAAG,OACP,YAAY,8BAA8B,CAC1C,UAAU,aAAa,SAAS,QAAQ,IAAI,YAAY,CAAC,CACzD,UAAU,aAAa,SAAS,QAAQ,IAAI,SAAS,CAAC,CACtD,UAAU,gBAAgB,SAAS,QAAQ,IAAI,SAAS,CAAC,CACzD,UAAU,WAAW,SAAS,QAAQ,IAAI,SAAS,CAAC,CACpD,UAAU,UAAU,SAAS,QAAQ,IAAI,SAAS,CAAC,CACnD,UAAU,kBAAkB,SAAS,QAAQ,IAAI,SAAS,CAAC,CAC3D,UAAU,yBAAyB,SAAS,QAAQ,IAAI,SAAS,CAAC,UAAU,OAAO,CAAC,CACpF,UAAU,YAAY,OAAO,CAC7B,UAAU,cAAc,SAAS,QAAQ,IAAI,SAAS,CAAC,CACvD,UAAU,cAAc,SAAS,QAAQ,IAAI,UAAU,iBAAiB,GAAG,CAAC,CAAC,CAC7E,wBAAwB,sBAAsB,CAAC,UAAU,EAAE,SAAS,CAAC,KAAK,GAAG,OAC7E,GAAG,SAAS,UAAU,CACtB,CACA,SAAS;AAEX,OAAM,GAAG,OACP,YAAY,yBAAyB,CACrC,GAAG,8BAA8B,CACjC,OAAO,aAAa,CACpB,SAAS;AAGX,OAAM,GAAG,6DAA6D,QAAQ,GAAG;;AAGlF,eAAsBC,QAAK,IAAoC;AAC9D,OAAM,GAAG,OAAO,UAAU,8BAA8B,CAAC,SAAS;;;;;;;;;;;;;;;;;;;;AC1BnE,eAAsBC,MAAG,IAAoC;AAE5D,OAAM,GAAG,OACP,YAAY,cAAc,CAC1B,UAAU,cAAc,SAAS,QAAQ,IAAI,SAAS,CAAC,CACvD,UAAU,cAAc,SAAS,QAAQ,IAAI,SAAS,CAAC,CACvD,UAAU,aAAa,OAAO,CAC9B,UAAU,mBAAmB,OAAO,CACpC,UAAU,aAAa,OAAO,CAC9B,UAAU,iBAAiB,OAAO,CAClC,UAAU,gBAAgB,YAAY,QAAQ,IAAI,SAAS,CAAC,UAAU,EAAE,CAAC,CACzE,UAAU,cAAc,SAAS,QAAQ,IAAI,SAAS,CAAC,UAAU,iBAAiB,GAAG,CAAC,CAAC,CACvF,UAAU,cAAc,SAAS,QAAQ,IAAI,SAAS,CAAC,UAAU,iBAAiB,GAAG,CAAC,CAAC,CACvF,wBAAwB,kBAAkB,CAAC,cAAc,aAAa,CAAC,CACvE,SAAS;AAKX,OAAM,GAAG;;;GAGP,QAAQ,GAAG;AAGb,OAAM,GAAG;;;GAGP,QAAQ,GAAG;;AAGd,eAAsBC,QAAK,IAAoC;AAC9D,OAAM,GAAG,mCAAmC,QAAQ,GAAG;AAGvD,OAAM,GAAG;;;GAGP,QAAQ,GAAG;;;;;;;;;;;;;;ACMd,MAAM,kBAAkB;AACxB,SAAS,WAAW,MAAsB;AACzC,QAAO,IAAI,KAAK,QAAQ,iBAAiB,OAAK,CAAC;;;AAIhD,MAAM,kBAAkB;;AAGxB,MAAM,qBAAqB;AAC3B,SAAS,kBAAkB,MAAoB;AAC9C,KAAI,CAAC,mBAAmB,KAAK,KAAK,CACjC,OAAM,IAAI,MAAM,gCAAgC,KAAK,GAAG;;;AAK1D,MAAM,uBAAuB,IAAI,IAAI;CAAC;CAAQ;CAAW;CAAQ;CAAQ;CAAQ;CAAW;CAAG,CAAC;AAChG,SAAS,mBAAmB,MAAc,SAAuB;AAChE,KAAI,CAAC,qBAAqB,IAAI,KAAK,aAAa,CAAC,CAChD,OAAM,IAAI,MAAM,2BAA2B,KAAK,gBAAgB,QAAQ,GAAG;;;;;;;;;;;AAa7E,MAAM,uBACL;AACD,SAAS,qBAAqB,OAAe,SAAuB;AACnE,KAAI,CAAC,qBAAqB,KAAK,MAAM,CACpC,OAAM,IAAI,MAAM,6BAA6B,MAAM,gBAAgB,QAAQ,GAAG;;;;;;;;AAUhF,MAAM,2BAA2B;AACjC,SAAS,oBAAoB,OAAuB;AAEnD,KAAI,MAAM,WAAW,IAAI,CAAE,QAAO;AAClC,KAAI,yBAAyB,KAAK,MAAM,CAAE,QAAO,IAAI,MAAM;AAC3D,QAAO;;;;;;AAOR,MAAM,uBAAuB;AAC7B,SAAS,uBAAuB,QAAgB,SAAuB;AACtE,KAAI,CAAC,qBAAqB,KAAK,OAAO,CACrC,OAAM,IAAI,MAAM,6BAA6B,QAAQ,wCAAwC;AAG9F,KAAI,OAAO,SAAS,IAAI,CACvB,OAAM,IAAI,MAAM,6BAA6B,QAAQ,uBAAuB;;;;;;AAQ9E,eAAe,WAAW,IAAoC;CAC7D,MAAM,aAAa,MAAM,eAAe,IAAI,OAAO;AAEnD,MAAK,MAAM,KAAK,YAAY;AAC3B,oBAAkB,EAAE;AASpB,OANkB,MAAM,GAAwB;;;qDAGG,EAAE;;IAEnD,QAAQ,GAAG,EACC,KAAK,IAAI,WAAW,KAAM;AAGxC,QAAM,GAAG,eAAe,IAAI,IAAI,EAAE,CAAC,+CAA+C,QAAQ,GAAG;AAC7F,QAAM,GAAG,eAAe,IAAI,IAAI,EAAE,CAAC,oCAAoC,QAAQ,GAAG;EAIlF,MAAM,cAAc,MAAM,GAAwB;;sBAE9B,EAAE;;;;;uBAKD,EAAE;;IAErB,QAAQ,GAAG;AAEb,OAAK,MAAM,KAAK,YAAY,KAC3B,OAAM,GAAG,eAAe,IAAI,IAAI,EAAE,CAAC,mBAAmB,IAAI,IAAI,EAAE,QAAQ,GAAG,QAAQ,GAAG;AAIvF,QAAM,GAAG;iBACM,IAAI,IAAI,EAAE,CAAC;oBACR,IAAI,IAAI,GAAG,EAAE,qBAAqB,CAAC;IACnD,QAAQ,GAAG;AAGb,QAAM,GAAG,UAAU,IAAI,IAAI,EAAE,CAAC,6BAA6B,QAAQ,GAAG;AAGtE,QAAM,GAAG,gBAAgB,IAAI,IAAI,OAAO,EAAE,SAAS,CAAC,MAAM,IAAI,IAAI,EAAE,CAAC,WAAW,QAAQ,GAAG;AAC3F,QAAM,GAAG;kBACO,IAAI,IAAI,OAAO,EAAE,oBAAoB,CAAC;QAChD,IAAI,IAAI,EAAE,CAAC;IACf,QAAQ,GAAG;;AAUd,MANwB,MAAM,GAAwB;;;;;GAKpD,QAAQ,GAAG,EACO,KAAK,IAAI,WAAW,KACvC,OAAM,GAAG;;;IAGP,QAAQ,GAAG;;AAIf,eAAsBC,MAAG,IAAoC;AAC5D,KAAI,CAAC,SAAS,GAAG,CAChB,QAAO,WAAW,GAAG;CAKtB,MAAM,eAAe,MAAM,eAAe,IAAI,gBAAgB;AAC9D,MAAK,MAAM,WAAW,cAAc;AACnC,oBAAkB,QAAQ,QAAQ,iBAAiB,GAAG,CAAC;AACvD,QAAM,GAAG,wBAAwB,IAAI,IAAI,QAAQ,GAAG,QAAQ,GAAG;;CAKhE,MAAM,SAAS,EAAE,OADE,MAAM,eAAe,IAAI,OAAO,EACjB,KAAK,UAAU,EAAE,MAAM,EAAE,EAAE;AAE7D,MAAK,MAAM,SAAS,OAAO,MAAM;EAChC,MAAM,IAAI,MAAM;AAChB,oBAAkB,EAAE;EACpB,MAAM,MAAM,GAAG,EAAE;EAMjB;GACC,MAAM,MAAM;GAKZ,MAAM,WAHY,MAAM,GAAe;wBAClB,IAAI,IAAI,EAAE,CAAC;KAC9B,QAAQ,IAAI,EACY;AAM1B,OAAI,QAAQ,MAAM,QAAQ,IAAI,SAAS,SAAS,CAC/C;GAID,MAAM,YAAY,MAAM,GAAc;wBACjB,IAAI,IAAI,EAAE,CAAC;KAC9B,QAAQ,IAAI;GAGd,MAAM,YAAqF,EAAE;AAC7F,QAAK,MAAM,OAAO,UAAU,MAAM;AAEjC,QAAI,IAAI,WAAW,QAAQ,IAAI,KAAK,WAAW,oBAAoB,CAAE;IAErE,MAAM,eAAe,MAAM,GAAoB;yBAC1B,IAAI,IAAI,IAAI,KAAK,CAAC;MACrC,QAAQ,IAAI;AAEd,cAAU,KAAK;KACd,MAAM,IAAI;KACV,QAAQ,IAAI,WAAW;KACvB,SAAS,aAAa,KAAK,KAAK,MAAM,EAAE,KAAK;KAC7C,SAAS,IAAI;KACb,CAAC;;GAIH,MAAM,8BAAc,IAAI,KAAqB;AAC7C,QAAK,MAAM,OAAO,UACjB,KAAI,IAAI,SAAS;IAChB,MAAM,eAAe,MAAM,GAAoB;;wCAEZ,IAAI,KAAK;OAC1C,QAAQ,IAAI;AACd,QAAI,aAAa,KAAK,IAAI,IACzB,aAAY,IAAI,IAAI,MAAM,aAAa,KAAK,GAAG,IAAI;;AAQtD,QAAK,MAAM,OAAO,QACjB,oBAAmB,IAAI,MAAM,cAAc;GAI5C,MAAM,UAAoB,EAAE;GAC5B,MAAM,WAAqB,EAAE;AAE7B,QAAK,MAAM,OAAO,SAAS;AAC1B,uBAAmB,IAAI,QAAQ,QAAQ,IAAI,KAAK;AAChD,aAAS,KAAK,WAAW,IAAI,KAAK,CAAC;IACnC,IAAI,MAAM,GAAG,WAAW,IAAI,KAAK,CAAC,GAAG,IAAI,QAAQ;AAEjD,QAAI,IAAI,GACP,QAAO;aACG,IAAI,SAAS,QAAQ,YAG3B,IAAI,QAAS,QAAO;AAGzB,QAAI,IAAI,eAAe,MAAM;AAC5B,0BAAqB,IAAI,YAAY,IAAI,KAAK;AAC9C,YAAO,YAAY,oBAAoB,IAAI,WAAW;;AAGvD,YAAQ,KAAK,IAAI;;AAIlB,WAAQ,KAAK,wCAAwC;AACrD,WAAQ,KAAK,6BAA2B;AAGxC,WAAQ,KAAK,+BAA2B;GAExC,MAAM,gBAAgB,QAAQ,KAAK,UAAc;GACjD,MAAM,gBAAgB,SAAS,KAAK,KAAK;AAIzC,QAAK,MAAM,OAAO,UACjB,OAAM,GAAG,wBAAwB,IAAI,IAAI,IAAI,KAAK,GAAG,QAAQ,IAAI;AAIlE,SAAM,IACJ,IAAI,gBAAgB,WAAW,IAAI,CAAC,cAAc,cAAc,WAAW,CAC3E,QAAQ,IAAI;AAGd,SAAM,IACJ,IACA,eAAe,WAAW,IAAI,CAAC,IAAI,cAAc,kDAAkD,cAAc,oBAAoB,WAAW,EAAE,GAClJ,CACA,QAAQ,IAAI;AAGd,SAAM,GAAG,cAAc,IAAI,IAAI,EAAE,GAAG,QAAQ,IAAI;AAChD,SAAM,IAAI,IAAI,eAAe,WAAW,IAAI,CAAC,aAAa,WAAW,EAAE,GAAG,CAAC,QAAQ,IAAI;AAGvF,QAAK,MAAM,OAAO,WAAW;AAE5B,QAAI,IAAI,SAAS,OAAO,EAAE,OAAQ;AAElC,QAAI,IAAI,WAAW,YAAY,IAAI,IAAI,KAAK,EAAE;KAE7C,MAAM,SAAS,YAAY,IAAI,IAAI,KAAK;AACxC,4BAAuB,QAAQ,IAAI,KAAK;AACxC,WAAM,IAAI,IAAI,OAAO,CAAC,QAAQ,IAAI;WAC5B;AAEN,UAAK,MAAM,KAAK,IAAI,QACnB,oBAAmB,GAAG,oBAAoB;KAE3C,MAAM,OAAO,IAAI,QAAQ,KAAK,MAAM,WAAW,EAAE,CAAC,CAAC,KAAK,KAAK;KAC7D,MAAM,SAAS,IAAI,SAAS,YAAY;AACxC,WAAM,IACJ,IAAI,UAAU,OAAO,QAAQ,WAAW,IAAI,KAAK,CAAC,MAAM,WAAW,EAAE,CAAC,IAAI,KAAK,GAAG,CAClF,QAAQ,IAAI;;;AAOhB,SAAM,GAAG;mBACO,IAAI,IAAI,OAAO,EAAE,OAAO,CAAC;SACnC,IAAI,IAAI,EAAE,CAAC;KACf,QAAQ,IAAI;AAEd,SAAM,GAAG;mBACO,IAAI,IAAI,OAAO,EAAE,SAAS,CAAC;SACrC,IAAI,IAAI,EAAE,CAAC;KACf,QAAQ,IAAI;AAEd,SAAM,GAAG;mBACO,IAAI,IAAI,OAAO,EAAE,oBAAoB,CAAC;SAChD,IAAI,IAAI,EAAE,CAAC;KACf,QAAQ,IAAI;;;AAUhB,KAAI,EAHc,MAAM,GAAe;;GAErC,QAAQ,GAAG,EACE,KAAK,MAAM,QAAQ,IAAI,SAAS,eAAe,CAC7D,OAAM,GAAG;;;IAGP,QAAQ,GAAG;;;;;AAOf,eAAe,aAAa,IAAoC;AAC/D,OAAM,GAAG,sDAAsD,QAAQ,GAAG;CAE1E,MAAM,aAAa,MAAM,eAAe,IAAI,OAAO;AACnD,MAAK,MAAM,KAAK,YAAY;AAC3B,oBAAkB,EAAE;AAGpB,QAAM,GAAG,wBAAwB,IAAI,IAAI,OAAO,EAAE,SAAS,GAAG,QAAQ,GAAG;AACzE,QAAM,GAAG,wBAAwB,IAAI,IAAI,OAAO,EAAE,oBAAoB,GAAG,QAAQ,GAAG;AAGpF,QAAM,GAAG,eAAe,IAAI,IAAI,EAAE,CAAC,6BAA6B,IAAI,IAAI,GAAG,EAAE,qBAAqB,GAAG,QACpG,GACA;AAGD,QAAM,GAAG,eAAe,IAAI,IAAI,EAAE,CAAC,kBAAkB,IAAI,IAAI,GAAG,EAAE,cAAc,CAAC,gBAAgB,QAChG,GACA;AAGD,QAAM,GAAG,eAAe,IAAI,IAAI,EAAE,CAAC,qBAAqB,QAAQ,GAAG;AACnE,QAAM,GAAG,eAAe,IAAI,IAAI,EAAE,CAAC,gCAAgC,QAAQ,GAAG;;;AAIhF,eAAsBC,QAAK,IAAoC;AAC9D,KAAI,CAAC,SAAS,GAAG,CAChB,QAAO,aAAa,GAAG;AAIxB,OAAM,GAAG;;;GAGP,QAAQ,GAAG;CAGb,MAAM,aAAa,MAAM,eAAe,IAAI,OAAO;AAEnD,MAAK,MAAM,aAAa,YAAY;EACnC,MAAM,IAAI;AACV,oBAAkB,EAAE;EACpB,MAAM,MAAM,GAAG,EAAE;EAGjB;GACC,MAAM,MAAM;GAKZ,MAAM,WAHY,MAAM,GAAe;wBAClB,IAAI,IAAI,EAAE,CAAC;KAC9B,QAAQ,IAAI,EACY;GAG1B,MAAM,YAAY,MAAM,GAAc;wBACjB,IAAI,IAAI,EAAE,CAAC;KAC9B,QAAQ,IAAI;GAEd,MAAM,YAAqF,EAAE;AAC7F,QAAK,MAAM,OAAO,UAAU,MAAM;AACjC,QAAI,IAAI,WAAW,QAAQ,IAAI,KAAK,WAAW,oBAAoB,CAAE;IAErE,MAAM,eAAe,MAAM,GAAoB;yBAC1B,IAAI,IAAI,IAAI,KAAK,CAAC;MACrC,QAAQ,IAAI;AAEd,cAAU,KAAK;KACd,MAAM,IAAI;KACV,QAAQ,IAAI,WAAW;KACvB,SAAS,aAAa,KAAK,KAAK,MAAM,EAAE,KAAK;KAC7C,SAAS,IAAI;KACb,CAAC;;GAIH,MAAM,8BAAc,IAAI,KAAqB;AAC7C,QAAK,MAAM,OAAO,UACjB,KAAI,IAAI,SAAS;IAChB,MAAM,eAAe,MAAM,GAAoB;;wCAEZ,IAAI,KAAK;OAC1C,QAAQ,IAAI;AACd,QAAI,aAAa,KAAK,IAAI,IACzB,aAAY,IAAI,IAAI,MAAM,aAAa,KAAK,GAAG,IAAI;;AAOtD,QAAK,MAAM,OAAO,SAAS;AAC1B,QAAI,IAAI,SAAS,YAAY,IAAI,SAAS,oBAAqB;AAC/D,uBAAmB,IAAI,MAAM,cAAc;;GAI5C,MAAM,UAAoB,EAAE;GAC5B,MAAM,WAAqB,EAAE;AAE7B,QAAK,MAAM,OAAO,SAAS;AAE1B,QAAI,IAAI,SAAS,YAAY,IAAI,SAAS,oBAAqB;AAE/D,uBAAmB,IAAI,QAAQ,QAAQ,IAAI,KAAK;AAChD,aAAS,KAAK,WAAW,IAAI,KAAK,CAAC;IACnC,IAAI,MAAM,GAAG,WAAW,IAAI,KAAK,CAAC,GAAG,IAAI,QAAQ;AAEjD,QAAI,IAAI,GACP,QAAO;aACG,IAAI,SAAS,OAEvB,QAAO;aAEH,IAAI,QAAS,QAAO;AAGzB,QAAI,IAAI,eAAe,MAAM;AAC5B,0BAAqB,IAAI,YAAY,IAAI,KAAK;AAC9C,YAAO,YAAY,oBAAoB,IAAI,WAAW;;AAGvD,YAAQ,KAAK,IAAI;;GAGlB,MAAM,gBAAgB,QAAQ,KAAK,UAAc;GACjD,MAAM,gBAAgB,SAAS,KAAK,KAAK;AAIzC,QAAK,MAAM,OAAO,UACjB,OAAM,GAAG,wBAAwB,IAAI,IAAI,IAAI,KAAK,GAAG,QAAQ,IAAI;AAIlE,SAAM,IACJ,IAAI,gBAAgB,WAAW,IAAI,CAAC,cAAc,cAAc,WAAW,CAC3E,QAAQ,IAAI;AAOd,SAAM,IACJ,IACA,yBAAyB,WAAW,IAAI,CAAC,IAAI,cAAc;aACnD,cAAc,QAAQ,WAAW,EAAE,CAAC;2BAE5C,CACA,QAAQ,IAAI;AAEd,SAAM,IACJ,IACA,yBAAyB,WAAW,IAAI,CAAC,IAAI,cAAc;aACnD,cAAc,QAAQ,WAAW,EAAE,CAAC;0CACP,WAAW,IAAI,CAAC;;uBAEnC,WAAW,EAAE,CAAC;;;oCAGD,WAAW,EAAE,CAAC;;;OAI7C,CACA,QAAQ,IAAI;AAGd,SAAM,IACJ,IACA,yBAAyB,WAAW,IAAI,CAAC,IAAI,cAAc;aACnD,cAAc,QAAQ,WAAW,EAAE,CAAC;0CACP,WAAW,IAAI,CAAC;qCAErD,CACA,QAAQ,IAAI;AAGd,SAAM,GAAG,cAAc,IAAI,IAAI,EAAE,GAAG,QAAQ,IAAI;AAChD,SAAM,IAAI,IAAI,eAAe,WAAW,IAAI,CAAC,aAAa,WAAW,EAAE,GAAG,CAAC,QAAQ,IAAI;AAGvF,QAAK,MAAM,OAAO,WAAW;AAE5B,QAAI,IAAI,SAAS,OAAO,EAAE,SAAU;AACpC,QAAI,IAAI,SAAS,OAAO,EAAE,oBAAqB;AAE/C,QAAI,IAAI,WAAW,YAAY,IAAI,IAAI,KAAK,EAAE;KAE7C,MAAM,SAAS,YAAY,IAAI,IAAI,KAAK;AACxC,4BAAuB,QAAQ,IAAI,KAAK;AACxC,WAAM,IAAI,IAAI,OAAO,CAAC,QAAQ,IAAI;WAC5B;KAEN,MAAM,OAAO,IAAI,QAAQ,QAAQ,MAAM,MAAM,YAAY,MAAM,oBAAoB;AACnF,SAAI,KAAK,WAAW,EAAG;AAGvB,UAAK,MAAM,KAAK,KACf,oBAAmB,GAAG,oBAAoB;KAE3C,MAAM,UAAU,KAAK,KAAK,MAAM,WAAW,EAAE,CAAC,CAAC,KAAK,KAAK;KACzD,MAAM,SAAS,IAAI,SAAS,YAAY;AACxC,WAAM,IACJ,IAAI,UAAU,OAAO,QAAQ,WAAW,IAAI,KAAK,CAAC,MAAM,WAAW,EAAE,CAAC,IAAI,QAAQ,GAAG,CACrF,QAAQ,IAAI;;;;;;;;;;;;;;;;;;;;AC1lBnB,eAAsBC,MAAG,IAAoC;AAC5D,OAAM,GAAG;;;GAGP,QAAQ,GAAG;;AAGd,eAAsBC,QAAK,IAAoC;AAC9D,OAAM,GAAG;;;GAGP,QAAQ,GAAG;;;;;;;;;;;;;;;;ACZd,eAAsBC,MAAG,IAAoC;AAE5D,OAAM,GAAG,OAAO,UAAU,wBAAwB,CAAC,UAAU,CAAC,SAAS;AAEvE,OAAM,GAAG,OAAO,WAAW,mBAAmB,CAAC,WAAW,cAAc,CAAC,SAAS;AAElF,OAAM,GAAG,OAAO,UAAU,6BAA6B,CAAC,SAAS;;AAGlE,eAAsBC,QAAK,IAAoC;AAE9D,OAAM,GAAG,OACP,YAAY,6BAA6B,CACzC,UAAU,MAAM,SAAS,QAAQ,IAAI,YAAY,CAAC,CAClD,UAAU,QAAQ,SAAS,QAAQ,IAAI,SAAS,CAAC,QAAQ,CAAC,CAC1D,UAAU,SAAS,SAAS,QAAQ,IAAI,SAAS,CAAC,CAClD,UAAU,cAAc,YAAY,QAAQ,IAAI,UAAU,EAAE,CAAC,CAC7D,UAAU,cAAc,SAAS,QAAQ,IAAI,UAAU,GAAG,oBAAoB,CAAC,CAC/E,SAAS;AAGX,OAAM,GAAG,OACP,WAAW,mBAAmB,CAC9B,UAAU,eAAe,SAAS,QAClC,IAAI,WAAW,gCAAgC,CAAC,SAAS,WAAW,CACpE,CACA,SAAS;AAEX,OAAM,GAAG,OACP,YAAY,wBAAwB,CACpC,GAAG,mBAAmB,CACtB,QAAQ,CAAC,cAAc,CAAC,CACxB,SAAS;;;;;;;;;;;;;;;;AC/BZ,eAAsBC,MAAG,IAAoC;AAE5D,OAAM,GAAG;;;GAGP,QAAQ,GAAG;AAGb,OAAM,GAAG;;;GAGP,QAAQ,GAAG;AAGb,OAAM,GAAG;;;;GAIP,QAAQ,GAAG;;AAGd,eAAsBC,QAAK,IAAoC;AAC9D,OAAM,GAAG;;GAEP,QAAQ,GAAG;AAEb,OAAM,GAAG;;;GAGP,QAAQ,GAAG;AAEb,OAAM,GAAG;;;GAGP,QAAQ,GAAG;;;;;;;;;;;;;;;;AClCd,eAAsBC,KAAG,IAAoC;AAC5D,OAAM,GAAG;;;GAGP,QAAQ,GAAG;AAEb,OAAM,GAAG;;;GAGP,QAAQ,GAAG;;AAGd,eAAsBC,OAAK,IAAoC;AAC9D,OAAM,GAAG;;;GAGP,QAAQ,GAAG;AAEb,OAAM,GAAG;;;GAGP,QAAQ,GAAG;;;;;;;;;;;;;;;ACtBd,eAAsBC,KAAG,IAAoC;AAC5D,OAAM,GAAG;;;GAGP,QAAQ,GAAG;AAEb,OAAM,GAAG;;;GAGP,QAAQ,GAAG;;AAGd,eAAsBC,OAAK,IAAoC;AAC9D,OAAM,GAAG;;;GAGP,QAAQ,GAAG;AAEb,OAAM,GAAG;;;GAGP,QAAQ,GAAG;;;;;;;;;;;;;;;;;;ACjBd,eAAsBC,KAAG,IAAoC;AAC5D,OAAM,GAAG,OACP,YAAY,wBAAwB,CACpC,UAAU,MAAM,SAAS,QAAQ,IAAI,YAAY,CAAC,CAClD,UAAU,QAAQ,SAAS,QAAQ,IAAI,SAAS,CAAC,CACjD,UAAU,iBAAiB,SAAS,QAAQ,IAAI,SAAS,CAAC,CAC1D,UAAU,UAAU,OAAO,CAC3B,UAAU,cAAc,SAAS,QAAQ,IAAI,UAAU,iBAAiB,GAAG,CAAC,CAAC,CAC7E,UAAU,cAAc,SAAS,QAAQ,IAAI,UAAU,iBAAiB,GAAG,CAAC,CAAC,CAC7E,SAAS;;AAGZ,eAAsBC,OAAK,IAAoC;AAC9D,OAAM,GAAG,OAAO,UAAU,wBAAwB,CAAC,SAAS;;;;;;;;;;;;;;;;;;ACb7D,eAAsBC,KAAG,IAAoC;AAC5D,OAAM,GAAG,OACP,YAAY,qBAAqB,CACjC,UAAU,MAAM,SAAS,QAAQ,IAAI,YAAY,CAAC,CAClD,UAAU,aAAa,SAAS,QAAQ,IAAI,SAAS,CAAC,CACtD,UAAU,aAAa,SAAS,QAAQ,IAAI,SAAS,CAAC,CACtD,UAAU,YAAY,SAAS,QAAQ,IAAI,SAAS,CAAC,CACrD,UAAU,cAAc,YAAY,QAAQ,IAAI,SAAS,CAAC,UAAU,EAAE,CAAC,CACvE,UAAU,QAAQ,OAAO,CACzB,UAAU,eAAe,SAAS,QAAQ,IAAI,SAAS,CAAC,CACxD,UAAU,eAAe,OAAO,CAChC,UAAU,UAAU,SAAS,QAAQ,IAAI,SAAS,CAAC,UAAU,OAAO,CAAC,CACrE,UAAU,aAAa,OAAO,CAC9B,UAAU,WAAW,YAAY,QAAQ,IAAI,SAAS,CAAC,UAAU,EAAE,CAAC,CACpE,UAAU,cAAc,SAAS,QAAQ,IAAI,UAAU,iBAAiB,GAAG,CAAC,CAAC,CAC7E,oBAAoB,6BAA6B,CAAC,aAAa,YAAY,CAAC,CAC5E,SAAS;AAIX,OAAM,GAAG,OACP,YAAY,qBAAqB,CACjC,GAAG,qBAAqB,CACxB,QAAQ;EAAC;EAAW;EAAU;EAAc,CAAC,CAC7C,SAAS;AAEX,OAAM,GAAG,OACP,YAAY,wBAAwB,CACpC,GAAG,qBAAqB,CACxB,OAAO,YAAY,CACnB,SAAS;;AAGZ,eAAsBC,OAAK,IAAoC;AAC9D,OAAM,GAAG,OAAO,UAAU,qBAAqB,CAAC,SAAS;;;;;;;;;AC3C1D,eAAsBC,KAAG,IAAoC;AAE5D,OAAM,GAAG,OACP,YAAY,mBAAmB,CAC/B,UAAU,MAAM,SAAS,QAAQ,IAAI,YAAY,CAAC,CAClD,UAAU,cAAc,SAAS,QAAQ,IAAI,SAAS,CAAC,CACvD,UAAU,cAAc,SAAS,QAAQ,IAAI,SAAS,CAAC,CACvD,UAAU,aAAa,SAAS,QAChC,IAAI,WAAW,sBAAsB,CAAC,SAAS,UAAU,CACzD,CACA,UAAU,eAAe,SAAS,QAAQ,IAAI,SAAS,CAAC,CACxD,UAAU,gBAAgB,SAAS,QAAQ,IAAI,SAAS,CAAC,CACzD,UAAU,cAAc,OAAO,CAC/B,UAAU,kBAAkB,SAAS,QAAQ,IAAI,WAAW,WAAW,CAAC,SAAS,WAAW,CAAC,CAC7F,UAAU,QAAQ,SAAS,QAAQ,IAAI,SAAS,CAAC,CACjD,UAAU,UAAU,SAAS,QAAQ,IAAI,SAAS,CAAC,UAAU,UAAU,CAAC,CACxE,UAAU,WAAW,OAAO,CAC5B,UAAU,cAAc,OAAO,CAC/B,UAAU,uBAAuB,OAAO,CACxC,UAAU,cAAc,SAAS,QAAQ,IAAI,UAAU,iBAAiB,GAAG,CAAC,CAAC,CAC7E,UAAU,cAAc,SAAS,QAAQ,IAAI,UAAU,iBAAiB,GAAG,CAAC,CAAC,CAC7E,SAAS;AAGX,OAAM,GAAG,OACP,YAAY,uBAAuB,CACnC,GAAG,mBAAmB,CACtB,QAAQ;EAAC;EAAc;EAAc;EAAS,CAAC,CAC/C,SAAS;AAEX,OAAM,GAAG,OACP,YAAY,sBAAsB,CAClC,GAAG,mBAAmB,CACtB,OAAO,YAAY,CACnB,SAAS;AAEX,OAAM,GAAG,OACP,YAAY,sBAAsB,CAClC,GAAG,mBAAmB,CACtB,QAAQ,CAAC,UAAU,aAAa,CAAC,CACjC,SAAS;AAEX,OAAM,GAAG,OACP,YAAY,4BAA4B,CACxC,GAAG,mBAAmB,CACtB,OAAO,eAAe,CACtB,SAAS;AAEX,OAAM,GAAG,OACP,YAAY,2BAA2B,CACvC,GAAG,mBAAmB,CACtB,OAAO,iBAAiB,CACxB,SAAS;AAGX,OAAM,GAAG,OACP,WAAW,sBAAsB,CACjC,UAAU,oBAAoB,YAAY,QAAQ,IAAI,UAAU,EAAE,CAAC,CACnE,SAAS;AAEX,OAAM,GAAG,OACP,WAAW,sBAAsB,CACjC,UAAU,uBAAuB,SAAS,QAAQ,IAAI,UAAU,aAAa,CAAC,CAC9E,SAAS;AAEX,OAAM,GAAG,OACP,WAAW,sBAAsB,CACjC,UAAU,8BAA8B,YAAY,QAAQ,IAAI,UAAU,GAAG,CAAC,CAC9E,SAAS;AAEX,OAAM,GAAG,OACP,WAAW,sBAAsB,CACjC,UAAU,+BAA+B,YAAY,QAAQ,IAAI,UAAU,EAAE,CAAC,CAC9E,SAAS;;AAGZ,eAAsBC,OAAK,IAAoC;AAC9D,OAAM,GAAG,OAAO,UAAU,mBAAmB,CAAC,SAAS;;;;;;;;;AC/ExD,eAAsBC,KAAG,IAAoC;AAC5D,OAAM,GAAG,sDAAsD,QAAQ,GAAG;;AAG3E,eAAsBC,OAAK,IAAoC;AAC9D,OAAM,GAAG,OAAO,WAAW,mBAAmB,CAAC,UAAU,cAAc,OAAO,CAAC,SAAS;;;;;;;;;ACHzF,eAAsBC,KAAG,IAAoC;AAE5D,OAAM,GAAG,OACP,YAAY,oBAAoB,CAChC,UAAU,MAAM,SAAS,QAAQ,IAAI,YAAY,CAAC,CAClD,UAAU,UAAU,SAAS,QAAQ,IAAI,SAAS,CAAC,CACnD,UAAU,eAAe,SAAS,QAAQ,IAAI,SAAS,CAAC,CACxD,UAAU,QAAQ,YAAY,QAAQ,IAAI,SAAS,CAAC,UAAU,IAAI,CAAC,CACnE,UAAU,cAAc,YAAY,QAAQ,IAAI,SAAS,CAAC,UAAU,EAAE,CAAC,CACvE,UAAU,WAAW,YAAY,QAAQ,IAAI,SAAS,CAAC,UAAU,EAAE,CAAC,CACpE,UAAU,QAAQ,YAAY,QAAQ,IAAI,SAAS,CAAC,UAAU,EAAE,CAAC,CACjE,UAAU,eAAe,OAAO,CAChC,UAAU,cAAc,OAAO,CAC/B,UAAU,QAAQ,YAAY,QAAQ,IAAI,SAAS,CAAC,UAAU,EAAE,CAAC,CACjE,UAAU,cAAc,SAAS,QAAQ,IAAI,UAAU,iBAAiB,GAAG,CAAC,CAAC,CAC7E,UAAU,cAAc,SAAS,QAAQ,IAAI,UAAU,iBAAiB,GAAG,CAAC,CAAC,CAC7E,SAAS;AAKX,OAAM,GAAG,OACP,YAAY,uBAAuB,CACnC,GAAG,oBAAoB,CACvB,OAAO,SAAS,CAChB,SAAS;AAEX,OAAM,GAAG,OACP,YAAY,wBAAwB,CACpC,GAAG,oBAAoB,CACvB,OAAO,UAAU,CACjB,SAAS;AAEX,OAAM,GAAG,OACP,YAAY,sBAAsB,CAClC,GAAG,oBAAoB,CACvB,OAAO,aAAa,CACpB,SAAS;AAGX,OAAM,GAAG,OACP,YAAY,kBAAkB,CAC9B,UAAU,MAAM,SAAS,QAAQ,IAAI,YAAY,CAAC,CAClD,UAAU,QAAQ,SAAS,QAAQ,IAAI,SAAS,CAAC,CACjD,UAAU,YAAY,OAAO,CAC7B,UAAU,cAAc,OAAO,CAC/B,UAAU,MAAM,OAAO,CACvB,UAAU,cAAc,SAAS,QAAQ,IAAI,UAAU,iBAAiB,GAAG,CAAC,CAAC,CAC7E,SAAS;AAEX,OAAM,GAAG,OAAO,YAAY,mBAAmB,CAAC,GAAG,kBAAkB,CAAC,OAAO,OAAO,CAAC,SAAS;AAE9F,OAAM,GAAG,OACP,YAAY,sBAAsB,CAClC,GAAG,kBAAkB,CACrB,OAAO,aAAa,CACpB,SAAS;;AAGZ,eAAsBC,OAAK,IAAoC;AAC9D,OAAM,GAAG,OAAO,UAAU,kBAAkB,CAAC,SAAS;AACtD,OAAM,GAAG,OAAO,UAAU,oBAAoB,CAAC,SAAS;;;;;;;;;;;;;;;;ACrDzD,eAAsBC,KAAG,IAAoC;CAC5D,MAAM,aAAa,MAAM,eAAe,IAAI,OAAO;AAEnD,MAAK,MAAM,aAAa,YAAY;EACnC,MAAM,QAAQ,EAAE,MAAM,WAAW;AAEjC,QAAM,GAAG;0BACe,IAAI,IAAI,OAAO,MAAM,KAAK,YAAY,CAAC;IAC7D,QAAQ,GAAG;AAEb,QAAM,GAAG;kBACO,IAAI,IAAI,OAAO,MAAM,KAAK,YAAY,CAAC;QACjD,IAAI,IAAI,MAAM,KAAK,CAAC;;IAExB,QAAQ,GAAG;;;AAIf,eAAsBC,OAAK,IAAoC;CAC9D,MAAM,aAAa,MAAM,eAAe,IAAI,OAAO;AAEnD,MAAK,MAAM,aAAa,YAAY;EACnC,MAAM,QAAQ,EAAE,MAAM,WAAW;AAEjC,QAAM,GAAG;0BACe,IAAI,IAAI,OAAO,MAAM,KAAK,YAAY,CAAC;IAC7D,QAAQ,GAAG;AAGb,QAAM,GAAG;kBACO,IAAI,IAAI,OAAO,MAAM,KAAK,YAAY,CAAC;QACjD,IAAI,IAAI,MAAM,KAAK,CAAC;;IAExB,QAAQ,GAAG;;;;;;;;;;ACxCf,eAAsBC,KAAG,IAAoC;AAC5D,OAAM,GAAG,OACP,YAAY,kBAAkB,CAC9B,UAAU,MAAM,SAAS,QAAQ,IAAI,YAAY,CAAC,CAClD,UAAU,QAAQ,SAAS,QAAQ,IAAI,SAAS,CAAC,QAAQ,CAAC,CAC1D,UAAU,gBAAgB,SAAS,QAAQ,IAAI,SAAS,CAAC,CACzD,UAAU,OAAO,OAAO,CACxB,UAAU,mBAAmB,SAAS,QAAQ,IAAI,WAAW,WAAW,CAAC,SAAS,WAAW,CAAC,CAC9F,UAAU,eAAe,OAAO,CAChC,UAAU,WAAW,SAAS,QAAQ,IAAI,WAAW,WAAW,CAAC,SAAS,WAAW,CAAC,CACtF,UAAU,YAAY,YAAY,QAAQ,IAAI,SAAS,CAAC,UAAU,EAAE,CAAC,CACrE,UAAU,cAAc,SAAS,QAAQ,IAAI,UAAU,iBAAiB,GAAG,CAAC,CAAC,CAC7E,UAAU,cAAc,SAAS,QAAQ,IAAI,UAAU,iBAAiB,GAAG,CAAC,CAAC,CAC7E,SAAS;AAEX,OAAM,GAAG;wBACc,IAAI,IAAI,6BAA6B,CAAC;OACvD,IAAI,IAAI,kBAAkB,CAAC;;GAE/B,QAAQ,GAAG;AAEb,OAAM,GAAG,OAAO,YAAY,mBAAmB,CAAC,GAAG,kBAAkB,CAAC,OAAO,OAAO,CAAC,SAAS;AAE9F,OAAM,GAAG,OACP,YAAY,2BAA2B,CACvC,GAAG,kBAAkB,CACrB,OAAO,eAAe,CACtB,SAAS;AAEX,OAAM,GAAG,OACP,YAAY,0BAA0B,CACtC,UAAU,MAAM,SAAS,QAAQ,IAAI,YAAY,CAAC,CAClD,UAAU,mBAAmB,SAAS,QAAQ,IAAI,SAAS,CAAC,CAC5D,UAAU,cAAc,SAAS,QAAQ,IAAI,SAAS,CAAC,CACvD,UAAU,aAAa,SAAS,QAChC,IAAI,SAAS,CAAC,WAAW,qBAAqB,CAAC,SAAS,UAAU,CAClE,CACA,UAAU,cAAc,YAAY,QAAQ,IAAI,SAAS,CAAC,UAAU,EAAE,CAAC,CACvE,UAAU,cAAc,OAAO,CAC/B,UAAU,cAAc,SAAS,QAAQ,IAAI,UAAU,iBAAiB,GAAG,CAAC,CAAC,CAC7E,oBAAoB,0BAA0B;EAAC;EAAmB;EAAc;EAAY,CAAC,CAC7F,SAAS;AAEX,OAAM,GAAG,OACP,YAAY,8BAA8B,CAC1C,GAAG,0BAA0B,CAC7B,QAAQ;EAAC;EAAmB;EAAc;EAAa,CAAC,CACxD,SAAS;AAEX,OAAM,GAAG,OACP,YAAY,6BAA6B,CACzC,GAAG,0BAA0B,CAC7B,OAAO,YAAY,CACnB,SAAS;CAEX,MAAM,aAAa,MAAM,eAAe,IAAI,OAAO;AACnD,MAAK,MAAM,aAAa,YAAY;AACnC,QAAM,GAAG;iBACM,IAAI,IAAI,UAAU,CAAC;;IAEhC,QAAQ,GAAG;AAEb,QAAM,GAAG;kBACO,IAAI,IAAI,OAAO,UAAU,iBAAiB,CAAC;QACrD,IAAI,IAAI,UAAU,CAAC;IACvB,QAAQ,GAAG;;;AAIf,eAAsBC,OAAK,IAAoC;CAC9D,MAAM,aAAa,MAAM,eAAe,IAAI,OAAO;AACnD,MAAK,MAAM,aAAa,YAAY;AACnC,QAAM,GAAG;0BACe,IAAI,IAAI,OAAO,UAAU,iBAAiB,CAAC;IACjE,QAAQ,GAAG;AAEb,QAAM,GAAG;iBACM,IAAI,IAAI,UAAU,CAAC;;IAEhC,QAAQ,GAAG;;AAGd,OAAM,GAAG,OAAO,UAAU,0BAA0B,CAAC,SAAS;AAC9D,OAAM,GAAG,OAAO,UAAU,kBAAkB,CAAC,SAAS;;;;;;;;;;;;;;;;;;AC7EvD,eAAsB,GAAG,IAAoC;AAE5D,OAAM,GAAG,OACP,YAAY,sBAAsB,CAClC,UAAU,OAAO,SAAS,QAAQ,IAAI,SAAS,CAAC,CAChD,UAAU,UAAU,SAAS,QAAQ,IAAI,SAAS,CAAC,CACnD,UAAU,SAAS,YAAY,QAAQ,IAAI,SAAS,CAAC,UAAU,EAAE,CAAC,CAClE,wBAAwB,kBAAkB,CAAC,OAAO,SAAS,CAAC,CAC5D,SAAS;AAGX,OAAM,GAAG,OACP,YAAY,yBAAyB,CACrC,GAAG,sBAAsB,CACzB,OAAO,SAAS,CAChB,SAAS;AAGX,OAAM,GAAG,OAAO,WAAW,uBAAuB,CAAC,UAAU,kBAAkB,OAAO,CAAC,SAAS;;AAGjG,eAAsB,KAAK,IAAoC;AAC9D,OAAM,GAAG,OAAO,UAAU,sBAAsB,CAAC,SAAS;AAI1D,OAAM,GAAG,OAAO,WAAW,uBAAuB,CAAC,WAAW,iBAAiB,CAAC,SAAS;;;;;;;;;ACG1F,IAAM,0BAAN,MAA2D;CAC1D,MAAM,gBAAoD;AACzD,SAAO;GACN,eAAeC;GACf,oBAAoBC;GACpB,uBAAuBC;GACvB,eAAeC;GACf,aAAaC;GACb,qBAAqBC;GACrB,eAAeC;GACf,YAAYC;GACZ,qBAAqBC;GACrB,gBAAgBC;GAChB,cAAcC;GACd,4BAA4BC;GAC5B,uBAAuBC;GACvB,eAAeC;GACf,kBAAkBC;GAClB,2BAA2BC;GAC3B,WAAWC;GACX,YAAYC;GACZ,8BAA8BC;GAC9B,iCAAiCC;GACjC,gCAAgCC;GAChC,uBAAuBC;GACvB,0BAA0BC;GAC1B,qBAAqBC;GACrB,kBAAkBC;GAClB,gBAAgBC;GAChB,uBAAuBC;GACvB,iBAAiBC;GACjB,6BAA6BC;GAC7B,eAAeC;GACf,mBAAmBC;GACnB;;;;AAUH,MAAM,kBAAkB;AACxB,MAAM,uBAAuB;;;;AAK7B,eAAsB,mBAAmB,IAAgD;CAQxF,MAAM,aAAa,MAPF,IAAI,SAAS;EAC7B;EACA,UAAU,IAAI,yBAAyB;EACvC,oBAAoB;EACpB,wBAAwB;EACxB,CAAC,CAEgC,eAAe;CAEjD,MAAM,UAAoB,EAAE;CAC5B,MAAM,UAAoB,EAAE;AAE5B,MAAK,MAAM,aAAa,WACvB,KAAI,UAAU,WACb,SAAQ,KAAK,UAAU,KAAK;KAE5B,SAAQ,KAAK,UAAU,KAAK;AAI9B,QAAO;EAAE;EAAS;EAAS;;;;;AAM5B,eAAsB,cAAc,IAAsD;CAQzF,MAAM,EAAE,OAAO,YAAY,MAPV,IAAI,SAAS;EAC7B;EACA,UAAU,IAAI,yBAAyB;EACvC,oBAAoB;EACpB,wBAAwB;EACxB,CAAC,CAEwC,iBAAiB;CAE3D,MAAM,UAAU,SAAS,QAAQ,MAAM,EAAE,WAAW,UAAU,CAAC,KAAK,MAAM,EAAE,cAAc,IAAI,EAAE;AAEhG,KAAI,OAAO;EAGV,IAAI,MAAM,iBAAiB,QAAQ,MAAM,UAAU,KAAK,UAAU,MAAM;AACxE,MAAI,CAAC,OAAO,iBAAiB,SAAS,MAAM,MAC3C,OAAM,MAAM,iBAAiB,QAAQ,MAAM,MAAM,UAAU,KAAK,UAAU,MAAM,MAAM;EAEvF,MAAM,kBAAkB,SAAS,MAAM,MAAM,EAAE,WAAW,QAAQ;AAClE,MAAI,gBACH,OAAM,GAAG,OAAO,gBAAgB,eAAe,gBAAgB,cAAc;AAE9E,QAAM,IAAI,MAAM,qBAAqB,MAAM;;AAG5C,QAAO,EAAE,SAAS;;;;;AAMnB,eAAsB,kBACrB,IACyC;CAQzC,MAAM,EAAE,OAAO,YAAY,MAPV,IAAI,SAAS;EAC7B;EACA,UAAU,IAAI,yBAAyB;EACvC,oBAAoB;EACpB,wBAAwB;EACxB,CAAC,CAEwC,aAAa;CAEvD,MAAM,aAAa,UAAU,IAAI,WAAW,YAAY,QAAQ,GAAG,gBAAgB;AAEnF,KAAI,OAAO;EACV,MAAM,MAAM,iBAAiB,QAAQ,MAAM,UAAU,KAAK,UAAU,MAAM;AAC1E,QAAM,IAAI,MAAM,oBAAoB,MAAM;;AAG3C,QAAO,EAAE,YAAY"}